blob: 7a9ffe3f423d90f3b6e30326f74ac369ea29c7ae [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 <gtest/gtest.h>
#include "llvm/BinaryFormat/Dwarf.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/common/test_with_loop.h"
#include "src/developer/debug/zxdb/symbols/arch.h"
#include "src/developer/debug/zxdb/symbols/compile_unit.h"
#include "src/developer/debug/zxdb/symbols/mock_module_symbols.h"
#include "src/developer/debug/zxdb/symbols/mock_symbol_data_provider.h"
#include "src/developer/debug/zxdb/symbols/symbol_test_parent_setter.h"
#include "src/developer/debug/zxdb/symbols/type_test_support.h"
#include "src/developer/debug/zxdb/symbols/variable.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
namespace {
using debug::RegisterID;
// Base address of the imaginary module. Relative addresses will be relative to this number.
constexpr TargetPointer kModuleBase = 0x78000000;
class DwarfExprEvalTest : public TestWithLoop {
public:
DwarfExprEvalTest() : provider_(fxl::MakeRefCounted<MockSymbolDataProvider>()) {}
DwarfExprEval& eval() { return eval_; }
fxl::RefPtr<MockSymbolDataProvider> provider() { return provider_; }
const SymbolContext symbol_context() const { return symbol_context_; }
// The expected_string is the stringified version of this expression.
//
// If expected_message is non-null, this error message will be expected on failure. The expected
// result will only be checked on success, true, and the expected_message will only be checked on
// failure.
//
// If the expected_result_type is "kData", the expected_result will be ignored. The caller should
// manually validate the result.
//
// The DwarfExprEval used in the computation will be in the completed state so tests can check
// eval().whatever for additional validation after this call returns.
void DoEvalTest(const std::vector<uint8_t> data, bool expected_success,
DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type, const char* expected_string,
const char* expected_message = nullptr);
// Same as the above but takes a DwarfExpr.
void DoEvalTest(DwarfExpr expr, bool expected_success,
DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type, const char* expected_string,
const char* expected_message = nullptr);
// Just does the evaluation part of the eval test. This does not check the stringified version
// of the expression, and does not clear any previous state of the evaluator.
//
// Tests can use this variant if they want to set up some stack entries manually and then run an
// expression based on those.
void DoEval(DwarfExpr expr, bool expected_success, DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type,
const char* expected_message = nullptr);
private:
DwarfExprEval eval_;
fxl::RefPtr<MockSymbolDataProvider> provider_;
SymbolContext symbol_context_ = SymbolContext(kModuleBase);
};
void DwarfExprEvalTest::DoEvalTest(const std::vector<uint8_t> data, bool expected_success,
DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type,
const char* expected_string, const char* expected_message) {
DoEvalTest(DwarfExpr(data), expected_success, expected_completion, expected_result,
expected_result_type, expected_string, expected_message);
}
void DwarfExprEvalTest::DoEvalTest(DwarfExpr expr, bool expected_success,
DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type,
const char* expected_string, const char* expected_message) {
// Check string-ification. Do this first because it won't set up the complete state of the
// DwarfExprEval and some tests want to validate this after the DoEvalTest call.
eval_.Clear();
std::string stringified = eval_.ToString(provider(), symbol_context_, expr, false);
EXPECT_EQ(expected_string, stringified);
eval_.Clear();
DoEval(expr, expected_success, expected_completion, expected_result, expected_result_type,
expected_message);
}
void DwarfExprEvalTest::DoEval(DwarfExpr expr, bool expected_success,
DwarfExprEval::Completion expected_completion,
const DwarfStackEntry& expected_result,
DwarfExprEval::ResultType expected_result_type,
const char* expected_message) {
bool callback_issued = false;
EXPECT_EQ(expected_completion,
eval_.Eval(provider(), symbol_context_, expr,
[&callback_issued, expected_success, expected_result, expected_result_type,
expected_message](DwarfExprEval* eval, const Err& err) {
EXPECT_TRUE(eval->is_complete());
EXPECT_EQ(expected_success, !err.has_error()) << err.msg();
if (err.ok()) {
EXPECT_EQ(expected_result_type, eval->GetResultType());
if (expected_result_type != DwarfExprEval::ResultType::kData)
EXPECT_EQ(expected_result, eval->GetResult());
} else if (expected_message) {
EXPECT_EQ(expected_message, err.msg());
}
callback_issued = true;
}));
if (expected_completion == DwarfExprEval::Completion::kAsync) {
// In the async case the message loop needs to be run to get the result.
EXPECT_FALSE(eval_.is_complete());
EXPECT_FALSE(callback_issued);
// Ensure the callback was made after running the loop.
loop().RunUntilNoTasks();
}
EXPECT_TRUE(eval_.is_complete());
EXPECT_TRUE(callback_issued);
}
const debug::RegisterID kDWARFReg0ID = debug::RegisterID::kARMv8_x0;
const debug::RegisterID kDWARFReg1ID = debug::RegisterID::kARMv8_x1;
const debug::RegisterID kDWARFReg3ID = debug::RegisterID::kARMv8_x3;
const debug::RegisterID kDWARFReg4ID = debug::RegisterID::kARMv8_x4;
const debug::RegisterID kDWARFReg5ID = debug::RegisterID::kARMv8_x5;
const debug::RegisterID kDWARFReg6ID = debug::RegisterID::kARMv8_x6;
const debug::RegisterID kDWARFReg9ID = debug::RegisterID::kARMv8_x9;
} // namespace
TEST_F(DwarfExprEvalTest, NoResult) {
const char kNoResults[] = "DWARF expression produced no results.";
// Empty expression.
DoEvalTest(DwarfExpr(), false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "", kNoResults);
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_TRUE(eval().result_is_constant());
// Nonempty expression that produces no results.
DoEvalTest({llvm::dwarf::DW_OP_nop}, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "DW_OP_nop", kNoResults);
}
TEST_F(DwarfExprEvalTest, MarkValue) {
// A computation without "stack_value" should report the result type as a pointers.
DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit4");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_TRUE(eval().result_is_constant());
// "stack value" should mark the result as a stack value.
DoEvalTest({llvm::dwarf::DW_OP_lit4, llvm::dwarf::DW_OP_stack_value}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
DwarfExprEval::ResultType::kValue, "DW_OP_lit4, DW_OP_stack_value");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_TRUE(eval().result_is_constant());
}
// Tests that we can recover from infinite loops and destroy the evaluator when it's got an
// asynchronous operation pending.
TEST_F(DwarfExprEvalTest, InfiniteLoop) {
// This expression loops back to the beginning infinitely.
std::vector<uint8_t> loop_data = {llvm::dwarf::DW_OP_skip, 0xfd, 0xff};
std::unique_ptr<DwarfExprEval> eval = std::make_unique<DwarfExprEval>();
bool callback_issued = false;
eval->Eval(provider(), symbol_context(), DwarfExpr(loop_data),
[&callback_issued](DwarfExprEval* eval, const Err& err) { callback_issued = true; });
// Let the message loop process messages for a few times so the evaluator can run.
loop().PostTask(FROM_HERE, []() { debug::MessageLoop::Current()->QuitNow(); });
loop().Run();
loop().PostTask(FROM_HERE, []() { debug::MessageLoop::Current()->QuitNow(); });
loop().Run();
// Reset the evaluator, this should cancel everything.
eval.reset();
// This should not crash (the evaluator may have posted a pending task that will get executed when
// we run the loop again, and it should notice the object is gone).
loop().PostTask(FROM_HERE, []() { debug::MessageLoop::Current()->QuitNow(); });
loop().Run();
// Callback should never have been issued.
EXPECT_FALSE(callback_issued);
}
// Tests synchronously reading a single register.
TEST_F(DwarfExprEvalTest, SyncRegister) {
constexpr uint64_t kValue = 0x1234567890123;
provider()->AddRegisterValue(kDWARFReg0ID, true, kValue);
DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(kValue), DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
// Tests the encoding form of registers as parameters to an operation rather than the version
// encoded in the operation.
//
// Also tests DW_OP_nop.
TEST_F(DwarfExprEvalTest, SyncRegisterAsNumber) {
constexpr uint64_t kValue = 0x1234567890123;
provider()->AddRegisterValue(kDWARFReg1ID, true, kValue);
// Use "regx" which will read the register number as a ULEB following it.
// The byte is the ULEB-encoded version of 1 (high bit set to 0 indicate it's the last byte).
std::vector<uint8_t> expr_data;
expr_data.push_back(llvm::dwarf::DW_OP_nop);
expr_data.push_back(llvm::dwarf::DW_OP_regx);
expr_data.push_back(0b00000001);
DoEvalTest(expr_data, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kValue),
DwarfExprEval::ResultType::kValue, "DW_OP_nop, DW_OP_regx(1)");
EXPECT_EQ(RegisterID::kARMv8_x1, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
// Tests asynchronously reading a single register.
TEST_F(DwarfExprEvalTest, AsyncRegister) {
constexpr uint64_t kValue = 0x1234567890123;
provider()->AddRegisterValue(kDWARFReg0ID, false, kValue);
DoEvalTest({llvm::dwarf::DW_OP_reg0}, true, DwarfExprEval::Completion::kAsync,
DwarfStackEntry(kValue), DwarfExprEval::ResultType::kValue, "DW_OP_reg0");
EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
// Tests synchronously hitting an invalid opcode.
TEST_F(DwarfExprEvalTest, SyncInvalidOp) {
// Make a program that consists only of a user-defined opcode (not supported). Can't use
// DW_OP_lo_user because that's a GNU TLS extension we know about.
DoEvalTest({llvm::dwarf::DW_OP_lo_user + 1}, false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0), DwarfExprEval::ResultType::kValue, "INVALID_OPCODE(0xe1)",
"Invalid opcode 0xe1 in DWARF expression.");
}
// Tests synchronously hitting an invalid opcode (async error handling).
TEST_F(DwarfExprEvalTest, AsyncInvalidOp) {
constexpr uint64_t kValue = 0x1234567890123;
provider()->AddRegisterValue(kDWARFReg0ID, false, kValue);
// Make a program that consists of getting an async register and then executing an invalid opcode.
// Can't use DW_OP_lo_user because that's a GNU TLS extension we know about.
std::vector<uint8_t> expr_data;
expr_data.push_back(llvm::dwarf::DW_OP_reg0);
expr_data.push_back(llvm::dwarf::DW_OP_lo_user + 1);
DoEvalTest(expr_data, false, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "DW_OP_reg0, INVALID_OPCODE(0xe1)",
"Invalid opcode 0xe1 in DWARF expression.");
}
// Tests the special opcodes that also encode a 0-31 literal.
TEST_F(DwarfExprEvalTest, LiteralOp) {
DoEvalTest({llvm::dwarf::DW_OP_lit4}, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit4");
}
// Tests that reading fixed-length constant without enough room fails.
TEST_F(DwarfExprEvalTest, Const4ReadOffEnd) {
DoEvalTest({llvm::dwarf::DW_OP_const4u, 0xf0}, false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer,
"ERROR: \"Bad number format in DWARF expression.\"",
"Bad number format in DWARF expression.");
}
// Tests that reading a ULEB number without enough room fails.
TEST_F(DwarfExprEvalTest, ConstReadOffEnd) {
// Note that LLVM allows LEB numbers to run off the end, and in that case just stops reading data
// and reports the bits read.
DoEvalTest({llvm::dwarf::DW_OP_constu}, false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer,
"ERROR: \"Bad number format in DWARF expression.\"",
"Bad number format in DWARF expression.");
}
TEST_F(DwarfExprEvalTest, Addr) {
// This encodes the relative address 0x4000.
DoEvalTest({llvm::dwarf::DW_OP_addr, 0, 0x40, 0, 0, 0, 0, 0, 0}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(kModuleBase + 0x4000),
DwarfExprEval::ResultType::kPointer, "DW_OP_addr(0x4000)");
}
TEST_F(DwarfExprEvalTest, AddrxAndConstx) {
// These definitions depend on .debug_addr data which is provided by a ModuleSymbols.
auto module_symbols = fxl::MakeRefCounted<MockModuleSymbols>("file.exe");
// This unit has an DW_AT_addr_base which is added to the offsets for the "addrx" and "constx"
// operators for expressions inside of it.
constexpr uint64_t kAddrBase = 12;
auto compile_unit = fxl::MakeRefCounted<CompileUnit>(module_symbols->GetWeakPtr(),
DwarfLang::kCpp14, "source.cc", kAddrBase);
// Offset from kAddrBase of our value.
constexpr uint8_t kOffset = 8;
// The value of the .debug_addr entry referenced by the variable.
constexpr uint64_t kAddr = 0x12345678;
module_symbols->AddDebugAddrEntry(kAddrBase, kOffset, kAddr);
// The variable our expression will be associated with. This variable doesn't have to actually
// have a type or the location expression we're using, it just needs to reference the compilation
// unit which has the addr_base and references the mock module symbols.
auto var =
fxl::MakeRefCounted<Variable>(DwarfTag::kVariable, "var", LazySymbol(), VariableLocation());
SymbolTestParentSetter var_parent_setter(var, compile_unit); // Link compile unit to the parent.
// Since the var doesn't actrually reference this expression, we don't need to worry about
// reference cycles. If in the future we need to reference the DwarfExpr from the var above,
// we'll need to manually clear the DwarfExpr's source to prevent a leak.
DwarfExpr addrx_expr({llvm::dwarf::DW_OP_addrx, kOffset}, UncachedLazySymbol::MakeUnsafe(var));
// The "addrx" expression should read the kAddr value from the .debug_addr table at the location
// we set up, and then relocate it relative to the module's base address.
DoEvalTest(addrx_expr, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(kModuleBase + kAddr), DwarfExprEval::ResultType::kPointer,
"DW_OP_addrx(8, with addr_base=0xc) -> rel=0x12345678, abs=0x8a345678");
// Same test with "constx". This is the same except the resulting address is not relocated from
// the module base.
//
// Note: I have not actually seen this operator in use. This expected behavior is based only on my
// reading of the spec.
DwarfExpr constx_expr({llvm::dwarf::DW_OP_constx, kOffset}, UncachedLazySymbol::MakeUnsafe(var));
DoEvalTest(constx_expr, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kAddr),
DwarfExprEval::ResultType::kValue,
"DW_OP_constx(8, with addr_base=0xc) -> 0x12345678");
// Same test with an invalid address offset.
DwarfExpr invalid_expr({llvm::dwarf::DW_OP_constx, 16}, UncachedLazySymbol::MakeUnsafe(var));
DoEvalTest(invalid_expr, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer,
"ERROR: \"Unable to read .debug_addr section to evaluate expression.\"");
}
TEST_F(DwarfExprEvalTest, Breg) {
provider()->AddRegisterValue(kDWARFReg0ID, true, 100);
provider()->AddRegisterValue(kDWARFReg9ID, false, 200);
// reg0 (=100) + 129 = 229 (synchronous).
// Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_breg0, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(229u), DwarfExprEval::ResultType::kPointer, "DW_OP_breg0(129)");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
// reg9 (=200) - 127 = 73 (asynchronous).
// -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_breg9, 0x81, 0x7f}, true, DwarfExprEval::Completion::kAsync,
DwarfStackEntry(73u), DwarfExprEval::ResultType::kPointer, "DW_OP_breg9(-127)");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Bregx) {
provider()->AddRegisterValue(kDWARFReg0ID, true, 100);
provider()->AddRegisterValue(kDWARFReg9ID, false, 200);
// reg0 (=100) + 129 = 229 (synchronous).
// Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x00, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(229u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 129)");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id()); // Because there's an offset.
EXPECT_FALSE(eval().result_is_constant());
// reg9 (=200) - 127 = 73 (asynchronous).
// -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x09, 0x81, 0x7f}, true, DwarfExprEval::Completion::kAsync,
DwarfStackEntry(73u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(9, -127)");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id()); // Because there's an offset.
EXPECT_FALSE(eval().result_is_constant());
// No offset should report the register source.
// reg0 (=100) + 0 = 100 (synchronous).
DoEvalTest({llvm::dwarf::DW_OP_bregx, 0x00, 0x00}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(100u), DwarfExprEval::ResultType::kPointer, "DW_OP_bregx(0, 0)");
EXPECT_EQ(RegisterID::kARMv8_x0, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, CFA) {
constexpr uint64_t kCFA = 0xdeadbeef;
provider()->set_cfa(kCFA);
// Most expressions involving the CFA are just the CFA itself (GCC likes
// to declare the function frame base as being equal to the CFA).
DoEvalTest({llvm::dwarf::DW_OP_call_frame_cfa}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(kCFA), DwarfExprEval::ResultType::kPointer, "DW_OP_call_frame_cfa");
EXPECT_EQ(RegisterID::kUnknown, eval().current_register_id());
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const1s) {
DoEvalTest({llvm::dwarf::DW_OP_const1s, static_cast<uint8_t>(-3)}, true,
DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
DwarfExprEval::ResultType::kPointer, "DW_OP_const1s(-3)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const1u) {
DoEvalTest({llvm::dwarf::DW_OP_const1u, 0xf0}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0xf0), DwarfExprEval::ResultType::kPointer, "DW_OP_const1u(240)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const2s) {
DoEvalTest({llvm::dwarf::DW_OP_const2s, static_cast<uint8_t>(-3), 0xff}, true,
DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
DwarfExprEval::ResultType::kPointer, "DW_OP_const2s(-3)");
}
TEST_F(DwarfExprEvalTest, Const2u) {
DoEvalTest({llvm::dwarf::DW_OP_const2u, 0x01, 0xf0}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0xf001), DwarfExprEval::ResultType::kPointer, "DW_OP_const2u(0xf001)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const4s) {
DoEvalTest({llvm::dwarf::DW_OP_const4s, static_cast<uint8_t>(-3), 0xff, 0xff, 0xff}, true,
DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
DwarfExprEval::ResultType::kPointer, "DW_OP_const4s(-3)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const4u) {
DoEvalTest({llvm::dwarf::DW_OP_const4u, 0x03, 0x02, 0x01, 0xf0}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0xf0010203),
DwarfExprEval::ResultType::kPointer, "DW_OP_const4u(0xf0010203)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const8s) {
DoEvalTest({llvm::dwarf::DW_OP_const8s, static_cast<uint8_t>(-3), 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff},
true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-3)),
DwarfExprEval::ResultType::kPointer, "DW_OP_const8s(-3)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Const8u) {
DoEvalTest({llvm::dwarf::DW_OP_const8u, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0xf0}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0xf001020304050607u),
DwarfExprEval::ResultType::kPointer, "DW_OP_const8u(0xf001020304050607)");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Consts) {
// -127 in SLEB is 0x81, 0x7f (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_consts, 0x81, 0x7f}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-127)),
DwarfExprEval::ResultType::kPointer, "DW_OP_consts(-127)");
EXPECT_TRUE(eval().result_is_constant());
}
// Tests both "constu" and "drop".
TEST_F(DwarfExprEvalTest, ConstuDrop) {
// 129 in ULEB is 0x81, 0x01 (example in DWARF spec).
DoEvalTest(
{llvm::dwarf::DW_OP_constu, 0x81, 0x01, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_drop},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(129u),
DwarfExprEval::ResultType::kPointer, "DW_OP_constu(129), DW_OP_lit0, DW_OP_drop");
EXPECT_TRUE(eval().result_is_constant());
}
// Tests both "dup" and "add".
TEST_F(DwarfExprEvalTest, DupAdd) {
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_dup, llvm::dwarf::DW_OP_plus}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(16u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_dup, DW_OP_plus");
EXPECT_TRUE(eval().result_is_constant());
// Adding two different types fails, this is an unsigned integer and a generic type.
auto uint_type = MakeUint32Type();
eval().Clear();
eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
eval().Push(DwarfStackEntry(10u));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_plus}), false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Neg) {
// Negate one should give -1 casted to unsigned.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg}, true,
DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-1)),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg");
EXPECT_TRUE(eval().result_is_constant());
// Double negate should come back to 1.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_neg}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg, DW_OP_neg");
EXPECT_TRUE(eval().result_is_constant());
// Negating a specifically unsigned value is a no-op. I have no idea if this is what DWARF wants
// (the spec isn't very specific). With this test we at least clearly define our behavior and
// make sure that if it changes, we do so on purpose.
auto uint_type = MakeUint32Type();
eval().Clear();
eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_neg}), true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(uint_type, uint128_t(17u)), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Not) {
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_not}, true,
DwarfExprEval::Completion::kSync,
DwarfStackEntry(~static_cast<DwarfStackEntry::UnsignedType>(1)),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_not");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Or) {
// 8 | 1 = 9.
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_or}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_or");
EXPECT_TRUE(eval().result_is_constant());
// Typed as a float should fail (can't "or" non-integral types).
auto float_type = MakeFloatType();
eval().Clear();
eval().Push(DwarfStackEntry(float_type, 8.0f));
eval().Push(DwarfStackEntry(float_type, 1.0f));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_or}), false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Mul) {
// 8 * 9 = 72.
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit9, llvm::dwarf::DW_OP_mul}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(72u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit9, DW_OP_mul");
EXPECT_TRUE(eval().result_is_constant());
// Typed as a float.
auto float_type = MakeFloatType();
eval().Clear();
eval().Push(DwarfStackEntry(float_type, -8.712f));
eval().Push(DwarfStackEntry(float_type, 67.991f));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_mul}), true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(float_type, -592.3376f), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Minus) {
// 8 - 2 = 6.
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_minus}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(6u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit2, DW_OP_minus");
EXPECT_TRUE(eval().result_is_constant());
// Typed as a double.
auto double_type = MakeDoubleType();
eval().Clear();
eval().Push(DwarfStackEntry(double_type, 8.712));
eval().Push(DwarfStackEntry(double_type, 67.991));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_minus}), true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(double_type, -59.279), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Over) {
// Stack of (1, 2), this pushes "1" on the top.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_over}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_over");
EXPECT_TRUE(eval().result_is_constant());
// Same operation with a drop to check the next-to-top item.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_over,
llvm::dwarf::DW_OP_drop},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_over, DW_OP_drop");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Pick) {
// Stack of 1, 2, 3. Pick 0 -> 3.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_pick, 0},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(3u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(0)");
EXPECT_TRUE(eval().result_is_constant());
// Stack of 1, 2, 3. Pick 2 -> 1.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_pick, 2},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(2)");
EXPECT_TRUE(eval().result_is_constant());
// Stack of 1, 2, 3. Pick 3 -> error.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_pick, 3},
false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_pick(3)",
"Stack underflow for DWARF expression.");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Swap) {
// 1, 2, swap -> 2, 1
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_swap}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_swap");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_swap,
llvm::dwarf::DW_OP_drop},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_swap, DW_OP_drop");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Rot) {
// 1, 2, 3, rot -> 3, 1, 2 (test with 0, 1, and 2 "drops" to check all 3
// stack elements).
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_rot},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(2u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_rot, llvm::dwarf::DW_OP_drop},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot, DW_OP_drop");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_lit3,
llvm::dwarf::DW_OP_rot, llvm::dwarf::DW_OP_drop, llvm::dwarf::DW_OP_drop},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(3u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_lit2, DW_OP_lit3, DW_OP_rot, DW_OP_drop, DW_OP_drop");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Abs) {
// Abs of 1 -> 1.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_abs}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_abs");
EXPECT_TRUE(eval().result_is_constant());
// Abs of -1 -> 1.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_abs}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_neg, DW_OP_abs");
// Absolute value of an explicitly unsigned type is a no-op (this value is picked so it will
// behave differently if the code treats it as signed.
auto uint_type = MakeUint32Type();
DwarfStackEntry stack_val(uint_type, static_cast<DwarfStackEntry::UnsignedType>(-5));
eval().Clear();
eval().Push(stack_val);
DoEval(DwarfExpr({llvm::dwarf::DW_OP_neg}), true, DwarfExprEval::Completion::kSync, stack_val,
DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, And) {
// 3 (=0b11) & 5 (=0b101) = 1
DoEvalTest({llvm::dwarf::DW_OP_lit3, llvm::dwarf::DW_OP_lit5, llvm::dwarf::DW_OP_and}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit3, DW_OP_lit5, DW_OP_and");
EXPECT_TRUE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Div) {
// 8 / -2 = -4.
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_neg,
llvm::dwarf::DW_OP_div},
true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-4)),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit2, DW_OP_neg, DW_OP_div");
EXPECT_TRUE(eval().result_is_constant());
// Divide by zero should give an error.
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_div}, false,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit0, DW_OP_div",
"Error evaluating DW_OP_div in DWARF expression: Divide by zero.");
}
TEST_F(DwarfExprEvalTest, Mod) {
// 7 % 2 = 1
DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit2, llvm::dwarf::DW_OP_mod}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit2, DW_OP_mod");
EXPECT_TRUE(eval().result_is_constant());
// Modulo 0 should give an error
DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_mod}, false,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit0, DW_OP_mod",
"Error evaluating DW_OP_mod in DWARF expression: Divide by zero.");
}
TEST_F(DwarfExprEvalTest, PlusUconst) {
// 7 + 129 = 136. 129 in ULEB is 0x81, 0x01 (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_plus_uconst, 0x81, 0x01}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(136u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_plus_uconst(129)");
EXPECT_TRUE(eval().result_is_constant());
// The value should get reinterpreted as the same type as the top of the stack. This adds 129 to
// a floating-point number.
auto double_type = MakeDoubleType();
eval().Clear();
eval().Push(DwarfStackEntry(double_type, 3.14159));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_plus_uconst, 0x81, 0x01}), true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(double_type, 132.14159),
DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Shr) {
// 8 >> 1 = 4
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_shr}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(4u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_shr");
}
TEST_F(DwarfExprEvalTest, Shra) {
// -7 (=0b1111...1111001) >> 2 = -2 (=0b1111...1110)
DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_neg, llvm::dwarf::DW_OP_lit2,
llvm::dwarf::DW_OP_shra},
true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(static_cast<DwarfStackEntry::UnsignedType>(-2)),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_neg, DW_OP_lit2, DW_OP_shra");
}
TEST_F(DwarfExprEvalTest, Shl) {
// 8 << 1 = 16
DoEvalTest({llvm::dwarf::DW_OP_lit8, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_shl}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(16u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit8, DW_OP_lit1, DW_OP_shl");
}
TEST_F(DwarfExprEvalTest, Xor) {
// 7 (=0b111) ^ 9 (=0b1001) = 14 (=0b1110)
DoEvalTest({llvm::dwarf::DW_OP_lit7, llvm::dwarf::DW_OP_lit9, llvm::dwarf::DW_OP_xor}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(14u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit7, DW_OP_lit9, DW_OP_xor");
}
TEST_F(DwarfExprEvalTest, Skip) {
// Note for these tests that execution evaluates the skip, but printing the instructions does
// not. Otherwise it could loop infinitely as it traces a program to print.
// Skip 0 (execute next instruction which just gives a constant).
DoEvalTest({llvm::dwarf::DW_OP_skip, 0, 0, llvm::dwarf::DW_OP_lit9}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
DwarfExprEval::ResultType::kPointer, "DW_OP_skip(0), DW_OP_lit9");
// Skip 1 (skip over user-defined instruction which would normally give an error).
DoEvalTest({llvm::dwarf::DW_OP_skip, 1, 0, llvm::dwarf::DW_OP_hi_user, llvm::dwarf::DW_OP_lit9},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_skip(1), INVALID_OPCODE(0xff), DW_OP_lit9");
// Skip to the end should just terminate the program. The result when nothing is left on the stack
// is 0.
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_skip, 1, 0, llvm::dwarf::DW_OP_nop}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_skip(1), DW_OP_nop");
// Skip before the beginning is an error.
DoEvalTest({llvm::dwarf::DW_OP_skip, 0, 0xff}, false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0), DwarfExprEval::ResultType::kPointer, "DW_OP_skip(-256)",
"DWARF expression skips out-of-bounds.");
}
TEST_F(DwarfExprEvalTest, Bra) {
// 0 @ top of stack means don't take the branch. This jumps out of bounds which should not be
// taken.
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_bra, 0xff, 0, llvm::dwarf::DW_OP_lit9},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_bra(255), DW_OP_lit9");
EXPECT_TRUE(eval().result_is_constant());
// Nonzero means take the branch. This jumps over a user-defined instruction which would give an
// error if executed.
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_bra, 1, 0, llvm::dwarf::DW_OP_lo_user,
llvm::dwarf::DW_OP_lit9},
true, DwarfExprEval::Completion::kSync, DwarfStackEntry(9u),
DwarfExprEval::ResultType::kPointer,
"DW_OP_lit1, DW_OP_bra(1), DW_OP_GNU_push_tls_address, DW_OP_lit9");
}
TEST_F(DwarfExprEvalTest, Eq) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_eq}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_eq");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_eq}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_eq");
// Can't compare different types (this is a signed and a float value).
auto uint_type = MakeUint32Type();
auto float_type = MakeFloatType();
eval().Clear();
eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
eval().Push(DwarfStackEntry(float_type, 17.0f));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_eq}), false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Ge) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ge}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_ge");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_ge}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_ge");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ge}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_ge");
// Can't compare different types (this is an unsigned int and an unsigned char which would be
// comparable in C).
auto uint_type = MakeUint32Type();
auto uchar_type = MakeUnsignedChar8Type();
eval().Clear();
eval().Push(DwarfStackEntry(uint_type, uint128_t(17u)));
eval().Push(DwarfStackEntry(uchar_type, uint128_t(17u)));
DoEval(DwarfExpr({llvm::dwarf::DW_OP_ge}), false, DwarfExprEval::Completion::kSync,
DwarfStackEntry(0u), DwarfExprEval::ResultType::kPointer);
}
TEST_F(DwarfExprEvalTest, Gt) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_gt}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_gt");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_gt}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_gt");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_gt}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_gt");
}
TEST_F(DwarfExprEvalTest, Le) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_le}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_le");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_le}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_le");
DoEvalTest({llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_le}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit1, DW_OP_lit0, DW_OP_le");
}
TEST_F(DwarfExprEvalTest, Lt) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lt}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_lt");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_lt}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_lt");
}
TEST_F(DwarfExprEvalTest, Ne) {
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_ne}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(0u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit0, DW_OP_ne");
DoEvalTest({llvm::dwarf::DW_OP_lit0, llvm::dwarf::DW_OP_lit1, llvm::dwarf::DW_OP_ne}, true,
DwarfExprEval::Completion::kSync, DwarfStackEntry(1u),
DwarfExprEval::ResultType::kPointer, "DW_OP_lit0, DW_OP_lit1, DW_OP_ne");
}
TEST_F(DwarfExprEvalTest, Fbreg) {
constexpr uint64_t kBase = 0x1000000;
provider()->set_bp(kBase);
DoEvalTest({llvm::dwarf::DW_OP_fbreg, 0}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(kBase), DwarfExprEval::ResultType::kPointer, "DW_OP_fbreg(0)");
EXPECT_FALSE(eval().result_is_constant());
// Note: 129 in SLEB is 0x81, 0x01 (example in DWARF spec).
DoEvalTest({llvm::dwarf::DW_OP_fbreg, 0x81, 0x01}, true, DwarfExprEval::Completion::kSync,
DwarfStackEntry(kBase + 129u), DwarfExprEval::ResultType::kPointer,
"DW_OP_fbreg(129)");
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, Deref) {
// This is a real program Clang generated. 0x58 = -40 in SLEB128 so:
// *[reg6 - 40] - 0x30
const std::vector<uint8_t> program = {llvm::dwarf::DW_OP_breg6, 0x58, llvm::dwarf::DW_OP_deref,
llvm::dwarf::DW_OP_constu, 0x30, llvm::dwarf::DW_OP_minus};
constexpr uint64_t kReg6 = 0x1000;
provider()->AddRegisterValue(kDWARFReg6ID, true, kReg6);
constexpr int64_t kOffsetFromReg6 = -40;
// Contents of the data at [reg6 - 40]
constexpr uint64_t kMemoryContents = 0x5000000000;
std::vector<uint8_t> mem;
mem.resize(sizeof(kMemoryContents));
memcpy(mem.data(), &kMemoryContents, sizeof(kMemoryContents));
provider()->AddMemory(kReg6 + kOffsetFromReg6, mem);
DoEvalTest(program, true, DwarfExprEval::Completion::kAsync,
DwarfStackEntry(kMemoryContents - 0x30), DwarfExprEval::ResultType::kPointer,
"DW_OP_breg6(-40), DW_OP_deref, DW_OP_constu(48), DW_OP_minus");
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, DerefSize) {
// This is a real program GCC generated.
// This is "[BYTE PTR rdx] + 2"
constexpr uint8_t kAddAmount = 2;
const std::vector<uint8_t> program = {
llvm::dwarf::DW_OP_breg1, 0, llvm::dwarf::DW_OP_deref_size, 0x01,
llvm::dwarf::DW_OP_plus_uconst, kAddAmount, llvm::dwarf::DW_OP_stack_value};
constexpr uint64_t kReg1 = 0x1000;
provider()->AddRegisterValue(kDWARFReg1ID, true, kReg1);
// Contents of the data at [reg1]. We have the value and some other bytes following it to make
// sure the correct number of bytes were read.
constexpr uint8_t kMemValue = 0x50;
std::vector<uint8_t> mem{kMemValue, 0xff, 0xff, 0xff, 0xff};
provider()->AddMemory(kReg1, mem);
DoEvalTest(program, true, DwarfExprEval::Completion::kAsync,
DwarfStackEntry(kMemValue + kAddAmount), DwarfExprEval::ResultType::kValue,
"DW_OP_breg1(0), DW_OP_deref_size(1), DW_OP_plus_uconst(2), DW_OP_stack_value");
EXPECT_FALSE(eval().result_is_constant());
}
TEST_F(DwarfExprEvalTest, ImplicitValue) {
// This is a real program GCC generated for the 80-bit constant 2.38. It encodes it as a 128-bit
// constant for some reason.
// clang-format off
std::vector<uint8_t> program = {llvm::dwarf::DW_OP_implicit_value, 0x10,
0x00, 0x50, 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0x98,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
constexpr uint128_t kExpected = (static_cast<uint128_t>(0x4000) << 64) | 0x9851eb851eb85000llu;
DoEvalTest(program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kExpected),
DwarfExprEval::ResultType::kValue, "DW_OP_implicit_value(16, 0x40009851eb851eb85000)");
EXPECT_TRUE(eval().result_is_constant());
// This program declares it has 0x10 bytes of data (2nd array value), but there are only 0x0f
// values following it.
std::vector<uint8_t> bad_program = {llvm::dwarf::DW_OP_implicit_value, 0x10,
0x00, 0x50, 0xb8, 0x1e, 0x85, 0xeb, 0x51, 0x98,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00};
// clang-format on
DoEvalTest(bad_program, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(kExpected),
DwarfExprEval::ResultType::kValue,
"ERROR: \"Not enough data for DWARF implicit value.\"",
"Not enough data for DWARF implicit value.");
}
TEST_F(DwarfExprEvalTest, Piece_Value) {
// This expression and the register and memory values were generated by GCC for this code with
// "-O2":
//
// int __attribute((noinline)) foo(int x, int y) {
// struct { int x, y; } s = {x, y};
// s.x *= 2;
// return s.x;
// }
//
// Structure definition:
// "x" offset = 0 (4 bytes long)
// "y" offset = 4 (4 bytes long)
//
// clang-format off
std::vector<uint8_t> program{
llvm::dwarf::DW_OP_breg3, 0, // Original s.x is in "reg3"
llvm::dwarf::DW_OP_lit1,
llvm::dwarf::DW_OP_shl, // reg3 << 1
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x04, // Pick 4 bytes.
llvm::dwarf::DW_OP_reg4, // s.y is in "reg4".
llvm::dwarf::DW_OP_piece, 0x04
};
// clang-format on
provider()->AddRegisterValue(kDWARFReg3ID, true, 1); // Original "x" value.
provider()->AddRegisterValue(kDWARFReg4ID, true, 17); // "y" value.
DoEvalTest(program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kData,
"DW_OP_breg3(0), DW_OP_lit1, DW_OP_shl, DW_OP_stack_value, DW_OP_piece(4), "
"DW_OP_reg4, DW_OP_piece(4)");
// Result should be {x = 2, y = 17}.
EXPECT_EQ("02 00 00 00 11 00 00 00\n", eval().TakeResultData().ToString());
}
TEST_F(DwarfExprEvalTest, Piece_ValueUnknown) {
// These expression were generated by GCC for this code with "-O1":
//
// struct Foo {
// float f;
// char c;
// double d;
// uint64_t asdf = 32;
// };
//
// Foo foo;
// foo.f = 78.0;
// foo.c = (char)argc;
//
// Both expressions show certain portions of the structure as being unknown with other values
// being statically known, and some values being in registers.
//
// clang-format off
std::vector<uint8_t> mostly_undefined{
llvm::dwarf::DW_OP_piece, 0x10, // 16 bytes undefined (f, c, d).
llvm::dwarf::DW_OP_const1u, 0x20, // Value of asdf = 32.
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x08
};
// clang-format on
DoEvalTest(mostly_undefined, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kData,
"DW_OP_piece(16), DW_OP_const1u(32), DW_OP_stack_value, DW_OP_piece(8)");
EXPECT_EQ(
"?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n" // 16 bytes undefined.
"20 00 00 00 00 00 00 00\n", // uint64_t = 32.
eval().TakeResultData().ToString());
// This program defines a different implementation of the same struct where the float is defined.
// clang-format off
std::vector<uint8_t> partially_defined{
llvm::dwarf::DW_OP_implicit_value, 0x04, 0x00, 0x00, 0x9c, 0x42,
llvm::dwarf::DW_OP_piece, 0x04, // 4 bytes undefined (the "float f").
llvm::dwarf::DW_OP_reg3, // rbx
llvm::dwarf::DW_OP_piece, 0x01, // Take the low byte of rbx for "char c".
llvm::dwarf::DW_OP_piece, 0x0b, // 11 bytes undefined (3 bytes padding, 8 bytes "double d").
llvm::dwarf::DW_OP_const1u, 0x20, // Value of asdf = 32.
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x08
};
// clang-format on
provider()->AddRegisterValue(kDWARFReg3ID, true, 0x8877665544332211u); // rbx value.
DoEvalTest(partially_defined, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kData,
"DW_OP_implicit_value(4, 0x429c0000), DW_OP_piece(4), DW_OP_reg3, DW_OP_piece(1), "
"DW_OP_piece(11), DW_OP_const1u(32), DW_OP_stack_value, DW_OP_piece(8)");
EXPECT_EQ(
// Low byte of rbx
// |
// Float---- | Pad----- Double-----------------
"00 00 9c 42 11 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n"
// uint64---------------
"20 00 00 00 00 00 00 00\n",
eval().TakeResultData().ToString());
// A complex program using "entry_value" that Clang produced (more general version of above).
// clang-format off
std::vector<uint8_t> entry_value{
llvm::dwarf::DW_OP_implicit_value, 0x4, 0x00, 0x00, 0x9c, 0x42,
llvm::dwarf::DW_OP_piece, 0x4,
llvm::dwarf::DW_OP_GNU_entry_value, 0x1, // 1 byte "entry value" expression follows.
llvm::dwarf::DW_OP_reg5, // The actual "entry value" expression.
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x1,
llvm::dwarf::DW_OP_piece, 0xb,
llvm::dwarf::DW_OP_const1u, 0x20,
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x8
};
// clang-format on
// Provide the entry value for register 5.
auto entry_provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
provider()->set_entry_provider(entry_provider);
entry_provider->AddRegisterValue(kDWARFReg5ID, true, 0x8877665544332211);
DoEvalTest(entry_value, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kData,
"DW_OP_implicit_value(4, 0x429c0000), DW_OP_piece(4), "
"DW_OP_GNU_entry_value(DW_OP_reg5), DW_OP_stack_value, DW_OP_piece(1), "
"DW_OP_piece(11), DW_OP_const1u(32), DW_OP_stack_value, DW_OP_piece(8)");
EXPECT_EQ(
// Low byte of entry value reg5.
// |
// Float---- | Pad----- Double-----------------
"00 00 9c 42 11 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n"
// uint64---------------
"20 00 00 00 00 00 00 00\n",
eval().TakeResultData().ToString());
}
TEST_F(DwarfExprEvalTest, Piece_Memory) {
// This expression is made up based on the Piece_Value one to also incorporate a memory
// dereference.
// clang-format off
std::vector<uint8_t> program{
llvm::dwarf::DW_OP_breg3, 0, // Original s.x is in "reg3"
llvm::dwarf::DW_OP_lit1,
llvm::dwarf::DW_OP_shl, // reg3 << 1
llvm::dwarf::DW_OP_stack_value,
llvm::dwarf::DW_OP_piece, 0x04, // Pick 4 bytes.
llvm::dwarf::DW_OP_breg4, 0, // DIFFERENT FROM ABOVE: s.y is pointed to "reg4".
llvm::dwarf::DW_OP_piece, 0x04
};
// clang-format on
// Data pointed to by "reg4".
constexpr uint64_t kReg4Address = 0x87654321;
std::vector<uint8_t> mem{0x11, 0, 0, 0};
provider()->AddMemory(kReg4Address, mem);
provider()->AddRegisterValue(kDWARFReg3ID, true, 1); // Original "x" value.
provider()->AddRegisterValue(kDWARFReg4ID, true, kReg4Address); // Points to the "y" value.
DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kData,
"DW_OP_breg3(0), DW_OP_lit1, DW_OP_shl, DW_OP_stack_value, DW_OP_piece(4), "
"DW_OP_breg4(0), DW_OP_piece(4)");
// Result should be {x = 2, y = 17}.
EXPECT_EQ("02 00 00 00 11 00 00 00\n", eval().TakeResultData().ToString());
}
TEST_F(DwarfExprEvalTest, GetTLSAddr) {
std::vector<uint8_t> program{
llvm::dwarf::DW_OP_const8u, 0, 1, 2, 3, 4, 5, 6, 7, llvm::dwarf::DW_OP_form_tls_address,
};
provider()->set_tls_segment(0xdeadbeef);
DoEvalTest(program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(0x7060504e1afbfef),
DwarfExprEval::ResultType::kPointer,
"DW_OP_const8u(0x706050403020100), DW_OP_form_tls_address");
}
// Tests the pretty formatting mode that decodes registers and simplfies literals.
TEST_F(DwarfExprEvalTest, PrettyPrint) {
eval().Clear();
std::string stringified =
eval().ToString(provider(), symbol_context(),
DwarfExpr({llvm::dwarf::DW_OP_reg3, llvm::dwarf::DW_OP_breg0, 2,
llvm::dwarf::DW_OP_lit3, llvm::dwarf::DW_OP_plus_uconst, 1,
// This address is "1" relative to the module base.
llvm::dwarf::DW_OP_addr, 1, 0, 0, 0, 0, 0, 0, 0}),
true);
EXPECT_EQ(
"register(x3), register(x0) + 2, push(3), + 1, push(" + to_hex_string(kModuleBase + 1) + ")",
stringified);
}
TEST_F(DwarfExprEvalTest, EntryValue) {
auto entry_provider = fxl::MakeRefCounted<MockSymbolDataProvider>();
provider()->set_entry_provider(entry_provider);
constexpr uint64_t kEntryX0 = 0x12783645190;
entry_provider->AddRegisterValue(kDWARFReg0ID, true, kEntryX0);
// The most common type of "entry value" expression is just the register value directly.
std::vector<uint8_t> simple_program{
llvm::dwarf::DW_OP_entry_value,
1,
llvm::dwarf::DW_OP_reg0,
llvm::dwarf::DW_OP_stack_value,
};
DoEvalTest(simple_program, true, DwarfExprEval::Completion::kSync, DwarfStackEntry(kEntryX0),
DwarfExprEval::ResultType::kValue, "DW_OP_entry_value(DW_OP_reg0), DW_OP_stack_value");
eval().Clear();
// An entry value expression with a bad length.
std::vector<uint8_t> bad_length{llvm::dwarf::DW_OP_entry_value, 23, llvm::dwarf::DW_OP_reg0};
DoEvalTest(bad_length, false, DwarfExprEval::Completion::kSync, DwarfStackEntry(0),
DwarfExprEval::ResultType::kValue,
"ERROR: \"DW_OP_entry_value sub expression is a bad length.\"",
"DW_OP_entry_value sub expression is a bad length.");
eval().Clear();
// Asynchronous entry-value evaluation. In practice this will seldon happen, but it probably means
// the entry value is computable from the stack in the calling function.
constexpr uint8_t kEntryOffset = 0x31; // Register offset in entry frame.
constexpr uint8_t kTopOffset = 0x01; // Register offset in top frame.
std::vector<uint8_t> complex_program{
llvm::dwarf::DW_OP_entry_value, 3, // 3 bytes in the program below.
// Entry value program.
llvm::dwarf::DW_OP_breg6, kEntryOffset, llvm::dwarf::DW_OP_deref,
// This is evaluated in the top frame so will get a different value for reg6.
llvm::dwarf::DW_OP_breg6, kTopOffset, llvm::dwarf::DW_OP_minus};
// Register values in both frames.
constexpr uint64_t kEntryX6 = 0x12345678;
entry_provider->AddRegisterValue(kDWARFReg6ID, true, kEntryX6);
constexpr uint64_t kTopX6 = 0x99;
provider()->AddRegisterValue(kDWARFReg6ID, true, kTopX6);
// The entry frame expression computes *(X6 + kEntryOffset).
constexpr uint64_t kEntryAddress = kEntryX6 + kEntryOffset;
constexpr uint64_t kEntryValue = 0x1122334455667788;
std::vector<uint8_t> entry_value = {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11};
entry_provider->AddMemory(kEntryAddress, entry_value);
// The outer expression computs (X6 + offset) and then subtracts that from the entry value.
constexpr uint64_t kExpected = kEntryValue - (kTopX6 + kTopOffset);
DoEvalTest(complex_program, true, DwarfExprEval::Completion::kAsync, DwarfStackEntry(kExpected),
DwarfExprEval::ResultType::kPointer,
"DW_OP_entry_value(DW_OP_breg6(49), DW_OP_deref), DW_OP_breg6(1), DW_OP_minus");
eval().Clear();
}
} // namespace zxdb