blob: 76db1c4913eb24b15099b707d7d36ed3d9613348 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_EXPR_EVAL_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_EXPR_EVAL_H_
#include <stdint.h>
#include <memory>
#include <vector>
#include "lib/fit/function.h"
#include "src/developer/debug/shared/register_id.h"
#include "src/developer/debug/zxdb/common/data_extractor.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/common/err_or.h"
#include "src/developer/debug/zxdb/common/int128_t.h"
#include "src/developer/debug/zxdb/common/tagged_data_builder.h"
#include "src/developer/debug/zxdb/symbols/arch.h"
#include "src/developer/debug/zxdb/symbols/dwarf_expr.h"
#include "src/developer/debug/zxdb/symbols/dwarf_stack_entry.h"
#include "src/developer/debug/zxdb/symbols/symbol_context.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/memory/ref_ptr.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class SymbolDataProvider;
// This class evaluates DWARF expressions. These expressions are used to encode the locations of
// variables and a few other nontrivial lookups.
//
// This class is complicated by supporting asynchronous interactions with the debugged program. This
// means that accessing register and memory data (which may be required to evaluate the expression)
// may be asynchronous.
//
// eval_ = std::make_unique<DwarfExprEval>();
// eval_.eval(..., [](DwarfExprEval* eval, const Err& err) {
// if (err.has_error()) {
// // Handle error.
// } else {
// ... use eval->GetResult() ...
// }
// });
class DwarfExprEval {
public:
// Type of completion from a call. Async completion will happen in a callback
// in the future.
enum class Completion { kSync, kAsync };
// A DWARF expression can compute either the address of the desired object in the debugged
// programs address space, or it can compute the actual value of the object (because it may not
// exist in memory).
enum class ResultType {
// The return value from GetResult() is a pointer to the result in memory. The caller will need
// to know the size and type of this result from the context.
kPointer,
// The return value from GetResult() is the resulting value itself. Most results will need
// to be truncated to the correct size (the caller needs to know the size and type from the
// context).
kValue,
// The result is stored in a data block returned by result_data(). It can be any size. Do not
// call GetResult() as the stack normally has no data on it in this case.
kData
};
enum class StringOutput {
kNone, // Don't do string output.
kLiteral, // Outputs exact DWARF opcodes and values.
kPretty, // Decodes values and register names.
};
using SignedType = DwarfStackEntry::SignedType;
using UnsignedType = DwarfStackEntry::UnsignedType;
using CompletionCallback = fit::callback<void(DwarfExprEval* eval, const Err& err)>;
DwarfExprEval();
~DwarfExprEval();
// Pushes a value on the stack. Call before Eval() for the cases where an expression requires
// some initial state.
void Push(DwarfStackEntry value);
// Clears any existing values in the stack.
void Clear() { stack_.clear(); }
// A complete expression has finished executing but may or may not have had an error. A successful
// expression indicates execution is complete and there is a valid result to read.
bool is_complete() const { return is_complete_; }
bool is_success() const { return is_success_; }
// Valid when is_success(), this indicates how to interpret the value from GetResult().
ResultType GetResultType() const;
// Valid when is_success() and type() == kPointer/kValue. Returns the result of evaluating the
// expression. The meaning will be dependent on the context of the expression being evaluated.
// Most results will be smaller than this in which case they will use only the low bits.
DwarfStackEntry GetResult() const;
// Destructively returns the generated data buffer. Valid when is_success() and type() == kData.
TaggedData TakeResultData();
// When the result is computed, this will indicate if the result is directly from a register,
// and if it is, which one. If the current result was the result of some computation and has no
// direct register source, it will be RegisterID::kUnknown.
debug::RegisterID current_register_id() const { return current_register_id_; }
// When the result is computed, this will indicate whether it's from a constant source (encoded in
// the DWARF expression) or is the result of reading some memory or registers.
bool result_is_constant() const { return result_is_constant_; }
// Evaluates the expression using the current stack. If the stack needs initial setup, callers
// should call Push() first, or Clear() if there might be unwanted data.
//
// This will take a reference to the SymbolDataProvider until the computation is complete.
//
// The symbol context is used to evaluate relative addresses. It should be the context associated
// with the module that this expression is from. Normally this will be retrieved from the
// symbol that generated the dwarf expression (see DwarfExpr::source()).
//
// The return value will indicate if the request completed synchronously. In synchronous
// completion the callback will have been called reentrantly from within the stack of this
// function. This does not indicate success as it could succeed or fail both synchronously and
// asynchronously.
//
// This class must not be deleted from within the completion callback.
Completion Eval(fxl::RefPtr<SymbolDataProvider> data_provider,
const SymbolContext& symbol_context, DwarfExpr expr, CompletionCallback cb);
// Converts the given DWARF expression to a string. The result values on this class won't be
// set since the expression won't actually be evaluated.
//
// The data_provider is required to get the current architecture for pretty-printing register
// names. To disable this, pass the default SymbolDataProvider implementation.
//
// When "pretty" mode is enabled, operations will be simplified and platform register names will
// be substituted.
std::string ToString(fxl::RefPtr<SymbolDataProvider> data_provider,
const SymbolContext& symbol_context, DwarfExpr expr, bool pretty);
private:
void SetUp(fxl::RefPtr<SymbolDataProvider> data_provider, const SymbolContext& symbol_context,
DwarfExpr expr, CompletionCallback cb);
// Evaluates the next phases of the expression until an asynchronous operation is required.
// Returns the value of |is_complete| because |this| could be deleted by the time this method
// returns.
bool ContinueEval();
// Evaluates a single operation.
Completion EvalOneOp();
// Adds a register's contents + an offset to the stack. Use 0 for the offset to get the raw
// register value.
Completion PushRegisterWithOffset(int dwarf_register_number, int128_t offset);
// These read constant data from the current index in the stream. The size of the data is in
// byte_size, and the result will be extended to a stack entry according to the type.
//
// They return true if the value was read, false if there wasn't enough data (they will issue the
// error internally, the calling code should just return on failure).
bool ReadSigned(int byte_size, SignedType* output);
bool ReadUnsigned(int byte_size, UnsignedType* output);
// Reads a signed or unsigned LEB constant from the stream. They return true if the value was
// read, false if there wasn't enough data (they will issue the error internally, the calling code
// should just return on failure).
bool ReadLEBSigned(SignedType* output);
bool ReadLEBUnsigned(UnsignedType* output);
// Schedules an asynchronous memory read. If there is any failure, including short reads, this
// will report it and fail evaluation.
//
// If the correct amount of memory is read, it will issue the callback with the data and then
// continue evaluation.
void ReadMemory(TargetPointer address, uint32_t byte_size,
fit::callback<void(DwarfExprEval* eval, std::vector<uint8_t> value)> on_success);
// Reports the given error. Always returns "kSync" so the caller can do "return ReportError()"
// which would be the common case.
Completion ReportError(const std::string& msg);
Completion ReportError(const Err& err);
void ReportStackUnderflow();
void ReportUnimplementedOpcode(uint8_t op);
// Executes the given unary operation with the top stack entry as the parameter and pushes the
// result.
Completion OpUnary(ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&), const char* op_name);
// Executes the given binary operation by popping the top two stack entries as parameters (the
// first is the next-to-top, the second is the top) and pushing the result on the stack.
Completion OpBinary(ErrOr<DwarfStackEntry> (*op)(const DwarfStackEntry&, const DwarfStackEntry&),
const char* op_name);
// Implements DW_OP_addrx and DW_OP_constx (corresponding to the given result types). The type
// of the result on the stack will be set to the given result type, and kPointer result types
// will be relocated according to the module's address offset.
Completion OpAddrBase(ResultType result_type, const char* op_name);
// Operations. On call, the data extractor will read at the byte following the opcode, and on
// return it will point to the next instruction (any parameters will be consumed).
//
// Some functions handle more than one opcode. In these cases, the opcode name for string output
// is passed in as op_name.
Completion OpAddr();
Completion OpBitPiece();
Completion OpBra();
Completion OpBreg(uint8_t op);
Completion OpCFA();
Completion OpDeref(uint32_t byte_size, const char* op_name, bool string_include_size);
Completion OpDerefSize();
Completion OpDrop();
Completion OpDup();
Completion OpEntryValue(const char* op_name);
Completion OpFbreg();
Completion OpImplicitPointer(const char* op_name);
Completion OpImplicitValue();
Completion OpRegx();
Completion OpBregx();
Completion OpOver();
Completion OpPick();
Completion OpPiece();
Completion OpPlusUconst();
Completion OpPushSigned(int byte_count, const char* op_name);
Completion OpPushUnsigned(int byte_count, const char* op_name);
Completion OpPushLEBSigned();
Completion OpPushLEBUnsigned();
Completion OpRot();
Completion OpSkip();
Completion OpStackValue();
Completion OpSwap();
Completion OpTlsAddr(const char* op_name);
// Adjusts the instruction offset by the given amount, handling out-of-bounds as appropriate. This
// is the backend for jumps and branches.
void Skip(int128_t amount);
// Returns true if generating a string rather than evaluating an expression.
bool is_string_output() const { return string_output_mode_ != StringOutput::kNone; }
// Returns a user-readable name for the current architecture's given DWARF register. For
// stringinfying DWARF expressions.
std::string GetRegisterName(int reg_number) const;
// Append an operation to the description in is_string_output() mode (will assert if used outside
// of this mode).
//
// When in kPretty output mode, the second parameter will be used if present. Otherwise the first
// parameter will be used. The first output should be the actual DWARF operatiors, while the
// second one can have another level of decode.
//
// Always returns "sync" completion so it can be used like "return AppendString(...)" from the
// opcode handlers.
Completion AppendString(const std::string& op_output,
const std::string& nice_output = std::string());
fxl::RefPtr<SymbolDataProvider> data_provider_;
SymbolContext symbol_context_;
// The expression. See also data_extractor_ which points into here.
DwarfExpr expr_;
// Determines if a string describing the expression is being generated instead of evaluating
// the expression. See is_string_output() and AppendString().
StringOutput string_output_mode_ = StringOutput::kNone;
std::string string_output_; // Result when string_output_mode_ != kNone;
CompletionCallback completion_callback_; // Null in string printing mode (it's synchronous).
bool in_completion_callback_ = false; // To check for lifetime errors.
DataExtractor data_extractor_;
// The result type. Normally expressions compute pointers unless explicitly tagged as a value.
// This tracks the current "simple" expression result type. For "composite" operations that
// use one or more DW_OP_[bit_]piece there will be nonempty result_data_ rather than writing
// "kData" here.
//
// This needs to be separate because there can be multiple simple expressions independent of the
// result_data_ in the composite case. So this value will never be "kData".
ResultType result_type_ = ResultType::kPointer;
// Indicates that execution is complete. When this is true, the callback will have been issued. A
// complete expression could have stopped on error or success (see is_success_).
bool is_complete_ = false;
// Indicates that the expression is complete and that there is a result value.
bool is_success_ = false;
std::vector<DwarfStackEntry> stack_;
// Tracks the result when generating composite descriptions via DW_OP_[bit_]piece. A nonempty
// contents indicates that the final result is of type "kData" (see result_type_ for more).
//
// TODO(bug 39630) we will need to track source information (memory address or register ID) for
// each subrange in this block to support writing to the generated object.
TaggedDataBuilder result_data_;
// Set when a register value is pushed on the stack and cleared when anything else happens. This
// allows the user of the expression to determine if the result of the expression is directly from
// a register (say, to support writing to that value in the future).
debug::RegisterID current_register_id_ = debug::RegisterID::kUnknown;
// Tracks whether the current expression uses only constant data. Any operations that read memory
// or registers should clear this.
bool result_is_constant_ = true;
// The nested evaluator for executing DW_OP_entry_value expressions.
std::unique_ptr<DwarfExprEval> nested_eval_;
fxl::WeakPtrFactory<DwarfExprEval> weak_factory_;
FXL_DISALLOW_COPY_AND_ASSIGN(DwarfExprEval);
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_DWARF_EXPR_EVAL_H_