1 | //===-- DWARFExpressionTest.cpp -------------------------------------------===// |
---|---|
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "lldb/Expression/DWARFExpression.h" |
10 | #include "Plugins/Platform/Linux/PlatformLinux.h" |
11 | #include "Plugins/SymbolFile/DWARF/DWARFDebugInfo.h" |
12 | #include "Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h" |
13 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
14 | #include "TestingSupport/Symbol/YAMLModuleTester.h" |
15 | #include "lldb/Core/Debugger.h" |
16 | #include "lldb/Core/PluginManager.h" |
17 | #include "lldb/Core/Value.h" |
18 | #include "lldb/Core/dwarf.h" |
19 | #include "lldb/Host/HostInfo.h" |
20 | #include "lldb/Symbol/ObjectFile.h" |
21 | #include "lldb/Utility/StreamString.h" |
22 | #include "llvm/ADT/StringExtras.h" |
23 | #include "llvm/Testing/Support/Error.h" |
24 | #include "gtest/gtest.h" |
25 | |
26 | using namespace lldb_private; |
27 | using namespace lldb_private::dwarf; |
28 | using namespace lldb_private::plugin::dwarf; |
29 | |
30 | static llvm::Expected<Scalar> Evaluate(llvm::ArrayRef<uint8_t> expr, |
31 | lldb::ModuleSP module_sp = {}, |
32 | DWARFUnit *unit = nullptr, |
33 | ExecutionContext *exe_ctx = nullptr) { |
34 | DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, |
35 | /*addr_size*/ 4); |
36 | |
37 | llvm::Expected<Value> result = |
38 | DWARFExpression::Evaluate(exe_ctx, /*reg_ctx*/ nullptr, module_sp, |
39 | opcodes: extractor, dwarf_cu: unit, reg_set: lldb::eRegisterKindLLDB, |
40 | /*initial_value_ptr*/ nullptr, |
41 | /*object_address_ptr*/ nullptr); |
42 | if (!result) |
43 | return result.takeError(); |
44 | |
45 | switch (result->GetValueType()) { |
46 | case Value::ValueType::Scalar: |
47 | return result->GetScalar(); |
48 | case Value::ValueType::LoadAddress: |
49 | return LLDB_INVALID_ADDRESS; |
50 | case Value::ValueType::HostAddress: { |
51 | // Convert small buffers to scalars to simplify the tests. |
52 | DataBufferHeap &buf = result->GetBuffer(); |
53 | if (buf.GetByteSize() <= 8) { |
54 | uint64_t val = 0; |
55 | memcpy(dest: &val, src: buf.GetBytes(), n: buf.GetByteSize()); |
56 | return Scalar(llvm::APInt(buf.GetByteSize()*8, val, false)); |
57 | } |
58 | } |
59 | [[fallthrough]]; |
60 | default: |
61 | break; |
62 | } |
63 | return llvm::createStringError(Fmt: "unsupported value type"); |
64 | } |
65 | |
66 | class DWARFExpressionTester : public YAMLModuleTester { |
67 | public: |
68 | DWARFExpressionTester(llvm::StringRef yaml_data, size_t cu_index) : |
69 | YAMLModuleTester(yaml_data, cu_index) {} |
70 | |
71 | using YAMLModuleTester::YAMLModuleTester; |
72 | llvm::Expected<Scalar> Eval(llvm::ArrayRef<uint8_t> expr) { |
73 | return ::Evaluate(expr, module_sp: m_module_sp, unit: m_dwarf_unit); |
74 | } |
75 | }; |
76 | |
77 | /// Unfortunately Scalar's operator==() is really picky. |
78 | static Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { |
79 | Scalar scalar(value); |
80 | scalar.TruncOrExtendTo(bits, sign); |
81 | return scalar; |
82 | } |
83 | |
84 | /// This is needed for the tests that use a mock process. |
85 | class DWARFExpressionMockProcessTest : public ::testing::Test { |
86 | public: |
87 | void SetUp() override { |
88 | FileSystem::Initialize(); |
89 | HostInfo::Initialize(); |
90 | platform_linux::PlatformLinux::Initialize(); |
91 | } |
92 | void TearDown() override { |
93 | platform_linux::PlatformLinux::Terminate(); |
94 | HostInfo::Terminate(); |
95 | FileSystem::Terminate(); |
96 | } |
97 | }; |
98 | |
99 | // NB: This class doesn't use the override keyword to avoid |
100 | // -Winconsistent-missing-override warnings from the compiler. The |
101 | // inconsistency comes from the overriding definitions in the MOCK_*** macros. |
102 | class MockTarget : public Target { |
103 | public: |
104 | MockTarget(Debugger &debugger, const ArchSpec &target_arch, |
105 | const lldb::PlatformSP &platform_sp) |
106 | : Target(debugger, target_arch, platform_sp, true) {} |
107 | |
108 | MOCK_METHOD2(ReadMemory, |
109 | llvm::Expected<std::vector<uint8_t>>(lldb::addr_t addr, |
110 | size_t size)); |
111 | |
112 | size_t ReadMemory(const Address &addr, void *dst, size_t dst_len, |
113 | Status &error, bool force_live_memory = false, |
114 | lldb::addr_t *load_addr_ptr = nullptr) /*override*/ { |
115 | auto expected_memory = this->ReadMemory(gmock_a0: addr.GetOffset(), gmock_a1: dst_len); |
116 | if (!expected_memory) { |
117 | llvm::consumeError(Err: expected_memory.takeError()); |
118 | return 0; |
119 | } |
120 | const size_t bytes_read = expected_memory->size(); |
121 | assert(bytes_read <= dst_len); |
122 | std::memcpy(dest: dst, src: expected_memory->data(), n: bytes_read); |
123 | return bytes_read; |
124 | } |
125 | }; |
126 | |
127 | TEST(DWARFExpression, DW_OP_pick) { |
128 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}), |
129 | llvm::HasValue(0)); |
130 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 1}), |
131 | llvm::HasValue(1)); |
132 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 2}), |
133 | llvm::Failed()); |
134 | } |
135 | |
136 | TEST(DWARFExpression, DW_OP_const) { |
137 | // Extend to address size. |
138 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), llvm::HasValue(0x88)); |
139 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, 0x88}), |
140 | llvm::HasValue(0xffffff88)); |
141 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x47, 0x88}), |
142 | llvm::HasValue(0x8847)); |
143 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2s, 0x47, 0x88}), |
144 | llvm::HasValue(0xffff8847)); |
145 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x42, 0x47, 0x88}), |
146 | llvm::HasValue(0x88474244)); |
147 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4s, 0x44, 0x42, 0x47, 0x88}), |
148 | llvm::HasValue(0x88474244)); |
149 | |
150 | // Truncate to address size. |
151 | EXPECT_THAT_EXPECTED( |
152 | Evaluate({DW_OP_const8u, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), |
153 | llvm::HasValue(0x33221100)); |
154 | EXPECT_THAT_EXPECTED( |
155 | Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), |
156 | llvm::HasValue(0x33221100)); |
157 | |
158 | // Don't truncate to address size for compatibility with clang (pr48087). |
159 | EXPECT_THAT_EXPECTED( |
160 | Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), |
161 | llvm::HasValue(0x01010101010101)); |
162 | EXPECT_THAT_EXPECTED( |
163 | Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), |
164 | llvm::HasValue(0xffff010101010101)); |
165 | } |
166 | |
167 | TEST(DWARFExpression, DW_OP_skip) { |
168 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x42, DW_OP_skip, 0x02, 0x00, |
169 | DW_OP_const1u, 0xff}), |
170 | llvm::HasValue(0x42)); |
171 | } |
172 | |
173 | TEST(DWARFExpression, DW_OP_bra) { |
174 | EXPECT_THAT_EXPECTED( |
175 | // clang-format off |
176 | Evaluate({ |
177 | DW_OP_const1u, 0x42, // push 0x42 |
178 | DW_OP_const1u, 0x1, // push 0x1 |
179 | DW_OP_bra, 0x02, 0x00, // if 0x1 > 0, then skip 0x0002 opcodes |
180 | DW_OP_const1u, 0xff, // push 0xff |
181 | }), |
182 | // clang-format on |
183 | llvm::HasValue(0x42)); |
184 | |
185 | EXPECT_THAT_ERROR(Evaluate({DW_OP_bra, 0x01, 0x00}).takeError(), |
186 | llvm::Failed()); |
187 | } |
188 | |
189 | TEST(DWARFExpression, DW_OP_convert) { |
190 | /// Auxiliary debug info. |
191 | const char *yamldata = R"( |
192 | --- !ELF |
193 | FileHeader: |
194 | Class: ELFCLASS64 |
195 | Data: ELFDATA2LSB |
196 | Type: ET_EXEC |
197 | Machine: EM_386 |
198 | DWARF: |
199 | debug_abbrev: |
200 | - Table: |
201 | - Code: 0x00000001 |
202 | Tag: DW_TAG_compile_unit |
203 | Children: DW_CHILDREN_yes |
204 | Attributes: |
205 | - Attribute: DW_AT_language |
206 | Form: DW_FORM_data2 |
207 | - Code: 0x00000002 |
208 | Tag: DW_TAG_base_type |
209 | Children: DW_CHILDREN_no |
210 | Attributes: |
211 | - Attribute: DW_AT_encoding |
212 | Form: DW_FORM_data1 |
213 | - Attribute: DW_AT_byte_size |
214 | Form: DW_FORM_data1 |
215 | debug_info: |
216 | - Version: 4 |
217 | AddrSize: 8 |
218 | AbbrevTableID: 0 |
219 | AbbrOffset: 0x0 |
220 | Entries: |
221 | - AbbrCode: 0x00000001 |
222 | Values: |
223 | - Value: 0x000000000000000C |
224 | - AbbrCode: 0x00000000 |
225 | - Version: 4 |
226 | AddrSize: 8 |
227 | AbbrevTableID: 0 |
228 | AbbrOffset: 0x0 |
229 | Entries: |
230 | - AbbrCode: 0x00000001 |
231 | Values: |
232 | - Value: 0x000000000000000C |
233 | # 0x0000000e: |
234 | - AbbrCode: 0x00000002 |
235 | Values: |
236 | - Value: 0x0000000000000007 # DW_ATE_unsigned |
237 | - Value: 0x0000000000000004 |
238 | # 0x00000011: |
239 | - AbbrCode: 0x00000002 |
240 | Values: |
241 | - Value: 0x0000000000000007 # DW_ATE_unsigned |
242 | - Value: 0x0000000000000008 |
243 | # 0x00000014: |
244 | - AbbrCode: 0x00000002 |
245 | Values: |
246 | - Value: 0x0000000000000005 # DW_ATE_signed |
247 | - Value: 0x0000000000000008 |
248 | # 0x00000017: |
249 | - AbbrCode: 0x00000002 |
250 | Values: |
251 | - Value: 0x0000000000000008 # DW_ATE_unsigned_char |
252 | - Value: 0x0000000000000001 |
253 | # 0x0000001a: |
254 | - AbbrCode: 0x00000002 |
255 | Values: |
256 | - Value: 0x0000000000000006 # DW_ATE_signed_char |
257 | - Value: 0x0000000000000001 |
258 | # 0x0000001d: |
259 | - AbbrCode: 0x00000002 |
260 | Values: |
261 | - Value: 0x000000000000000b # DW_ATE_numeric_string |
262 | - Value: 0x0000000000000001 |
263 | - AbbrCode: 0x00000000 |
264 | |
265 | )"; |
266 | // Compile unit relative offsets to each DW_TAG_base_type |
267 | uint8_t offs_uint32_t = 0x0000000e; |
268 | uint8_t offs_uint64_t = 0x00000011; |
269 | uint8_t offs_sint64_t = 0x00000014; |
270 | uint8_t offs_uchar = 0x00000017; |
271 | uint8_t offs_schar = 0x0000001a; |
272 | |
273 | DWARFExpressionTester t(yamldata, /*cu_index=*/1); |
274 | ASSERT_TRUE((bool)t.GetDwarfUnit()); |
275 | |
276 | // Constant is given as little-endian. |
277 | bool is_signed = true; |
278 | bool not_signed = false; |
279 | |
280 | // |
281 | // Positive tests. |
282 | // |
283 | |
284 | // Leave as is. |
285 | EXPECT_THAT_EXPECTED( |
286 | t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // |
287 | DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), |
288 | llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); |
289 | |
290 | // Zero-extend to 64 bits. |
291 | EXPECT_THAT_EXPECTED( |
292 | t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // |
293 | DW_OP_convert, offs_uint64_t, DW_OP_stack_value}), |
294 | llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); |
295 | |
296 | // Sign-extend to 64 bits. |
297 | EXPECT_THAT_EXPECTED( |
298 | t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // |
299 | DW_OP_convert, offs_sint64_t, DW_OP_stack_value}), |
300 | llvm::HasValue(GetScalar(64, 0xffffffffffeeddcc, is_signed))); |
301 | |
302 | // Sign-extend, then truncate. |
303 | EXPECT_THAT_EXPECTED( |
304 | t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // |
305 | DW_OP_convert, offs_sint64_t, // |
306 | DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), |
307 | llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); |
308 | |
309 | // Truncate to default unspecified (pointer-sized) type. |
310 | EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // |
311 | DW_OP_convert, offs_sint64_t, // |
312 | DW_OP_convert, 0x00, DW_OP_stack_value}), |
313 | llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); |
314 | |
315 | // Truncate to 8 bits. |
316 | EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, |
317 | offs_uchar, DW_OP_stack_value}), |
318 | llvm::HasValue(GetScalar(8, 'A', not_signed))); |
319 | |
320 | // Also truncate to 8 bits. |
321 | EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, |
322 | offs_schar, DW_OP_stack_value}), |
323 | llvm::HasValue(GetScalar(8, 'A', is_signed))); |
324 | |
325 | // |
326 | // Errors. |
327 | // |
328 | |
329 | // No Module. |
330 | EXPECT_THAT_ERROR(Evaluate({DW_OP_const1s, 'X', DW_OP_convert, 0x00}, nullptr, |
331 | t.GetDwarfUnit()) |
332 | .takeError(), |
333 | llvm::Failed()); |
334 | |
335 | // No DIE. |
336 | EXPECT_THAT_ERROR( |
337 | t.Eval({DW_OP_const1s, 'X', DW_OP_convert, 0x01}).takeError(), |
338 | llvm::Failed()); |
339 | |
340 | // Unsupported. |
341 | EXPECT_THAT_ERROR( |
342 | t.Eval({DW_OP_const1s, 'X', DW_OP_convert, 0x1d}).takeError(), |
343 | llvm::Failed()); |
344 | } |
345 | |
346 | TEST(DWARFExpression, DW_OP_stack_value) { |
347 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_stack_value}), llvm::Failed()); |
348 | } |
349 | |
350 | TEST(DWARFExpression, DW_OP_piece) { |
351 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x11, 0x22, DW_OP_piece, 2, |
352 | DW_OP_const2u, 0x33, 0x44, DW_OP_piece, 2}), |
353 | llvm::HasValue(GetScalar(32, 0x44332211, true))); |
354 | EXPECT_THAT_EXPECTED( |
355 | Evaluate({DW_OP_piece, 1, DW_OP_const1u, 0xff, DW_OP_piece, 1}), |
356 | // Note that the "00" should really be "undef", but we can't |
357 | // represent that yet. |
358 | llvm::HasValue(GetScalar(16, 0xff00, true))); |
359 | } |
360 | |
361 | TEST(DWARFExpression, DW_OP_implicit_value) { |
362 | unsigned char bytes = 4; |
363 | |
364 | EXPECT_THAT_EXPECTED( |
365 | Evaluate({DW_OP_implicit_value, bytes, 0x11, 0x22, 0x33, 0x44}), |
366 | llvm::HasValue(GetScalar(8 * bytes, 0x44332211, true))); |
367 | } |
368 | |
369 | TEST(DWARFExpression, DW_OP_unknown) { |
370 | EXPECT_THAT_EXPECTED( |
371 | Evaluate({0xff}), |
372 | llvm::FailedWithMessage( |
373 | "Unhandled opcode DW_OP_unknown_ff in DWARFExpression")); |
374 | } |
375 | |
376 | TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) { |
377 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit0, DW_OP_deref}), llvm::Failed()); |
378 | |
379 | struct MockProcess : Process { |
380 | MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp) |
381 | : Process(target_sp, listener_sp) {} |
382 | |
383 | llvm::StringRef GetPluginName() override { return "mock process"; } |
384 | bool CanDebug(lldb::TargetSP target, |
385 | bool plugin_specified_by_name) override { |
386 | return false; |
387 | }; |
388 | Status DoDestroy() override { return {}; } |
389 | void RefreshStateAfterStop() override {} |
390 | bool DoUpdateThreadList(ThreadList &old_thread_list, |
391 | ThreadList &new_thread_list) override { |
392 | return false; |
393 | }; |
394 | size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, |
395 | Status &error) override { |
396 | for (size_t i = 0; i < size; ++i) |
397 | ((char *)buf)[i] = (vm_addr + i) & 0xff; |
398 | error.Clear(); |
399 | return size; |
400 | } |
401 | }; |
402 | |
403 | // Set up a mock process. |
404 | ArchSpec arch("i386-pc-linux"); |
405 | Platform::SetHostPlatform( |
406 | platform_linux::PlatformLinux::CreateInstance(force: true, arch: &arch)); |
407 | lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); |
408 | ASSERT_TRUE(debugger_sp); |
409 | lldb::TargetSP target_sp; |
410 | lldb::PlatformSP platform_sp; |
411 | debugger_sp->GetTargetList().CreateTarget( |
412 | debugger&: *debugger_sp, user_exe_path: "", arch, get_dependent_modules: eLoadDependentsNo, platform_sp, target_sp); |
413 | ASSERT_TRUE(target_sp); |
414 | ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); |
415 | ASSERT_TRUE(platform_sp); |
416 | lldb::ListenerSP listener_sp(Listener::MakeListener(name: "dummy")); |
417 | lldb::ProcessSP process_sp = |
418 | std::make_shared<MockProcess>(args&: target_sp, args&: listener_sp); |
419 | ASSERT_TRUE(process_sp); |
420 | |
421 | ExecutionContext exe_ctx(process_sp); |
422 | // Implicit location: *0x4. |
423 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, |
424 | {}, {}, &exe_ctx), |
425 | llvm::HasValue(GetScalar(32, 0x07060504, false))); |
426 | // Memory location: *(*0x4). |
427 | // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. |
428 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref}, {}, {}, &exe_ctx), |
429 | llvm::HasValue(Scalar(LLDB_INVALID_ADDRESS))); |
430 | // Memory location: *0x4. |
431 | // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. |
432 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4}, {}, {}, &exe_ctx), |
433 | llvm::HasValue(Scalar(4))); |
434 | // Implicit location: *0x4. |
435 | // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. |
436 | EXPECT_THAT_EXPECTED( |
437 | Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), |
438 | llvm::HasValue(GetScalar(32, 0x07060504, false))); |
439 | } |
440 | |
441 | TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { |
442 | // Set up a wasm target |
443 | ArchSpec arch("wasm32-unknown-unknown-wasm"); |
444 | lldb::PlatformSP host_platform_sp = |
445 | platform_linux::PlatformLinux::CreateInstance(force: true, arch: &arch); |
446 | ASSERT_TRUE(host_platform_sp); |
447 | Platform::SetHostPlatform(host_platform_sp); |
448 | lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); |
449 | ASSERT_TRUE(debugger_sp); |
450 | lldb::TargetSP target_sp; |
451 | lldb::PlatformSP platform_sp; |
452 | debugger_sp->GetTargetList().CreateTarget(debugger&: *debugger_sp, user_exe_path: "", arch, |
453 | get_dependent_modules: lldb_private::eLoadDependentsNo, |
454 | platform_sp, target_sp); |
455 | |
456 | ExecutionContext exe_ctx(target_sp, false); |
457 | // DW_OP_addr takes a single operand of address size width: |
458 | uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0}; |
459 | DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, |
460 | /*addr_size*/ 4); |
461 | |
462 | llvm::Expected<Value> result = DWARFExpression::Evaluate( |
463 | exe_ctx: &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, opcodes: extractor, |
464 | /*unit*/ dwarf_cu: nullptr, reg_set: lldb::eRegisterKindLLDB, |
465 | /*initial_value_ptr*/ nullptr, |
466 | /*object_address_ptr*/ nullptr); |
467 | |
468 | ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); |
469 | ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); |
470 | } |
471 | |
472 | TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { |
473 | const char *yamldata = R"( |
474 | --- !ELF |
475 | FileHeader: |
476 | Class: ELFCLASS64 |
477 | Data: ELFDATA2LSB |
478 | Type: ET_EXEC |
479 | Machine: EM_386 |
480 | DWARF: |
481 | debug_abbrev: |
482 | - Table: |
483 | - Code: 0x00000001 |
484 | Tag: DW_TAG_compile_unit |
485 | Children: DW_CHILDREN_no |
486 | Attributes: |
487 | - Attribute: DW_AT_addr_base |
488 | Form: DW_FORM_sec_offset |
489 | |
490 | debug_info: |
491 | - Version: 5 |
492 | AddrSize: 4 |
493 | UnitType: DW_UT_compile |
494 | Entries: |
495 | - AbbrCode: 0x00000001 |
496 | Values: |
497 | - Value: 0x8 # Offset of the first Address past the header |
498 | - AbbrCode: 0x0 |
499 | |
500 | debug_addr: |
501 | - Version: 5 |
502 | AddressSize: 4 |
503 | Entries: |
504 | - Address: 0x1234 |
505 | - Address: 0x5678 |
506 | )"; |
507 | |
508 | // Can't use DWARFExpressionTester from above because subsystems overlap with |
509 | // the fixture. |
510 | SubsystemRAII<ObjectFileELF, SymbolFileDWARF> subsystems; |
511 | llvm::Expected<TestFile> file = TestFile::fromYaml(Yaml: yamldata); |
512 | EXPECT_THAT_EXPECTED(file, llvm::Succeeded()); |
513 | auto module_sp = std::make_shared<Module>(args: file->moduleSpec()); |
514 | auto *dwarf_cu = llvm::cast<SymbolFileDWARF>(Val: module_sp->GetSymbolFile()) |
515 | ->DebugInfo() |
516 | .GetUnitAtIndex(idx: 0); |
517 | ASSERT_TRUE(dwarf_cu); |
518 | dwarf_cu->ExtractDIEsIfNeeded(); |
519 | |
520 | // Set up a wasm target |
521 | ArchSpec arch("wasm32-unknown-unknown-wasm"); |
522 | lldb::PlatformSP host_platform_sp = |
523 | platform_linux::PlatformLinux::CreateInstance(force: true, arch: &arch); |
524 | ASSERT_TRUE(host_platform_sp); |
525 | Platform::SetHostPlatform(host_platform_sp); |
526 | lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); |
527 | ASSERT_TRUE(debugger_sp); |
528 | lldb::TargetSP target_sp; |
529 | lldb::PlatformSP platform_sp; |
530 | debugger_sp->GetTargetList().CreateTarget(debugger&: *debugger_sp, user_exe_path: "", arch, |
531 | get_dependent_modules: lldb_private::eLoadDependentsNo, |
532 | platform_sp, target_sp); |
533 | |
534 | ExecutionContext exe_ctx(target_sp, false); |
535 | |
536 | auto evaluate = [&](DWARFExpression &expr) -> llvm::Expected<Value> { |
537 | DataExtractor extractor; |
538 | expr.GetExpressionData(data&: extractor); |
539 | return DWARFExpression::Evaluate(exe_ctx: &exe_ctx, /*reg_ctx*/ nullptr, |
540 | /*module_sp*/ {}, opcodes: extractor, dwarf_cu, |
541 | reg_set: lldb::eRegisterKindLLDB, |
542 | /*initial_value_ptr*/ nullptr, |
543 | /*object_address_ptr*/ nullptr); |
544 | }; |
545 | |
546 | // DW_OP_addrx takes a single leb128 operand, the index in the addr table: |
547 | uint8_t expr_data[] = {DW_OP_addrx, 0x01}; |
548 | DataExtractor extractor(expr_data, sizeof(expr_data), lldb::eByteOrderLittle, |
549 | /*addr_size*/ 4); |
550 | DWARFExpression expr(extractor); |
551 | |
552 | llvm::Expected<Value> result = evaluate(expr); |
553 | ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); |
554 | ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); |
555 | ASSERT_EQ(result->GetScalar().UInt(), 0x5678u); |
556 | |
557 | ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); |
558 | result = evaluate(expr); |
559 | ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); |
560 | ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); |
561 | ASSERT_EQ(result->GetScalar().UInt(), 0xdeadbeefu); |
562 | } |
563 | |
564 | class CustomSymbolFileDWARF : public SymbolFileDWARF { |
565 | static char ID; |
566 | |
567 | public: |
568 | using SymbolFileDWARF::SymbolFileDWARF; |
569 | |
570 | bool isA(const void *ClassID) const override { |
571 | return ClassID == &ID || SymbolFile::isA(ClassID); |
572 | } |
573 | static bool classof(const SymbolFile *obj) { return obj->isA(ClassID: &ID); } |
574 | |
575 | static llvm::StringRef GetPluginNameStatic() { return "custom_dwarf"; } |
576 | |
577 | static llvm::StringRef GetPluginDescriptionStatic() { |
578 | return "Symbol file reader with expression extensions."; |
579 | } |
580 | |
581 | static void Initialize() { |
582 | PluginManager::RegisterPlugin(name: GetPluginNameStatic(), |
583 | description: GetPluginDescriptionStatic(), create_callback: CreateInstance, |
584 | debugger_init_callback: SymbolFileDWARF::DebuggerInitialize); |
585 | } |
586 | |
587 | static void Terminate() { PluginManager::UnregisterPlugin(create_callback: CreateInstance); } |
588 | |
589 | static lldb_private::SymbolFile * |
590 | CreateInstance(lldb::ObjectFileSP objfile_sp) { |
591 | return new CustomSymbolFileDWARF(std::move(objfile_sp), |
592 | /*dwo_section_list*/ nullptr); |
593 | } |
594 | |
595 | lldb::offset_t |
596 | GetVendorDWARFOpcodeSize(const lldb_private::DataExtractor &data, |
597 | const lldb::offset_t data_offset, |
598 | const uint8_t op) const final { |
599 | auto offset = data_offset; |
600 | if (op != DW_OP_WASM_location) { |
601 | return LLDB_INVALID_OFFSET; |
602 | } |
603 | |
604 | // DW_OP_WASM_location WASM_GLOBAL:0x03 index:u32 |
605 | // Called with "arguments" 0x03 and 0x04 |
606 | // Location type: |
607 | if (data.GetU8(offset_ptr: &offset) != /* global */ 0x03) { |
608 | return LLDB_INVALID_OFFSET; |
609 | } |
610 | |
611 | // Index |
612 | if (data.GetU32(offset_ptr: &offset) != 0x04) { |
613 | return LLDB_INVALID_OFFSET; |
614 | } |
615 | |
616 | // Report the skipped distance: |
617 | return offset - data_offset; |
618 | } |
619 | |
620 | bool |
621 | ParseVendorDWARFOpcode(uint8_t op, const lldb_private::DataExtractor &opcodes, |
622 | lldb::offset_t &offset, |
623 | std::vector<lldb_private::Value> &stack) const final { |
624 | if (op != DW_OP_WASM_location) { |
625 | return false; |
626 | } |
627 | |
628 | // DW_OP_WASM_location WASM_GLOBAL:0x03 index:u32 |
629 | // Called with "arguments" 0x03 and 0x04 |
630 | // Location type: |
631 | if (opcodes.GetU8(offset_ptr: &offset) != /* global */ 0x03) { |
632 | return false; |
633 | } |
634 | |
635 | // Index: |
636 | if (opcodes.GetU32(offset_ptr: &offset) != 0x04) { |
637 | return false; |
638 | } |
639 | |
640 | // Return some value: |
641 | stack.push_back(x: {GetScalar(bits: 32, value: 42, sign: false)}); |
642 | return true; |
643 | } |
644 | }; |
645 | |
646 | char CustomSymbolFileDWARF::ID; |
647 | |
648 | static auto testExpressionVendorExtensions(lldb::ModuleSP module_sp, |
649 | DWARFUnit &dwarf_unit) { |
650 | // Test that expression extensions can be evaluated, for example |
651 | // DW_OP_WASM_location which is not currently handled by DWARFExpression: |
652 | EXPECT_THAT_EXPECTED(Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 |
653 | 0x04, 0x00, 0x00, // index:u32 |
654 | 0x00, DW_OP_stack_value}, |
655 | module_sp, &dwarf_unit), |
656 | llvm::HasValue(GetScalar(32, 42, false))); |
657 | |
658 | // Test that searches for opcodes work in the presence of extensions: |
659 | uint8_t expr[] = {DW_OP_WASM_location, 0x03, 0x04, 0x00, 0x00, 0x00, |
660 | DW_OP_form_tls_address}; |
661 | DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, |
662 | /*addr_size*/ 4); |
663 | DWARFExpression dwarf_expr(extractor); |
664 | ASSERT_TRUE(dwarf_expr.ContainsThreadLocalStorage(&dwarf_unit)); |
665 | } |
666 | |
667 | TEST(DWARFExpression, Extensions) { |
668 | const char *yamldata = R"( |
669 | --- !ELF |
670 | FileHeader: |
671 | Class: ELFCLASS64 |
672 | Data: ELFDATA2LSB |
673 | Type: ET_EXEC |
674 | Machine: EM_386 |
675 | DWARF: |
676 | debug_abbrev: |
677 | - Table: |
678 | - Code: 0x00000001 |
679 | Tag: DW_TAG_compile_unit |
680 | Children: DW_CHILDREN_no |
681 | debug_info: |
682 | - Version: 4 |
683 | AddrSize: 4 |
684 | Entries: |
685 | - AbbrCode: 0x1 |
686 | - AbbrCode: 0x0 |
687 | )"; |
688 | |
689 | SubsystemRAII<FileSystem, HostInfo, TypeSystemClang, ObjectFileELF, |
690 | CustomSymbolFileDWARF> |
691 | subsystems; |
692 | |
693 | llvm::Expected<TestFile> file = TestFile::fromYaml(Yaml: yamldata); |
694 | EXPECT_THAT_EXPECTED(file, llvm::Succeeded()); |
695 | |
696 | auto module_sp = std::make_shared<Module>(args: file->moduleSpec()); |
697 | auto &symfile = |
698 | *llvm::cast<CustomSymbolFileDWARF>(Val: module_sp->GetSymbolFile()); |
699 | auto *dwarf_unit = symfile.DebugInfo().GetUnitAtIndex(idx: 0); |
700 | |
701 | testExpressionVendorExtensions(module_sp, dwarf_unit&: *dwarf_unit); |
702 | } |
703 | |
704 | TEST(DWARFExpression, ExtensionsDWO) { |
705 | const char *skeleton_yamldata = R"( |
706 | --- !ELF |
707 | FileHeader: |
708 | Class: ELFCLASS64 |
709 | Data: ELFDATA2LSB |
710 | Type: ET_EXEC |
711 | Machine: EM_386 |
712 | DWARF: |
713 | debug_abbrev: |
714 | - Table: |
715 | - Code: 0x00000001 |
716 | Tag: DW_TAG_skeleton_unit |
717 | Children: DW_CHILDREN_no |
718 | Attributes: |
719 | - Attribute: DW_AT_dwo_name |
720 | Form: DW_FORM_string |
721 | - Attribute: DW_AT_dwo_id |
722 | Form: DW_FORM_data4 |
723 | debug_info: |
724 | - Version: 4 |
725 | AddrSize: 4 |
726 | Entries: |
727 | - AbbrCode: 0x1 |
728 | Values: |
729 | - CStr: "dwo_unit" |
730 | - Value: 0x01020304 |
731 | - AbbrCode: 0x0 |
732 | )"; |
733 | |
734 | // .dwo sections aren't currently supported by dwarfyaml. The dwo_yamldata |
735 | // contents where generated by roundtripping the following yaml through |
736 | // yaml2obj | obj2yaml and renaming the sections. This works because the |
737 | // structure of the .dwo and non-.dwo sections is identical. |
738 | // |
739 | // --- !ELF |
740 | // FileHeader: |
741 | // Class: ELFCLASS64 |
742 | // Data: ELFDATA2LSB |
743 | // Type: ET_EXEC |
744 | // Machine: EM_386 |
745 | // DWARF: |
746 | // debug_abbrev: #.dwo |
747 | // - Table: |
748 | // - Code: 0x00000001 |
749 | // Tag: DW_TAG_compile_unit |
750 | // Children: DW_CHILDREN_no |
751 | // Attributes: |
752 | // - Attribute: DW_AT_dwo_id |
753 | // Form: DW_FORM_data4 |
754 | // debug_info: #.dwo |
755 | // - Version: 4 |
756 | // AddrSize: 4 |
757 | // Entries: |
758 | // - AbbrCode: 0x1 |
759 | // Values: |
760 | // - Value: 0x0120304 |
761 | // - AbbrCode: 0x0 |
762 | const char *dwo_yamldata = R"( |
763 | --- !ELF |
764 | FileHeader: |
765 | Class: ELFCLASS64 |
766 | Data: ELFDATA2LSB |
767 | Type: ET_EXEC |
768 | Machine: EM_386 |
769 | Sections: |
770 | - Name: .debug_abbrev.dwo |
771 | Type: SHT_PROGBITS |
772 | AddressAlign: 0x1 |
773 | Content: '0111007506000000' |
774 | - Name: .debug_info.dwo |
775 | Type: SHT_PROGBITS |
776 | AddressAlign: 0x1 |
777 | Content: 0D00000004000000000004010403020100 |
778 | )"; |
779 | |
780 | SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, CustomSymbolFileDWARF> |
781 | subsystems; |
782 | |
783 | llvm::Expected<TestFile> skeleton_file = |
784 | TestFile::fromYaml(Yaml: skeleton_yamldata); |
785 | EXPECT_THAT_EXPECTED(skeleton_file, llvm::Succeeded()); |
786 | llvm::Expected<TestFile> dwo_file = TestFile::fromYaml(Yaml: dwo_yamldata); |
787 | EXPECT_THAT_EXPECTED(dwo_file, llvm::Succeeded()); |
788 | |
789 | auto skeleton_module_sp = |
790 | std::make_shared<Module>(args: skeleton_file->moduleSpec()); |
791 | auto &skeleton_symfile = |
792 | *llvm::cast<CustomSymbolFileDWARF>(Val: skeleton_module_sp->GetSymbolFile()); |
793 | |
794 | auto dwo_module_sp = std::make_shared<Module>(args: dwo_file->moduleSpec()); |
795 | SymbolFileDWARFDwo dwo_symfile( |
796 | skeleton_symfile, dwo_module_sp->GetObjectFile()->shared_from_this(), |
797 | 0x0120304); |
798 | auto *dwo_dwarf_unit = dwo_symfile.DebugInfo().GetUnitAtIndex(idx: 0); |
799 | |
800 | testExpressionVendorExtensions(module_sp: dwo_module_sp, dwarf_unit&: *dwo_dwarf_unit); |
801 | } |
802 | |
803 | TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { |
804 | using ::testing::ByMove; |
805 | using ::testing::ElementsAre; |
806 | using ::testing::Return; |
807 | |
808 | // Set up a mock process. |
809 | ArchSpec arch("i386-pc-linux"); |
810 | Platform::SetHostPlatform( |
811 | platform_linux::PlatformLinux::CreateInstance(force: true, arch: &arch)); |
812 | lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); |
813 | ASSERT_TRUE(debugger_sp); |
814 | lldb::PlatformSP platform_sp; |
815 | auto target_sp = |
816 | std::make_shared<MockTarget>(args&: *debugger_sp, args&: arch, args&: platform_sp); |
817 | ASSERT_TRUE(target_sp); |
818 | ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); |
819 | |
820 | EXPECT_CALL(*target_sp, ReadMemory(0x40, 1)) |
821 | .WillOnce(once_action: Return(value: ByMove(x: std::vector<uint8_t>{0x11}))); |
822 | EXPECT_CALL(*target_sp, ReadMemory(0x50, 1)) |
823 | .WillOnce(once_action: Return(value: ByMove(x: std::vector<uint8_t>{0x22}))); |
824 | |
825 | ExecutionContext exe_ctx(static_cast<lldb::TargetSP>(target_sp), false); |
826 | |
827 | uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1, |
828 | DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1}; |
829 | DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, |
830 | /*addr_size*/ 4); |
831 | llvm::Expected<Value> result = DWARFExpression::Evaluate( |
832 | exe_ctx: &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, opcodes: extractor, |
833 | /*unit*/ dwarf_cu: nullptr, reg_set: lldb::eRegisterKindLLDB, |
834 | /*initial_value_ptr*/ nullptr, |
835 | /*object_address_ptr*/ nullptr); |
836 | |
837 | ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); |
838 | ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); |
839 | ASSERT_THAT(result->GetBuffer().GetData(), ElementsAre(0x11, 0x22)); |
840 | } |
841 |
Definitions
- Evaluate
- DWARFExpressionTester
- DWARFExpressionTester
- Eval
- GetScalar
- DWARFExpressionMockProcessTest
- SetUp
- TearDown
- MockTarget
- MockTarget
- ReadMemory
- ReadMemory
- CustomSymbolFileDWARF
- isA
- classof
- GetPluginNameStatic
- GetPluginDescriptionStatic
- Initialize
- Terminate
- CreateInstance
- GetVendorDWARFOpcodeSize
- ParseVendorDWARFOpcode
- ID
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more