1 | //===-- TestPECallFrameInfo.cpp -------------------------------------------===// |
2 | // |
3 | // |
4 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
5 | // See https://llvm.org/LICENSE.txt for license information. |
6 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
7 | // |
8 | //===----------------------------------------------------------------------===// |
9 | |
10 | #include "gtest/gtest.h" |
11 | |
12 | #include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h" |
13 | #include "Plugins/Process/Utility/lldb-x86-register-enums.h" |
14 | #include "TestingSupport/SubsystemRAII.h" |
15 | #include "TestingSupport/TestUtilities.h" |
16 | |
17 | #include "lldb/Core/Module.h" |
18 | #include "lldb/Symbol/CallFrameInfo.h" |
19 | #include "lldb/Symbol/UnwindPlan.h" |
20 | #include "llvm/Testing/Support/Error.h" |
21 | |
22 | using namespace lldb_private; |
23 | using namespace lldb; |
24 | |
25 | class PECallFrameInfoTest : public testing::Test { |
26 | SubsystemRAII<FileSystem, ObjectFilePECOFF> subsystems; |
27 | }; |
28 | |
29 | static llvm::Expected<std::unique_ptr<UnwindPlan>> |
30 | GetUnwindPlan(addr_t file_addr) { |
31 | llvm::Expected<TestFile> ExpectedFile = TestFile::fromYaml( |
32 | Yaml: R"( |
33 | --- !COFF |
34 | OptionalHeader: |
35 | AddressOfEntryPoint: 0 |
36 | ImageBase: 16777216 |
37 | SectionAlignment: 4096 |
38 | FileAlignment: 512 |
39 | MajorOperatingSystemVersion: 6 |
40 | MinorOperatingSystemVersion: 0 |
41 | MajorImageVersion: 0 |
42 | MinorImageVersion: 0 |
43 | MajorSubsystemVersion: 6 |
44 | MinorSubsystemVersion: 0 |
45 | Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI |
46 | DLLCharacteristics: [ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA, IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLL_CHARACTERISTICS_NX_COMPAT ] |
47 | SizeOfStackReserve: 1048576 |
48 | SizeOfStackCommit: 4096 |
49 | SizeOfHeapReserve: 1048576 |
50 | SizeOfHeapCommit: 4096 |
51 | ExportTable: |
52 | RelativeVirtualAddress: 0 |
53 | Size: 0 |
54 | ImportTable: |
55 | RelativeVirtualAddress: 0 |
56 | Size: 0 |
57 | ResourceTable: |
58 | RelativeVirtualAddress: 0 |
59 | Size: 0 |
60 | ExceptionTable: |
61 | RelativeVirtualAddress: 12288 |
62 | Size: 60 |
63 | CertificateTable: |
64 | RelativeVirtualAddress: 0 |
65 | Size: 0 |
66 | BaseRelocationTable: |
67 | RelativeVirtualAddress: 0 |
68 | Size: 0 |
69 | Debug: |
70 | RelativeVirtualAddress: 0 |
71 | Size: 0 |
72 | Architecture: |
73 | RelativeVirtualAddress: 0 |
74 | Size: 0 |
75 | GlobalPtr: |
76 | RelativeVirtualAddress: 0 |
77 | Size: 0 |
78 | TlsTable: |
79 | RelativeVirtualAddress: 0 |
80 | Size: 0 |
81 | LoadConfigTable: |
82 | RelativeVirtualAddress: 0 |
83 | Size: 0 |
84 | BoundImport: |
85 | RelativeVirtualAddress: 0 |
86 | Size: 0 |
87 | IAT: |
88 | RelativeVirtualAddress: 0 |
89 | Size: 0 |
90 | DelayImportDescriptor: |
91 | RelativeVirtualAddress: 0 |
92 | Size: 0 |
93 | ClrRuntimeHeader: |
94 | RelativeVirtualAddress: 0 |
95 | Size: 0 |
96 | header: |
97 | Machine: IMAGE_FILE_MACHINE_AMD64 |
98 | Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ] |
99 | sections: |
100 | - Name: .text |
101 | Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ] |
102 | VirtualAddress: 4096 |
103 | VirtualSize: 4096 |
104 | - Name: .rdata |
105 | Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ] |
106 | VirtualAddress: 8192 |
107 | VirtualSize: 68 |
108 | SectionData: 010C06000C3208F006E00470036002302105020005540D0000100000001100000020000019400E352F74670028646600213465001A3315015E000EF00CE00AD008C00650 |
109 | |
110 | |
111 | # Unwind info at 0x2000: |
112 | # 01 0C 06 00 No chained info, prolog size = 0xC, unwind codes size is 6 words, no frame register |
113 | # 0C 32 UOP_AllocSmall(2) 3 * 8 + 8 bytes, offset in prolog is 0xC |
114 | # 08 F0 UOP_PushNonVol(0) R15(0xF), offset in prolog is 8 |
115 | # 06 E0 UOP_PushNonVol(0) R14(0xE), offset in prolog is 6 |
116 | # 04 70 UOP_PushNonVol(0) RDI(7), offset in prolog is 4 |
117 | # 03 60 UOP_PushNonVol(0) RSI(6), offset in prolog is 3 |
118 | # 02 30 UOP_PushNonVol(0) RBX(3), offset in prolog is 2 |
119 | # Corresponding prolog: |
120 | # 00 push rbx |
121 | # 02 push rsi |
122 | # 03 push rdi |
123 | # 04 push r14 |
124 | # 06 push r15 |
125 | # 08 sub rsp, 20h |
126 | |
127 | # Unwind info at 0x2010: |
128 | # 21 05 02 00 Has chained info, prolog size = 5, unwind codes size is 2 words, no frame register |
129 | # 05 54 0D 00 UOP_SaveNonVol(4) RBP(5) to RSP + 0xD * 8, offset in prolog is 5 |
130 | # Chained runtime function: |
131 | # 00 10 00 00 Start address is 0x1000 |
132 | # 00 11 00 00 End address is 0x1100 |
133 | # 00 20 00 00 Unwind info RVA is 0x2000 |
134 | # Corresponding prolog: |
135 | # 00 mov [rsp+68h], rbp |
136 | |
137 | # Unwind info at 0x2024: |
138 | # 19 40 0E 35 No chained info, prolog size = 0x40, unwind codes size is 0xE words, frame register is RBP, frame register offset is RSP + 3 * 16 |
139 | # 2F 74 67 00 UOP_SaveNonVol(4) RDI(7) to RSP + 0x67 * 8, offset in prolog is 0x2F |
140 | # 28 64 66 00 UOP_SaveNonVol(4) RSI(6) to RSP + 0x66 * 8, offset in prolog is 0x28 |
141 | # 21 34 65 00 UOP_SaveNonVol(4) RBX(3) to RSP + 0x65 * 8, offset in prolog is 0x21 |
142 | # 1A 33 UOP_SetFPReg(3), offset in prolog is 0x1A |
143 | # 15 01 5E 00 UOP_AllocLarge(1) 0x5E * 8 bytes, offset in prolog is 0x15 |
144 | # 0E F0 UOP_PushNonVol(0) R15(0xF), offset in prolog is 0xE |
145 | # 0C E0 UOP_PushNonVol(0) R14(0xE), offset in prolog is 0xC |
146 | # 0A D0 UOP_PushNonVol(0) R13(0xD), offset in prolog is 0xA |
147 | # 08 C0 UOP_PushNonVol(0) R12(0xC), offset in prolog is 8 |
148 | # 06 50 UOP_PushNonVol(0) RBP(5), offset in prolog is 6 |
149 | # Corresponding prolog: |
150 | # 00 mov [rsp+8], rcx |
151 | # 05 push rbp |
152 | # 06 push r12 |
153 | # 08 push r13 |
154 | # 0A push r14 |
155 | # 0C push r15 |
156 | # 0E sub rsp, 2F0h |
157 | # 15 lea rbp, [rsp+30h] |
158 | # 1A mov [rbp+2F8h], rbx |
159 | # 21 mov [rbp+300h], rsi |
160 | # 28 mov [rbp+308h], rdi |
161 | |
162 | - Name: .pdata |
163 | Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ] |
164 | VirtualAddress: 12288 |
165 | VirtualSize: 60 |
166 | SectionData: 000000000000000000000000000000000000000000000000001000000011000000200000001100000012000010200000001200000013000024200000 |
167 | |
168 | # 00 00 00 00 |
169 | # 00 00 00 00 Test correct processing of empty runtime functions at begin |
170 | # 00 00 00 00 |
171 | |
172 | # 00 00 00 00 |
173 | # 00 00 00 00 Test correct processing of empty runtime functions at begin |
174 | # 00 00 00 00 |
175 | |
176 | # 00 10 00 00 Start address is 0x1000 |
177 | # 00 11 00 00 End address is 0x1100 |
178 | # 00 20 00 00 Unwind info RVA is 0x2000 |
179 | |
180 | # 00 11 00 00 Start address is 0x1100 |
181 | # 00 12 00 00 End address is 0x1200 |
182 | # 10 20 00 00 Unwind info RVA is 0x2010 |
183 | |
184 | # 00 12 00 00 Start address is 0x1200 |
185 | # 00 13 00 00 End address is 0x1300 |
186 | # 24 20 00 00 Unwind info RVA is 0x2024 |
187 | |
188 | symbols: [] |
189 | ... |
190 | )" ); |
191 | if (!ExpectedFile) |
192 | return ExpectedFile.takeError(); |
193 | |
194 | ModuleSP module_sp = std::make_shared<Module>(args: ExpectedFile->moduleSpec()); |
195 | ObjectFile *object_file = module_sp->GetObjectFile(); |
196 | if (!object_file) |
197 | return llvm::createStringError(Fmt: "object file is null" ); |
198 | |
199 | std::unique_ptr<CallFrameInfo> cfi = object_file->CreateCallFrameInfo(); |
200 | if (!cfi) |
201 | return llvm::createStringError(Fmt: "call frame info is null" ); |
202 | |
203 | SectionList *sect_list = object_file->GetSectionList(); |
204 | if (!sect_list) |
205 | return llvm::createStringError(Fmt: "section list is null" ); |
206 | |
207 | std::unique_ptr<UnwindPlan> plan_up = |
208 | cfi->GetUnwindPlan(addr: Address(file_addr, sect_list)); |
209 | if (!plan_up) |
210 | return llvm::createStringError(Fmt: "unwind plan is null" ); |
211 | return plan_up; |
212 | } |
213 | |
214 | TEST_F(PECallFrameInfoTest, Basic_eh) { |
215 | llvm::Expected<std::unique_ptr<UnwindPlan>> expected_plan = |
216 | GetUnwindPlan(file_addr: 0x1001080); |
217 | ASSERT_THAT_EXPECTED(expected_plan, llvm::Succeeded()); |
218 | UnwindPlan &plan = **expected_plan; |
219 | EXPECT_EQ(plan.GetRowCount(), 7); |
220 | |
221 | UnwindPlan::Row row; |
222 | row.SetOffset(0); |
223 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 8); |
224 | row.SetRegisterLocationToIsCFAPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0, can_replace: true); |
225 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rip_x86_64, offset: -8, can_replace: true); |
226 | EXPECT_EQ(*plan.GetRowAtIndex(0), row); |
227 | |
228 | row.SetOffset(2); |
229 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x10); |
230 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rbx_x86_64, offset: -0x10, can_replace: true); |
231 | EXPECT_EQ(*plan.GetRowAtIndex(1), row); |
232 | |
233 | row.SetOffset(3); |
234 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x18); |
235 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rsi_x86_64, offset: -0x18, can_replace: true); |
236 | EXPECT_EQ(*plan.GetRowAtIndex(2), row); |
237 | |
238 | row.SetOffset(4); |
239 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x20); |
240 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rdi_x86_64, offset: -0x20, can_replace: true); |
241 | EXPECT_EQ(*plan.GetRowAtIndex(3), row); |
242 | |
243 | row.SetOffset(6); |
244 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x28); |
245 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r14_x86_64, offset: -0x28, can_replace: true); |
246 | EXPECT_EQ(*plan.GetRowAtIndex(4), row); |
247 | |
248 | row.SetOffset(8); |
249 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x30); |
250 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r15_x86_64, offset: -0x30, can_replace: true); |
251 | EXPECT_EQ(*plan.GetRowAtIndex(5), row); |
252 | |
253 | row.SetOffset(0xC); |
254 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x50); |
255 | EXPECT_EQ(*plan.GetRowAtIndex(6), row); |
256 | } |
257 | |
258 | TEST_F(PECallFrameInfoTest, Chained_eh) { |
259 | llvm::Expected<std::unique_ptr<UnwindPlan>> expected_plan = |
260 | GetUnwindPlan(file_addr: 0x1001180); |
261 | ASSERT_THAT_EXPECTED(expected_plan, llvm::Succeeded()); |
262 | UnwindPlan &plan = **expected_plan; |
263 | EXPECT_EQ(plan.GetRowCount(), 2); |
264 | |
265 | UnwindPlan::Row row; |
266 | row.SetOffset(0); |
267 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x50); |
268 | row.SetRegisterLocationToIsCFAPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0, can_replace: true); |
269 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rip_x86_64, offset: -8, can_replace: true); |
270 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rbx_x86_64, offset: -0x10, can_replace: true); |
271 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rsi_x86_64, offset: -0x18, can_replace: true); |
272 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rdi_x86_64, offset: -0x20, can_replace: true); |
273 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r14_x86_64, offset: -0x28, can_replace: true); |
274 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r15_x86_64, offset: -0x30, can_replace: true); |
275 | EXPECT_EQ(*plan.GetRowAtIndex(0), row); |
276 | |
277 | row.SetOffset(5); |
278 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rbp_x86_64, offset: 0x18, can_replace: true); |
279 | EXPECT_EQ(*plan.GetRowAtIndex(1), row); |
280 | } |
281 | |
282 | TEST_F(PECallFrameInfoTest, Frame_reg_eh) { |
283 | llvm::Expected<std::unique_ptr<UnwindPlan>> expected_plan = |
284 | GetUnwindPlan(file_addr: 0x1001280); |
285 | ASSERT_THAT_EXPECTED(expected_plan, llvm::Succeeded()); |
286 | UnwindPlan &plan = **expected_plan; |
287 | EXPECT_EQ(plan.GetRowCount(), 11); |
288 | |
289 | UnwindPlan::Row row; |
290 | row.SetOffset(0); |
291 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 8); |
292 | row.SetRegisterLocationToIsCFAPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0, can_replace: true); |
293 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rip_x86_64, offset: -8, can_replace: true); |
294 | EXPECT_EQ(*plan.GetRowAtIndex(0), row); |
295 | |
296 | row.SetOffset(6); |
297 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x10); |
298 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rbp_x86_64, offset: -0x10, can_replace: true); |
299 | EXPECT_EQ(*plan.GetRowAtIndex(1), row); |
300 | |
301 | row.SetOffset(8); |
302 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x18); |
303 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r12_x86_64, offset: -0x18, can_replace: true); |
304 | EXPECT_EQ(*plan.GetRowAtIndex(2), row); |
305 | |
306 | row.SetOffset(0xA); |
307 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x20); |
308 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r13_x86_64, offset: -0x20, can_replace: true); |
309 | EXPECT_EQ(*plan.GetRowAtIndex(3), row); |
310 | |
311 | row.SetOffset(0xC); |
312 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x28); |
313 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r14_x86_64, offset: -0x28, can_replace: true); |
314 | EXPECT_EQ(*plan.GetRowAtIndex(4), row); |
315 | |
316 | row.SetOffset(0xE); |
317 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x30); |
318 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_r15_x86_64, offset: -0x30, can_replace: true); |
319 | EXPECT_EQ(*plan.GetRowAtIndex(5), row); |
320 | |
321 | row.SetOffset(0x15); |
322 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rsp_x86_64, offset: 0x320); |
323 | EXPECT_EQ(*plan.GetRowAtIndex(6), row); |
324 | |
325 | row.SetOffset(0x1A); |
326 | row.GetCFAValue().SetIsRegisterPlusOffset(reg_num: lldb_rbp_x86_64, offset: 0x2F0); |
327 | EXPECT_EQ(*plan.GetRowAtIndex(7), row); |
328 | |
329 | row.SetOffset(0x21); |
330 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rbx_x86_64, offset: 8, can_replace: true); |
331 | EXPECT_EQ(*plan.GetRowAtIndex(8), row); |
332 | |
333 | row.SetOffset(0x28); |
334 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rsi_x86_64, offset: 0x10, can_replace: true); |
335 | EXPECT_EQ(*plan.GetRowAtIndex(9), row); |
336 | |
337 | row.SetOffset(0x2F); |
338 | row.SetRegisterLocationToAtCFAPlusOffset(reg_num: lldb_rdi_x86_64, offset: 0x18, can_replace: true); |
339 | EXPECT_EQ(*plan.GetRowAtIndex(10), row); |
340 | } |
341 | |