blob: e3764b5ed02dd657a33ee3d483340c191dc6baaa [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.
#include "src/developer/debug/zxdb/symbols/dwarf_expr_eval.h"
#include <inttypes.h>
#include <lib/syslog/cpp/macros.h>
#include <stdlib.h>
#include <limits>
#include <utility>
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/Support/DataExtractor.h"
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/symbols/arch.h"
#include "src/developer/debug/zxdb/symbols/symbol_data_provider.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace zxdb {
namespace {
// For debug print StackEntry values.
std::string ToString128(uint128_t v) {
if (v > 1024)
return to_hex_string(v); // Use hex for very large values (probably addresses).
return std::to_string(static_cast<uint64_t>(v)); // Use decimal for small values.
}
std::string ToString128(int128_t v) {
if (v < 0)
return "-" + ToString128(static_cast<uint128_t>(-v));
return std::to_string(static_cast<int64_t>(v));
}
// Makes a string expressing adding or subtracting the given constant value.
std::string MakeAddString(DwarfExprEval::SignedStackEntry val) {
if (val < 0)
return " - " + ToString128(-val);
return " + " + ToString128(val);
}
} // namespace
DwarfExprEval::DwarfExprEval()
: symbol_context_(SymbolContext::ForRelativeAddresses()), weak_factory_(this) {}
DwarfExprEval::~DwarfExprEval() {
// This assertion verifies that this class was not accidentally deleted from
// within the completion callback. This class is not set up to handle this
// case.
FX_CHECK(!in_completion_callback_);
}
void DwarfExprEval::Push(StackEntry value) { stack_.push_back(value); }
DwarfExprEval::ResultType DwarfExprEval::GetResultType() const {
FX_DCHECK(is_complete_);
FX_DCHECK(is_success_);
if (!result_data_.empty())
return ResultType::kData;
return result_type_;
}
DwarfExprEval::StackEntry DwarfExprEval::GetResult() const {
FX_DCHECK(is_complete_);
FX_DCHECK(is_success_);
return stack_.back();
}
DwarfExprEval::Completion DwarfExprEval::Eval(fxl::RefPtr<SymbolDataProvider> data_provider,
const SymbolContext& symbol_context, Expression expr,
CompletionCallback cb) {
SetUp(std::move(data_provider), symbol_context, expr, std::move(cb));
// Note: ContinueEval() may call callback, which may delete |this|
return ContinueEval() ? Completion::kSync : Completion::kAsync;
}
std::string DwarfExprEval::ToString(fxl::RefPtr<SymbolDataProvider> data_provider,
const SymbolContext& symbol_context, Expression expr,
bool pretty) {
SetUp(std::move(data_provider), symbol_context, expr, nullptr);
string_output_mode_ = pretty ? StringOutput::kPretty : StringOutput::kLiteral;
string_output_.clear();
bool is_complete = ContinueEval();
FX_DCHECK(is_complete); // Always expect string printing mode to complete.
std::string result = std::move(string_output_);
string_output_mode_ = StringOutput::kNone;
string_output_.clear();
return result;
}
void DwarfExprEval::SetUp(fxl::RefPtr<SymbolDataProvider> data_provider,
const SymbolContext& symbol_context, Expression expr,
CompletionCallback cb) {
is_complete_ = false;
data_provider_ = std::move(data_provider);
symbol_context_ = symbol_context;
expr_ = std::move(expr);
expr_index_ = 0;
completion_callback_ = std::move(cb);
if (!expr_.empty()) {
// Assume little-endian.
data_extractor_ = std::make_unique<llvm::DataExtractor>(
llvm::StringRef(reinterpret_cast<const char*>(&expr_[0]), expr_.size()), true,
kTargetPointerSize);
}
}
bool DwarfExprEval::ContinueEval() {
// To allow interruption, only a certain number of instructions will be
// executed in sequence without posting back to the message loop. This
// gives calling code the chance to cancel long or hung executions. Since
// most programs are 1-4 instructions, the threshold can be low.
constexpr int kMaxInstructionsAtOnce = 32;
int instruction_count = 0;
do {
// Check for successfully reaching the end of the stream.
if (!is_complete_ && expr_index_ == expr_.size()) {
if (is_string_output())
return true; // Only expecting to produce a string.
data_provider_.reset();
is_complete_ = true;
Err err;
if (stack_.empty() && result_data_.empty()) {
// Failure to compute any values.
err = Err("DWARF expression produced no results.");
is_success_ = false;
} else {
is_success_ = true;
}
in_completion_callback_ = true;
completion_callback_(this, err);
completion_callback_ = {};
in_completion_callback_ = false;
return is_complete_;
}
if (instruction_count == kMaxInstructionsAtOnce) {
// Enough instructions have run at once. Schedule a callback to continue
// execution in the message loop.
debug_ipc::MessageLoop::Current()->PostTask(FROM_HERE,
[weak_eval = weak_factory_.GetWeakPtr()]() {
if (weak_eval)
weak_eval->ContinueEval();
});
return is_complete_;
}
instruction_count++;
} while (!is_complete_ && EvalOneOp() == Completion::kSync);
return is_complete_;
}
DwarfExprEval::Completion DwarfExprEval::EvalOneOp() {
FX_DCHECK(!is_complete_);
FX_DCHECK(expr_index_ < expr_.size());
// Clear any current register information. See current_register_id_ declaration for more.
current_register_id_ = debug_ipc::RegisterID::kUnknown;
// Opcode is next byte in the data buffer. Consume it.
uint8_t op = expr_[expr_index_];
expr_index_++;
// Literals 0-31.
if (op >= llvm::dwarf::DW_OP_lit0 && op <= llvm::dwarf::DW_OP_lit31) {
int literal_value = op - llvm::dwarf::DW_OP_lit0;
if (is_string_output()) {
return AppendString("DW_OP_lit" + std::to_string(literal_value),
"push(" + std::to_string(literal_value) + ")");
} else {
Push(literal_value);
}
return Completion::kSync;
}
// Registers 0-31.
if (op >= llvm::dwarf::DW_OP_reg0 && op <= llvm::dwarf::DW_OP_reg31) {
int reg_index = op - llvm::dwarf::DW_OP_reg0;
if (is_string_output())
return AppendString("DW_OP_reg" + std::to_string(reg_index), GetRegisterName(reg_index));
result_type_ = ResultType::kValue;
return PushRegisterWithOffset(reg_index, 0);
}
// Base register with SLEB128 offset.
if (op >= llvm::dwarf::DW_OP_breg0 && op <= llvm::dwarf::DW_OP_breg31)
return OpBreg(op);
switch (op) {
case llvm::dwarf::DW_OP_addr:
return OpAddr();
case llvm::dwarf::DW_OP_const1u:
return OpPushUnsigned(1, "DW_OP_const1u");
case llvm::dwarf::DW_OP_const1s:
return OpPushSigned(1, "DW_OP_const1s");
case llvm::dwarf::DW_OP_const2u:
return OpPushUnsigned(2, "DW_OP_const2u");
case llvm::dwarf::DW_OP_const2s:
return OpPushSigned(2, "DW_OP_const2s");
case llvm::dwarf::DW_OP_const4u:
return OpPushUnsigned(4, "DW_OP_const4u");
case llvm::dwarf::DW_OP_const4s:
return OpPushSigned(4, "DW_OP_const4s");
case llvm::dwarf::DW_OP_const8u:
return OpPushUnsigned(8, "DW_OP_const8u");
case llvm::dwarf::DW_OP_const8s:
return OpPushSigned(8, "DW_OP_const8s");
case llvm::dwarf::DW_OP_constu:
return OpPushLEBUnsigned();
case llvm::dwarf::DW_OP_consts:
return OpPushLEBSigned();
case llvm::dwarf::DW_OP_dup:
return OpDup();
case llvm::dwarf::DW_OP_drop:
return OpDrop();
case llvm::dwarf::DW_OP_over:
return OpOver();
case llvm::dwarf::DW_OP_pick:
return OpPick();
case llvm::dwarf::DW_OP_swap:
return OpSwap();
case llvm::dwarf::DW_OP_rot:
return OpRot();
case llvm::dwarf::DW_OP_xderef:
// We don't have multiple address spaces.
ReportUnimplementedOpcode(op);
return Completion::kSync;
case llvm::dwarf::DW_OP_abs:
return OpUnary(
[](StackEntry a) { return static_cast<StackEntry>(llabs(static_cast<long long>(a))); },
"DW_OP_abs");
case llvm::dwarf::DW_OP_and:
return OpBinary([](StackEntry a, StackEntry b) { return a & b; }, "DW_OP_and");
case llvm::dwarf::DW_OP_div:
return OpDiv();
case llvm::dwarf::DW_OP_minus:
return OpBinary([](StackEntry a, StackEntry b) { return a - b; }, "DW_OP_minus");
case llvm::dwarf::DW_OP_mod:
return OpMod();
case llvm::dwarf::DW_OP_mul:
return OpBinary([](StackEntry a, StackEntry b) { return a * b; }, "DW_OP_mul");
case llvm::dwarf::DW_OP_neg:
return OpUnary(
[](StackEntry a) { return static_cast<StackEntry>(-static_cast<SignedStackEntry>(a)); },
"DW_OP_neg");
case llvm::dwarf::DW_OP_not:
return OpUnary([](StackEntry a) { return ~a; }, "DW_OP_not");
case llvm::dwarf::DW_OP_or:
return OpBinary([](StackEntry a, StackEntry b) { return a | b; }, "DW_OP_or");
case llvm::dwarf::DW_OP_plus:
return OpBinary([](StackEntry a, StackEntry b) { return a + b; }, "DW_OP_plus");
case llvm::dwarf::DW_OP_plus_uconst:
return OpPlusUconst();
case llvm::dwarf::DW_OP_shl:
return OpBinary([](StackEntry a, StackEntry b) { return a << b; }, "DW_OP_shl");
case llvm::dwarf::DW_OP_shr:
return OpBinary([](StackEntry a, StackEntry b) { return a >> b; }, "DW_OP_shr");
case llvm::dwarf::DW_OP_shra:
return OpBinary(
[](StackEntry a, StackEntry b) {
return static_cast<StackEntry>(static_cast<SignedStackEntry>(a) >>
static_cast<SignedStackEntry>(b));
},
"DW_OP_shra");
case llvm::dwarf::DW_OP_xor:
return OpBinary([](StackEntry a, StackEntry b) { return a ^ b; }, "DW_OP_xor");
case llvm::dwarf::DW_OP_skip:
return OpSkip();
case llvm::dwarf::DW_OP_bra:
return OpBra();
case llvm::dwarf::DW_OP_eq:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a == b); },
"DW_OP_eq");
case llvm::dwarf::DW_OP_ge:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a >= b); },
"DW_OP_ge");
case llvm::dwarf::DW_OP_gt:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a > b); },
"DW_OP_gt");
case llvm::dwarf::DW_OP_le:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a <= b); },
"DW_OP_le");
case llvm::dwarf::DW_OP_lt:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a < b); },
"DW_OP_lt");
case llvm::dwarf::DW_OP_ne:
return OpBinary([](StackEntry a, StackEntry b) { return static_cast<StackEntry>(a != b); },
"DW_OP_ne");
case llvm::dwarf::DW_OP_regx:
return OpRegx();
case llvm::dwarf::DW_OP_fbreg:
return OpFbreg();
case llvm::dwarf::DW_OP_bregx:
return OpBregx();
case llvm::dwarf::DW_OP_piece:
return OpPiece();
case llvm::dwarf::DW_OP_deref:
return OpDeref(sizeof(TargetPointer), "DW_OP_deref", false);
case llvm::dwarf::DW_OP_deref_size:
return OpDerefSize();
case llvm::dwarf::DW_OP_xderef_size:
// We don't have multiple address spaces.
if (is_string_output()) {
AppendString("DW_OP_xderef_size");
} else {
ReportUnimplementedOpcode(op);
}
return Completion::kSync;
case llvm::dwarf::DW_OP_nop:
if (is_string_output())
AppendString("DW_OP_nop");
return Completion::kSync;
case llvm::dwarf::DW_OP_push_object_address:
case llvm::dwarf::DW_OP_call2: // 2-byte offset of DIE.
case llvm::dwarf::DW_OP_call4: // 4-byte offset of DIE.
case llvm::dwarf::DW_OP_call_ref: // 4- or 8-byte offset of DIE.
// TODO(brettw) implement these.
ReportUnimplementedOpcode(op);
return Completion::kSync;
case llvm::dwarf::DW_OP_call_frame_cfa:
return OpCFA();
case llvm::dwarf::DW_OP_bit_piece:
return OpBitPiece();
case llvm::dwarf::DW_OP_implicit_value:
return OpImplicitValue();
case llvm::dwarf::DW_OP_stack_value:
return OpStackValue();
case llvm::dwarf::DW_OP_GNU_push_tls_address:
return OpTlsAddr("DW_OP_GNU_push_tls_address");
case llvm::dwarf::DW_OP_form_tls_address:
return OpTlsAddr("DW_OP_form_tls_address");
case llvm::dwarf::DW_OP_implicit_pointer:
return OpImplicitPointer("DW_OP_implicit_pointer");
case 0xf2: // DW_OP_GNU_implicit_pointer (pre-DWARF5 GNU extension for the non-GNU one).
return OpImplicitPointer("DW_OP_GNU_implicit_pointer");
case 0xf3: // DW_OP_GNU_entry_value
return OpEntryValue();
default:
// Invalid or unknown opcode.
if (is_string_output()) {
AppendString("INVALID_OPCODE(" + to_hex_string(op) + ")");
} else {
ReportError(fxl::StringPrintf("Invalid opcode 0x%x in DWARF expression.", op));
}
return Completion::kSync;
}
}
DwarfExprEval::Completion DwarfExprEval::PushRegisterWithOffset(int dwarf_register_number,
SignedStackEntry offset) {
// Reading register data means the result is not constant.
result_is_constant_ = false;
const debug_ipc::RegisterInfo* reg_info =
debug_ipc::DWARFToRegisterInfo(data_provider_->GetArch(), dwarf_register_number);
if (!reg_info) {
ReportError(fxl::StringPrintf("Register %d not known.", dwarf_register_number));
return Completion::kSync;
}
// This function doesn't set the result_type_ because it is called from different contexts. The
// callers should set the result_type_ as appropriate for their operation.
if (auto reg_data = data_provider_->GetRegister(reg_info->id)) {
// State known synchronously (could be available or known unavailable).
if (reg_data->empty()) {
ReportError(fxl::StringPrintf("Register %d not available.", dwarf_register_number));
} else {
// This truncates to 128 bits and converts from little-endian. DWARF doesn't seem to use the
// stack machine for vector computations (it's not specified that the stack items are large
// enough). When it uses a stack register for a floating-point scalar computation, it just
// uses the low bits.
StackEntry reg_value = 0;
memcpy(&reg_value, reg_data->data(), std::min(sizeof(StackEntry), reg_data->size()));
Push(reg_value + offset);
// When the current value represents a register, save that fact.
if (offset == 0)
current_register_id_ = reg_info->id;
}
return Completion::kSync;
}
// Must request async.
data_provider_->GetRegisterAsync(
reg_info->id, [reg_id = reg_info->id, weak_eval = weak_factory_.GetWeakPtr(), offset](
const Err& err, std::vector<uint8_t> reg_data) {
if (!weak_eval)
return;
if (err.has_error()) {
weak_eval->ReportError(err);
return;
}
// Truncate/convert from little-endian as above.
StackEntry reg_value = 0;
memcpy(&reg_value, reg_data.data(), std::min(sizeof(StackEntry), reg_data.size()));
weak_eval->Push(static_cast<StackEntry>(reg_value + offset));
// When the current value represents a register, save that fact.
if (offset == 0)
weak_eval->current_register_id_ = reg_id;
// Picks up processing at the next instruction.
weak_eval->ContinueEval();
});
return Completion::kAsync;
}
bool DwarfExprEval::ReadSigned(int byte_size, SignedStackEntry* output) {
uint64_t old_expr_index = expr_index_;
*output = data_extractor_->getSigned(&expr_index_, byte_size);
if (old_expr_index == expr_index_) {
ReportError("Bad number format in DWARF expression.");
return false;
}
return true;
}
bool DwarfExprEval::ReadUnsigned(int byte_size, StackEntry* output) {
uint64_t old_expr_index = expr_index_;
*output = data_extractor_->getUnsigned(&expr_index_, byte_size);
if (old_expr_index == expr_index_) {
ReportError("Bad number format in DWARF expression.");
return false;
}
return true;
}
bool DwarfExprEval::ReadLEBSigned(SignedStackEntry* output) {
uint64_t old_expr_index = expr_index_;
*output = data_extractor_->getSLEB128(&expr_index_);
if (old_expr_index == expr_index_) {
ReportError("Bad number format in DWARF expression.");
return false;
}
return true;
}
bool DwarfExprEval::ReadLEBUnsigned(StackEntry* output) {
uint64_t old_expr_index = expr_index_;
*output = data_extractor_->getULEB128(&expr_index_);
if (old_expr_index == expr_index_) {
ReportError("Bad number format in DWARF expression.");
return false;
}
return true;
}
void DwarfExprEval::ReadMemory(
TargetPointer address, uint32_t byte_size,
fit::callback<void(DwarfExprEval* eval, std::vector<uint8_t> value)> on_success) {
// Reading memory means the result is not constant.
result_is_constant_ = false;
data_provider_->GetMemoryAsync(
address, byte_size,
[address, byte_size, weak_eval = weak_factory_.GetWeakPtr(),
on_success = std::move(on_success)](const Err& err, std::vector<uint8_t> value) mutable {
if (!weak_eval) {
return;
} else if (err.has_error()) {
weak_eval->ReportError(err);
} else if (value.size() != byte_size) {
weak_eval->ReportError(
fxl::StringPrintf("Invalid pointer 0x%" PRIx64 ".", static_cast<uint64_t>(address)));
} else {
on_success(weak_eval.get(), std::move(value));
// Picks up processing at the next instruction.
weak_eval->ContinueEval();
}
});
}
void DwarfExprEval::ReportError(const std::string& msg) { ReportError(Err(msg)); }
void DwarfExprEval::ReportError(const Err& err) {
if (is_string_output())
AppendString("ERROR: \"" + err.msg() + "\"");
data_provider_.reset();
is_complete_ = true;
// Wrap completion callback with the flag to catch deletions from within the callback.
in_completion_callback_ = true;
if (completion_callback_)
completion_callback_(this, err);
completion_callback_ = {};
in_completion_callback_ = false;
}
void DwarfExprEval::ReportStackUnderflow() { ReportError("Stack underflow for DWARF expression."); }
void DwarfExprEval::ReportUnimplementedOpcode(uint8_t op) {
ReportError(fxl::StringPrintf("Unimplemented opcode 0x%x in DWARF expression.", op));
}
DwarfExprEval::Completion DwarfExprEval::OpUnary(StackEntry (*op)(StackEntry),
const char* op_name) {
if (is_string_output())
return AppendString(op_name);
if (stack_.empty())
ReportStackUnderflow();
else
stack_.back() = op(stack_.back());
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpBinary(StackEntry (*op)(StackEntry, StackEntry),
const char* op_name) {
if (is_string_output())
return AppendString(op_name);
if (stack_.size() < 2) {
ReportStackUnderflow();
} else {
StackEntry b = stack_.back();
stack_.pop_back();
StackEntry a = stack_.back();
stack_.back() = op(a, b);
}
return Completion::kSync;
}
// 1 parameter: unsigned the size of a pointer. This is relative to the load address of the current
// module. It is used to for globals and statics.
DwarfExprEval::Completion DwarfExprEval::OpAddr() {
StackEntry offset;
if (!ReadUnsigned(kTargetPointerSize, &offset))
return Completion::kSync;
TargetPointer address = symbol_context_.RelativeToAbsolute(static_cast<TargetPointer>(offset));
if (is_string_output()) {
if (symbol_context_.is_relative() || string_output_mode_ == StringOutput::kLiteral)
return AppendString("DW_OP_addr(" + to_hex_string(offset) + ")");
// Show final address since we know it.
return AppendString("push(" + to_hex_string(address) + ")");
}
Push(address);
return Completion::kSync;
}
// ULEB128 size + ULEB128 offset.
DwarfExprEval::Completion DwarfExprEval::OpBitPiece() {
StackEntry size;
if (!ReadLEBUnsigned(&size))
return Completion::kSync;
StackEntry offset;
if (!ReadLEBUnsigned(&offset))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_bit_piece(" + ToString128(size) + ", " + ToString128(offset) + ")");
// Clang will generate bit_piece operations to make 80-bit long double constants, but the
// expressions are invalid: https://bugs.llvm.org/show_bug.cgi?id=43682
// We were able to get GCC to generate a piece operation for:
// void foo(int x, int y) {
// struct { int x:3, :3, y:3; } s = {x, y};
// }
// That also seems invalid. So we're waiting for a clearly valid example in the wild before
// spending time trying to implement this.
ReportError(
"The DWARF encoding for this symbol uses DW_OP_bit_piece which is unimplemented.\n"
"Please file a bit with a repro case so we can implement it properly.");
return Completion::kSync;
}
// 1 parameter: 2 byte signed integer constant.
DwarfExprEval::Completion DwarfExprEval::OpBra() {
// "The 2-byte constant is the number of bytes of the DWARF expression to skip forward or backward
// from the current operation, beginning after the 2-byte constant."
SignedStackEntry skip_amount = 0;
if (!ReadSigned(2, &skip_amount))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_bra(" + ToString128(skip_amount) + ")");
if (stack_.empty()) {
ReportStackUnderflow();
return Completion::kSync;
}
// 0 @ top of stack means don't take the branch.
StackEntry condition = stack_.back();
stack_.pop_back();
if (condition == 0)
return Completion::kSync;
// Otherwise take the branch.
Skip(skip_amount);
return Completion::kSync;
}
// 1 parameter: SLEB128 offset added to base register.
DwarfExprEval::Completion DwarfExprEval::OpBreg(uint8_t op) {
int reg_index = op - llvm::dwarf::DW_OP_breg0;
SignedStackEntry offset = 0;
if (!ReadLEBSigned(&offset))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_breg" + std::to_string(reg_index) + "(" + ToString128(offset) + ")",
GetRegisterName(reg_index) + MakeAddString(offset));
}
result_type_ = ResultType::kPointer;
return PushRegisterWithOffset(reg_index, offset);
}
DwarfExprEval::Completion DwarfExprEval::OpCFA() {
if (is_string_output())
return AppendString("DW_OP_call_frame_cfa");
// Reading the CFA means the result is not constant.
result_is_constant_ = false;
if (StackEntry cfa = data_provider_->GetCanonicalFrameAddress())
Push(cfa);
else
ReportError("Frame address is 0.");
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpDiv() {
if (is_string_output())
return AppendString("DW_OP_div");
if (stack_.size() < 2) {
ReportStackUnderflow();
} else {
StackEntry b = stack_.back();
stack_.pop_back();
StackEntry a = stack_.back();
if (b == 0) {
ReportError("DWARF expression divided by zero.");
} else {
stack_.back() = static_cast<StackEntry>(static_cast<SignedStackEntry>(a) /
static_cast<SignedStackEntry>(b));
}
}
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpDrop() {
if (is_string_output())
return AppendString("DW_OP_drop");
if (stack_.empty())
ReportStackUnderflow();
else
stack_.pop_back();
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpDup() {
if (is_string_output())
return AppendString("DW_OP_dup");
if (stack_.empty())
ReportStackUnderflow();
else
stack_.push_back(stack_.back());
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpEntryValue() {
// This GNU extension is a ULEB128 length followed by a sub-expression of that length. This
// sub-expression is supposed to be evaluated in a separate stack using the register values that
// were present at the beginning of the function:
// https://gcc.gnu.org/ml/gcc-patches/2010-08/txt00152.txt
//
// Generally if the registers were saved registers it would just encode those locations. This is
// really used for non-saved registers and requires that the debugger have previously saved those
// registers separately. This isn't something that we currently do, and can't be done in general
// (it could be implemented if you previously single- stepped into that function though).
StackEntry length;
if (!ReadLEBUnsigned(&length))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_GNU_entry_value(" + ToString128(length) + ")");
ReportError("Optimized out (DW_OP_GNU_entry_value)");
return Completion::kSync;
}
// 1 parameter: Signed LEB128 offset from frame base pointer.
DwarfExprEval::Completion DwarfExprEval::OpFbreg() {
// Reading the frame base means the result is not constant.
result_is_constant_ = false;
SignedStackEntry offset = 0;
if (!ReadLEBSigned(&offset))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_fbreg(" + ToString128(offset) + ")",
"frame_base" + MakeAddString(offset));
}
if (auto bp = data_provider_->GetFrameBase()) {
// Available synchronously.
// Certain problems can cause the BP to be set to 0 which is obviously
// invalid, report that error specifically.
if (*bp == 0)
ReportError("Base Pointer is 0, can't evaluate.");
result_type_ = ResultType::kPointer;
Push(*bp + offset);
return Completion::kSync;
}
// Must request async.
data_provider_->GetFrameBaseAsync(
[weak_eval = weak_factory_.GetWeakPtr(), offset](const Err& err, StackEntry value) {
if (!weak_eval)
return;
if (err.has_error()) {
weak_eval->ReportError(err);
return;
}
if (value == 0) {
weak_eval->ReportError("Base Pointer is 0, can't evaluate.");
return;
}
weak_eval->result_type_ = ResultType::kPointer;
weak_eval->Push(static_cast<StackEntry>(value + offset));
// Picks up processing at the next instruction.
weak_eval->ContinueEval();
});
return Completion::kAsync;
}
// 2 parameters: 8-byte unsigned DIE offset containing the value, SLEB128 offset from that value.
DwarfExprEval::Completion DwarfExprEval::OpImplicitPointer(const char* op_name) {
// GCC generates this when a pointer has been optimized out, but it still can provide the value of
// the thing that it pointed to. We don't implement this.
StackEntry die_offset;
if (!ReadUnsigned(8, &die_offset))
return Completion::kSync;
SignedStackEntry value_offset;
if (!ReadLEBSigned(&value_offset))
return Completion::kSync;
if (is_string_output()) {
return AppendString(std::string(op_name) + "(" + to_hex_string(die_offset) + ", " +
ToString128(value_offset) + ")");
}
ReportError("Optimized out (DW_OP_implicit_pointer)");
return Completion::kSync;
}
// 2 parameters: ULEB128 length, followed by that much data (in machine-endianness).
DwarfExprEval::Completion DwarfExprEval::OpImplicitValue() {
StackEntry len = 0;
if (!ReadLEBUnsigned(&len))
return Completion::kSync;
if (len > sizeof(StackEntry) || expr_index_ + static_cast<size_t>(len) > expr_.size()) {
ReportError(fxl::StringPrintf("DWARF implicit value length too long: 0x%x.",
static_cast<unsigned>(len)));
return Completion::kSync;
}
StackEntry value = 0;
if (len > 0) {
memcpy(&value, &expr_[expr_index_], static_cast<size_t>(len));
Skip(len);
}
if (is_string_output()) {
return AppendString(
"DW_OP_implicit_value(" + ToString128(len) + ", " + to_hex_string(value) + ")",
"push(" + to_hex_string(value) + ")");
}
Push(value);
result_type_ = ResultType::kValue;
return Completion::kSync;
}
// 1 parameter: ULEB128 constant indexing the register.
DwarfExprEval::Completion DwarfExprEval::OpRegx() {
StackEntry reg = 0;
if (!ReadLEBUnsigned(&reg))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_regx(" + ToString128(reg) + ")",
GetRegisterName(static_cast<int>(reg)));
}
result_type_ = ResultType::kValue;
return PushRegisterWithOffset(static_cast<int>(reg), 0);
}
// 2 parameters: ULEB128 register number + SLEB128 offset.
DwarfExprEval::Completion DwarfExprEval::OpBregx() {
StackEntry reg_val = 0;
if (!ReadLEBUnsigned(&reg_val))
return Completion::kSync;
int reg = static_cast<int>(reg_val);
SignedStackEntry offset = 0;
if (!ReadLEBSigned(&offset))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_bregx(" + std::to_string(reg) + ", " + ToString128(offset) + ")",
GetRegisterName(reg) + MakeAddString(offset));
}
result_type_ = ResultType::kPointer;
return PushRegisterWithOffset(reg, offset);
}
// Pops the stack and pushes an given-sized value from memory at that location.
DwarfExprEval::Completion DwarfExprEval::OpDeref(uint32_t byte_size, const char* op_name,
bool string_include_size) {
if (is_string_output()) {
if (string_include_size)
return AppendString(std::string(op_name) + "(" + std::to_string(byte_size) + ")");
return AppendString(op_name);
}
if (stack_.empty()) {
ReportStackUnderflow();
return Completion::kSync;
}
if (byte_size == 0 || byte_size > sizeof(StackEntry)) {
ReportError(fxl::StringPrintf("Invalid DWARF expression read size: %u", byte_size));
return Completion::kSync;
}
StackEntry addr = stack_.back();
stack_.pop_back();
ReadMemory(addr, byte_size, [](DwarfExprEval* eval, std::vector<uint8_t> data) {
// Success. This assumes little-endian and copies starting from the low bytes. The data will
// have already been validated to be the correct size so we know it will fit in a StackEntry.
FX_DCHECK(data.size() <= sizeof(StackEntry));
StackEntry to_push = 0;
memcpy(&to_push, &data[0], data.size());
eval->Push(to_push);
});
return Completion::kAsync;
}
DwarfExprEval::Completion DwarfExprEval::OpDerefSize() {
// The operand is a 1-byte unsigned constant following the opcode.
StackEntry byte_size = 0;
if (!ReadUnsigned(1, &byte_size))
return Completion::kSync;
// The generic deref path can handle the rest.
return OpDeref(static_cast<uint32_t>(byte_size), "DW_OP_deref_size", true);
}
DwarfExprEval::Completion DwarfExprEval::OpMod() {
if (is_string_output())
return AppendString("DW_OP_mod");
if (stack_.size() < 2) {
ReportStackUnderflow();
} else {
StackEntry b = stack_.back();
stack_.pop_back();
StackEntry a = stack_.back();
if (b == 0) {
ReportError("DWARF expression divided by zero.");
} else {
stack_.back() = static_cast<StackEntry>(static_cast<SignedStackEntry>(a) %
static_cast<SignedStackEntry>(b));
}
}
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpOver() {
if (is_string_output())
return AppendString("DW_OP_over");
// Duplicates the next-to-top over the top item.
if (stack_.size() < 2)
ReportStackUnderflow();
else
Push(stack_[stack_.size() - 2]);
return Completion::kSync;
}
// 1 parameter: 1-byte stack index from the top to push.
DwarfExprEval::Completion DwarfExprEval::OpPick() {
StackEntry index = 0;
if (!ReadUnsigned(1, &index))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_pick(" + ToString128(index) + ")");
if (stack_.size() <= index) {
ReportStackUnderflow();
return Completion::kSync;
}
// Index is from end (0 = last item).
Push(stack_[stack_.size() - 1 - index]);
return Completion::kSync;
}
// 1 paramter: ULEB size of item in bytes.
DwarfExprEval::Completion DwarfExprEval::OpPiece() {
StackEntry byte_size = 0;
if (!ReadLEBUnsigned(&byte_size))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_piece(" + ToString128(byte_size) + ")");
if (stack_.empty()) {
ReportStackUnderflow();
return Completion::kSync;
}
StackEntry source = stack_.back();
stack_.pop_back();
if (result_type_ == ResultType::kValue) {
// Simple case where the source of the "piece" is the value at the top of the stack.
if (byte_size > sizeof(StackEntry)) {
ReportError(fxl::StringPrintf("DWARF expression listed a data size of %d which is too large.",
static_cast<int>(byte_size)));
return Completion::kSync;
}
// We want the low bytes, this assumes little-endian.
uint8_t source_as_bytes[sizeof(StackEntry)];
memcpy(&source_as_bytes, &source, sizeof(StackEntry));
result_data_.insert(result_data_.end(), std::begin(source_as_bytes),
&source_as_bytes[byte_size]);
// Reset the expression state to start a new one.
result_type_ = ResultType::kPointer;
return Completion::kSync;
}
// This is the more complex case where the top of the stack is a pointer to the value in memory.
// We read that many bytes from memory and add it to the result data.
ReadMemory(source, byte_size, [](DwarfExprEval* eval, std::vector<uint8_t> data) {
// Success. Copy to the result.
eval->result_data_.insert(eval->result_data_.end(), data.begin(), data.end());
// Reset the expression state to start a new one.
eval->result_type_ = ResultType::kPointer;
});
// The ReadMemory call will complete asynchronously.
return Completion::kAsync;
}
DwarfExprEval::Completion DwarfExprEval::OpPlusUconst() {
// "Pops the top stack entry, adds it to the unsigned LEB128 constant operand and pushes the
// result."
StackEntry param = 0;
if (!ReadLEBUnsigned(&param))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_plus_uconst(" + ToString128(param) + ")", "+ " + ToString128(param));
}
if (stack_.empty()) {
ReportStackUnderflow();
} else {
StackEntry top = stack_.back();
stack_.pop_back();
Push(top + param);
}
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpPushSigned(int byte_count, const char* op_name) {
SignedStackEntry value = 0;
if (!ReadSigned(byte_count, &value))
return Completion::kSync;
if (is_string_output()) {
return AppendString(std::string(op_name) + "(" + ToString128(value) + ")",
"push(" + ToString128(value) + ")");
}
Push(static_cast<StackEntry>(value));
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpPushUnsigned(int byte_count, const char* op_name) {
StackEntry value = 0;
if (!ReadUnsigned(byte_count, &value))
return Completion::kSync;
if (is_string_output()) {
return AppendString(std::string(op_name) + "(" + ToString128(value) + ")",
"push(" + ToString128(value) + ")");
}
Push(value);
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpPushLEBSigned() {
SignedStackEntry value = 0;
if (!ReadLEBSigned(&value))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_consts(" + ToString128(value) + ")",
"push(" + ToString128(value) + ")");
Push(static_cast<StackEntry>(value));
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpPushLEBUnsigned() {
StackEntry value = 0;
if (!ReadLEBUnsigned(&value))
return Completion::kSync;
if (is_string_output())
return AppendString("DW_OP_constu(" + ToString128(value) + ")",
"push(" + ToString128(value) + ")");
Push(value);
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpRot() {
if (is_string_output())
return AppendString("DW_OP_rot");
// Rotates the top 3 entries "down" with wraparound. "The entry at the top of the stack becomes
// the third stack entry, the second entry becomes the top of the stack, and the third entry
// becomes the second entry."
if (stack_.size() < 3) {
ReportStackUnderflow();
} else {
StackEntry top = stack_[stack_.size() - 1];
StackEntry one_back = stack_[stack_.size() - 2];
StackEntry two_back = stack_[stack_.size() - 3];
stack_[stack_.size() - 1] = one_back;
stack_[stack_.size() - 2] = two_back;
stack_[stack_.size() - 3] = top;
}
return Completion::kSync;
}
// 1 parameter: 2-byte signed constant.
DwarfExprEval::Completion DwarfExprEval::OpSkip() {
SignedStackEntry skip_amount = 0;
if (!ReadSigned(2, &skip_amount))
return Completion::kSync;
if (is_string_output()) {
return AppendString("DW_OP_skip(" + ToString128(skip_amount) + ")");
// Don't actually execute the skip in printing mode, because it could skip backwards to do a
// loop and we would keep printing from there.
}
Skip(skip_amount);
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpStackValue() {
if (is_string_output())
return AppendString("DW_OP_stack_value");
// "Specifies that the object does not exist in memory but rather is a constant value. The value
// from the top of the stack is the value to be used. This is the actual object value and not the
// location."
result_type_ = ResultType::kValue;
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpSwap() {
if (is_string_output())
return AppendString("DW_OP_swap");
if (stack_.size() < 2)
ReportStackUnderflow();
else
std::swap(stack_[stack_.size() - 1], stack_[stack_.size() - 2]);
return Completion::kSync;
}
DwarfExprEval::Completion DwarfExprEval::OpTlsAddr(const char* op_name) {
if (is_string_output())
return AppendString(op_name);
if (stack_.size() < 1) {
ReportStackUnderflow();
return Completion::kSync;
}
auto debug_address = data_provider_->GetDebugAddressForContext(symbol_context_);
if (!debug_address) {
ReportError("Debug address unavailable.");
return Completion::kSync;
}
data_provider_->GetTLSSegment(
symbol_context_, [weak_eval = weak_factory_.GetWeakPtr()](ErrOr<uint64_t> value) {
if (!weak_eval) {
return;
}
if (value.has_error()) {
weak_eval->ReportError(value.err());
return;
}
weak_eval->stack_.back() += static_cast<StackEntry>(value.value());
weak_eval->ContinueEval();
});
return Completion::kAsync;
}
void DwarfExprEval::Skip(SignedStackEntry amount) {
SignedStackEntry new_index = static_cast<SignedStackEntry>(expr_index_) + amount;
if (new_index >= static_cast<SignedStackEntry>(expr_.size())) {
// Skip to or past the end just terminates the program.
expr_index_ = expr_.size();
} else if (new_index < 0) {
// Skip before beginning is an error.
ReportError("DWARF expression skips out-of-bounds.");
} else {
expr_index_ = static_cast<uint32_t>(new_index);
}
}
std::string DwarfExprEval::GetRegisterName(int reg_number) const {
const debug_ipc::RegisterInfo* reg_info =
data_provider_ ? debug_ipc::DWARFToRegisterInfo(data_provider_->GetArch(), reg_number)
: nullptr;
if (!reg_info) // Fall back on reporting the register
return "dwarf_register(" + std::to_string(reg_number) + ")";
return "register(" + reg_info->name + ")";
}
DwarfExprEval::Completion DwarfExprEval::AppendString(const std::string& op_output,
const std::string& nice_output) {
FX_DCHECK(is_string_output()); // Must be in string output mode.
if (!string_output_.empty())
string_output_.append(", ");
if (string_output_mode_ == StringOutput::kPretty && !nice_output.empty()) {
string_output_.append(nice_output);
} else {
string_output_.append(op_output);
}
return Completion::kSync;
}
} // namespace zxdb