| 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 | |