Skip to content

Commit e724009

Browse files
authored
[lldb] Add MockMemory class for dwarf expression testing (#168467)
This change unifies the way that we specify mocked memory to make it easy to control the process and target memory contents for unit tests. We add a MockMemory class that can be used in dwarf expression testing to specify the output of the `ReadMemory` function. The MockMemory class is built on a map that maps a `(address, size)` pair to a vector of bytes that is `size` bytes long and contains the memory contents for that `address`. The MockProcessWithMemRead and MockTarget classes are updated to use the new MockMemory interface. The MockProcessWithMemRead class was renamed to MockProcess and the old MockProcess was deleted. The old MockProcess had and ReadMemory implementation that returned the value `i & 0xff` for reading the address `i` and was easily be replaced with the MockMemory object. The CreateTestContext function now takes optional values for process memory and target memory and uses those to create the mock objects.
1 parent 01227ab commit e724009

File tree

1 file changed

+122
-94
lines changed

1 file changed

+122
-94
lines changed

lldb/unittests/Expression/DWARFExpressionTest.cpp

Lines changed: 122 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//===----------------------------------------------------------------------===//
88
#include "lldb/Expression/DWARFExpression.h"
99
#include "ValueMatcher.h"
10+
#include <unordered_map>
1011
#ifdef ARCH_AARCH64
1112
#include "Plugins/ABI/AArch64/ABISysV_arm64.h"
1213
#endif
@@ -39,49 +40,75 @@ using namespace lldb_private;
3940
using namespace llvm::dwarf;
4041

4142
namespace {
42-
struct MockProcess : Process {
43-
MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp)
44-
: Process(target_sp, listener_sp) {}
45-
46-
llvm::StringRef GetPluginName() override { return "mock process"; }
43+
/// Mock memory implementation for testing.
44+
/// Stores predefined memory contents indexed by {address, size} pairs.
45+
class MockMemory {
46+
public:
47+
/// Represents a memory read request with an address and size.
48+
/// Used as a key in the memory map to look up predefined test data.
49+
struct Request {
50+
lldb::addr_t addr;
51+
size_t size;
52+
53+
bool operator==(const Request &other) const {
54+
return addr == other.addr && size == other.size;
55+
}
4756

48-
bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override {
49-
return false;
57+
/// Hash function for Request to enable its use in unordered_map.
58+
struct Hash {
59+
size_t operator()(const Request &req) const {
60+
size_t h1 = std::hash<lldb::addr_t>{}(req.addr);
61+
size_t h2 = std::hash<size_t>{}(req.size);
62+
return h1 ^ (h2 << 1);
63+
}
64+
};
5065
};
5166

52-
Status DoDestroy() override { return {}; }
53-
54-
void RefreshStateAfterStop() override {}
55-
56-
bool DoUpdateThreadList(ThreadList &old_thread_list,
57-
ThreadList &new_thread_list) override {
58-
return false;
59-
};
67+
typedef std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> Map;
68+
MockMemory() = default;
69+
MockMemory(Map memory) : m_memory(std::move(memory)) {
70+
// Make sure the requested memory size matches the returned value.
71+
for (auto &kv : m_memory) {
72+
auto &req = kv.first;
73+
auto &bytes = kv.second;
74+
assert(bytes.size() == req.size);
75+
}
76+
}
6077

61-
size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size,
62-
Status &error) override {
63-
for (size_t i = 0; i < size; ++i)
64-
((char *)buf)[i] = (vm_addr + i) & 0xff;
65-
error.Clear();
66-
return size;
78+
llvm::Expected<std::vector<uint8_t>> ReadMemory(lldb::addr_t addr,
79+
size_t size) {
80+
if (!m_memory.count({addr, size})) {
81+
return llvm::createStringError(
82+
llvm::inconvertibleErrorCode(),
83+
"MockMemory::ReadMemory {address=0x%" PRIx64 ", size=%zu} not found",
84+
addr, size);
85+
}
86+
return m_memory[{addr, size}];
6787
}
88+
89+
private:
90+
std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> m_memory;
6891
};
6992

70-
/// A Process whose `ReadMemory` override queries a DenseMap.
71-
struct MockProcessWithMemRead : Process {
93+
/// A Process whose `ReadMemory` override queries MockMemory.
94+
struct MockProcess : Process {
7295
using addr_t = lldb::addr_t;
7396

74-
llvm::DenseMap<addr_t, addr_t> memory_map;
97+
MockMemory m_memory;
7598

76-
MockProcessWithMemRead(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
77-
llvm::DenseMap<addr_t, addr_t> &&memory_map)
78-
: Process(target_sp, listener_sp), memory_map(memory_map) {}
99+
MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp,
100+
MockMemory memory)
101+
: Process(target_sp, listener_sp), m_memory(std::move(memory)) {}
79102
size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size,
80103
Status &error) override {
81-
assert(memory_map.contains(vm_addr));
82-
assert(size == sizeof(addr_t));
83-
*reinterpret_cast<addr_t *>(buf) = memory_map[vm_addr];
84-
return sizeof(addr_t);
104+
auto expected_memory = m_memory.ReadMemory(vm_addr, size);
105+
if (!expected_memory) {
106+
error = Status::FromError(expected_memory.takeError());
107+
return 0;
108+
}
109+
assert(expected_memory->size() == size);
110+
std::memcpy(buf, expected_memory->data(), expected_memory->size());
111+
return size;
85112
}
86113
size_t ReadMemory(addr_t addr, void *buf, size_t size,
87114
Status &status) override {
@@ -202,6 +229,35 @@ class DWARFExpressionMockProcessTest : public ::testing::Test {
202229
}
203230
};
204231

232+
/// Mock target implementation for testing.
233+
/// Provides predefined memory contents via MockMemory instead of reading from
234+
/// a real process.
235+
class MockTarget : public Target {
236+
public:
237+
MockTarget(Debugger &debugger, const ArchSpec &target_arch,
238+
const lldb::PlatformSP &platform_sp, MockMemory memory)
239+
: Target(debugger, target_arch, platform_sp, true),
240+
m_memory(std::move(memory)) {}
241+
242+
size_t ReadMemory(const Address &addr, void *dst, size_t dst_len,
243+
Status &error, bool force_live_memory = false,
244+
lldb::addr_t *load_addr_ptr = nullptr,
245+
bool *did_read_live_memory = nullptr) override {
246+
auto expected_memory = m_memory.ReadMemory(addr.GetOffset(), dst_len);
247+
if (!expected_memory) {
248+
error = Status::FromError(expected_memory.takeError());
249+
return 0;
250+
}
251+
const size_t bytes_read = expected_memory->size();
252+
assert(bytes_read <= dst_len);
253+
std::memcpy(dst, expected_memory->data(), bytes_read);
254+
return bytes_read;
255+
}
256+
257+
private:
258+
MockMemory m_memory;
259+
};
260+
205261
struct TestContext {
206262
lldb::PlatformSP platform_sp;
207263
lldb::TargetSP target_sp;
@@ -213,30 +269,34 @@ struct TestContext {
213269

214270
/// A helper function to create TestContext objects with the
215271
/// given triple, memory, and register contents.
216-
static bool CreateTestContext(
217-
TestContext *ctx, llvm::StringRef triple,
218-
std::optional<RegisterValue> reg_value = {},
219-
std::optional<llvm::DenseMap<lldb::addr_t, lldb::addr_t>> memory = {}) {
272+
static bool CreateTestContext(TestContext *ctx, llvm::StringRef triple,
273+
std::optional<RegisterValue> reg_value = {},
274+
std::optional<MockMemory> process_memory = {},
275+
std::optional<MockMemory> target_memory = {}) {
220276
ArchSpec arch(triple);
221-
Platform::SetHostPlatform(
222-
platform_linux::PlatformLinux::CreateInstance(true, &arch));
223-
lldb::PlatformSP platform_sp;
277+
lldb::PlatformSP platform_sp =
278+
platform_linux::PlatformLinux::CreateInstance(true, &arch);
279+
Platform::SetHostPlatform(platform_sp);
224280
lldb::TargetSP target_sp;
225281
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
226-
Status status = debugger_sp->GetTargetList().CreateTarget(
227-
*debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
282+
283+
Status status;
284+
if (target_memory)
285+
target_sp = std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp,
286+
std::move(*target_memory));
287+
else
288+
status = debugger_sp->GetTargetList().CreateTarget(
289+
*debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp);
228290

229291
EXPECT_TRUE(status.Success());
230292
if (!status.Success())
231293
return false;
232294

233295
lldb::ProcessSP process_sp;
234-
if (memory)
235-
process_sp = std::make_shared<MockProcessWithMemRead>(
236-
target_sp, Listener::MakeListener("dummy"), std::move(*memory));
237-
else
238-
process_sp = std::make_shared<MockProcess>(target_sp,
239-
Listener::MakeListener("dummy"));
296+
if (!process_memory)
297+
process_memory = MockMemory();
298+
process_sp = std::make_shared<MockProcess>(
299+
target_sp, Listener::MakeListener("dummy"), std::move(*process_memory));
240300

241301
auto thread_sp = std::make_shared<MockThread>(*process_sp);
242302

@@ -253,35 +313,6 @@ static bool CreateTestContext(
253313
return true;
254314
}
255315

256-
// NB: This class doesn't use the override keyword to avoid
257-
// -Winconsistent-missing-override warnings from the compiler. The
258-
// inconsistency comes from the overriding definitions in the MOCK_*** macros.
259-
class MockTarget : public Target {
260-
public:
261-
MockTarget(Debugger &debugger, const ArchSpec &target_arch,
262-
const lldb::PlatformSP &platform_sp)
263-
: Target(debugger, target_arch, platform_sp, true) {}
264-
265-
MOCK_METHOD2(ReadMemory,
266-
llvm::Expected<std::vector<uint8_t>>(lldb::addr_t addr,
267-
size_t size));
268-
269-
size_t ReadMemory(const Address &addr, void *dst, size_t dst_len,
270-
Status &error, bool force_live_memory = false,
271-
lldb::addr_t *load_addr_ptr = nullptr,
272-
bool *did_read_live_memory = nullptr) /*override*/ {
273-
auto expected_memory = this->ReadMemory(addr.GetOffset(), dst_len);
274-
if (!expected_memory) {
275-
llvm::consumeError(expected_memory.takeError());
276-
return 0;
277-
}
278-
const size_t bytes_read = expected_memory->size();
279-
assert(bytes_read <= dst_len);
280-
std::memcpy(dst, expected_memory->data(), bytes_read);
281-
return bytes_read;
282-
}
283-
};
284-
285316
TEST(DWARFExpression, DW_OP_pick) {
286317
EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}),
287318
ExpectScalar(0));
@@ -544,8 +575,12 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) {
544575
EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit0, DW_OP_deref}), llvm::Failed());
545576

546577
// Set up a mock process.
578+
MockMemory::Map memory = {
579+
{{0x4, 4}, {0x4, 0x5, 0x6, 0x7}},
580+
};
547581
TestContext test_ctx;
548-
ASSERT_TRUE(CreateTestContext(&test_ctx, "i386-pc-linux"));
582+
ASSERT_TRUE(
583+
CreateTestContext(&test_ctx, "i386-pc-linux", {}, std::move(memory)));
549584

550585
ExecutionContext exe_ctx(test_ctx.process_sp);
551586
// Implicit location: *0x4.
@@ -1057,23 +1092,16 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) {
10571092
using ::testing::Return;
10581093

10591094
// Set up a mock process.
1060-
ArchSpec arch("i386-pc-linux");
1061-
Platform::SetHostPlatform(
1062-
platform_linux::PlatformLinux::CreateInstance(true, &arch));
1063-
lldb::DebuggerSP debugger_sp = Debugger::CreateInstance();
1064-
ASSERT_TRUE(debugger_sp);
1065-
lldb::PlatformSP platform_sp;
1066-
auto target_sp =
1067-
std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp);
1068-
ASSERT_TRUE(target_sp);
1069-
ASSERT_TRUE(target_sp->GetArchitecture().IsValid());
1070-
1071-
EXPECT_CALL(*target_sp, ReadMemory(0x40, 1))
1072-
.WillOnce(Return(ByMove(std::vector<uint8_t>{0x11})));
1073-
EXPECT_CALL(*target_sp, ReadMemory(0x50, 1))
1074-
.WillOnce(Return(ByMove(std::vector<uint8_t>{0x22})));
1095+
TestContext test_ctx;
1096+
MockMemory::Map memory = {
1097+
{{0x40, 1}, {0x11}},
1098+
{{0x50, 1}, {0x22}},
1099+
};
1100+
ASSERT_TRUE(
1101+
CreateTestContext(&test_ctx, "i386-pc-linux", {}, {}, std::move(memory)));
1102+
ASSERT_TRUE(test_ctx.target_sp->GetArchitecture().IsValid());
10751103

1076-
ExecutionContext exe_ctx(static_cast<lldb::TargetSP>(target_sp), false);
1104+
ExecutionContext exe_ctx(test_ctx.target_sp, false);
10771105

10781106
uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1,
10791107
DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1};
@@ -1106,10 +1134,10 @@ class DWARFExpressionMockProcessTestWithAArch
11061134
/// The expression DW_OP_breg22, 0, DW_OP_deref should produce that same value,
11071135
/// without clearing the top byte 0xff.
11081136
TEST_F(DWARFExpressionMockProcessTestWithAArch, DW_op_deref_no_ptr_fixing) {
1109-
llvm::DenseMap<lldb::addr_t, lldb::addr_t> memory;
11101137
constexpr lldb::addr_t expected_value = ((0xffULL) << 56) | 0xabcdefULL;
11111138
constexpr lldb::addr_t addr = 42;
1112-
memory[addr] = expected_value;
1139+
MockMemory::Map memory = {
1140+
{{addr, sizeof(addr)}, {0xef, 0xcd, 0xab, 0x00, 0x00, 0x00, 0x00, 0xff}}};
11131141

11141142
TestContext test_ctx;
11151143
ASSERT_TRUE(CreateTestContext(&test_ctx, "aarch64-pc-linux",

0 commit comments

Comments
 (0)