534 lines · cpp
1#include "PECallFrameInfo.h"2 3#include "ObjectFilePECOFF.h"4 5#include "Plugins/Process/Utility/lldb-x86-register-enums.h"6#include "lldb/Symbol/UnwindPlan.h"7#include "llvm/Support/Win64EH.h"8 9using namespace lldb;10using namespace lldb_private;11using namespace llvm::Win64EH;12 13template <typename T>14static const T *TypedRead(const DataExtractor &data_extractor, offset_t &offset,15 offset_t size = sizeof(T)) {16 return static_cast<const T *>(data_extractor.GetData(&offset, size));17}18 19struct EHInstruction {20 enum class Type {21 PUSH_REGISTER,22 ALLOCATE,23 SET_FRAME_POINTER_REGISTER,24 SAVE_REGISTER25 };26 27 uint8_t offset;28 Type type;29 uint32_t reg;30 uint32_t frame_offset;31};32 33using EHProgram = std::vector<EHInstruction>;34 35class UnwindCodesIterator {36public:37 UnwindCodesIterator(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);38 39 bool GetNext();40 bool IsError() const { return m_error; }41 42 const UnwindInfo *GetUnwindInfo() const { return m_unwind_info; }43 const UnwindCode *GetUnwindCode() const { return m_unwind_code; }44 bool IsChained() const { return m_chained; }45 46private:47 ObjectFilePECOFF &m_object_file;48 49 bool m_error = false;50 51 uint32_t m_unwind_info_rva;52 DataExtractor m_unwind_info_data;53 const UnwindInfo *m_unwind_info = nullptr;54 55 DataExtractor m_unwind_code_data;56 offset_t m_unwind_code_offset;57 const UnwindCode *m_unwind_code = nullptr;58 59 bool m_chained = false;60};61 62UnwindCodesIterator::UnwindCodesIterator(ObjectFilePECOFF &object_file,63 uint32_t unwind_info_rva)64 : m_object_file(object_file),65 m_unwind_info_rva(unwind_info_rva), m_unwind_code_offset{} {}66 67bool UnwindCodesIterator::GetNext() {68 static constexpr int UNWIND_INFO_SIZE = 4;69 70 m_error = false;71 m_unwind_code = nullptr;72 while (!m_unwind_code) {73 if (!m_unwind_info) {74 m_unwind_info_data =75 m_object_file.ReadImageDataByRVA(m_unwind_info_rva, UNWIND_INFO_SIZE);76 77 offset_t offset = 0;78 m_unwind_info =79 TypedRead<UnwindInfo>(m_unwind_info_data, offset, UNWIND_INFO_SIZE);80 if (!m_unwind_info) {81 m_error = true;82 break;83 }84 85 m_unwind_code_data = m_object_file.ReadImageDataByRVA(86 m_unwind_info_rva + UNWIND_INFO_SIZE,87 m_unwind_info->NumCodes * sizeof(UnwindCode));88 m_unwind_code_offset = 0;89 }90 91 if (m_unwind_code_offset < m_unwind_code_data.GetByteSize()) {92 m_unwind_code =93 TypedRead<UnwindCode>(m_unwind_code_data, m_unwind_code_offset);94 m_error = !m_unwind_code;95 break;96 }97 98 if (!(m_unwind_info->getFlags() & UNW_ChainInfo))99 break;100 101 uint32_t runtime_function_rva =102 m_unwind_info_rva + UNWIND_INFO_SIZE +103 ((m_unwind_info->NumCodes + 1) & ~1) * sizeof(UnwindCode);104 DataExtractor runtime_function_data = m_object_file.ReadImageDataByRVA(105 runtime_function_rva, sizeof(RuntimeFunction));106 107 offset_t offset = 0;108 const auto *runtime_function =109 TypedRead<RuntimeFunction>(runtime_function_data, offset);110 if (!runtime_function) {111 m_error = true;112 break;113 }114 115 m_unwind_info_rva = runtime_function->UnwindInfoOffset;116 m_unwind_info = nullptr;117 m_chained = true;118 }119 120 return !!m_unwind_code;121}122 123class EHProgramBuilder {124public:125 EHProgramBuilder(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);126 127 bool Build();128 129 const EHProgram &GetProgram() const { return m_program; }130 131private:132 static uint32_t ConvertMachineToLLDBRegister(uint8_t machine_reg);133 static uint32_t ConvertXMMToLLDBRegister(uint8_t xmm_reg);134 135 bool ProcessUnwindCode(UnwindCode code);136 void Finalize();137 138 bool ParseBigOrScaledFrameOffset(uint32_t &result, bool big, uint32_t scale);139 bool ParseBigFrameOffset(uint32_t &result);140 bool ParseFrameOffset(uint32_t &result);141 142 UnwindCodesIterator m_iterator;143 EHProgram m_program;144};145 146EHProgramBuilder::EHProgramBuilder(ObjectFilePECOFF &object_file,147 uint32_t unwind_info_rva)148 : m_iterator(object_file, unwind_info_rva) {}149 150bool EHProgramBuilder::Build() {151 while (m_iterator.GetNext())152 if (!ProcessUnwindCode(*m_iterator.GetUnwindCode()))153 return false;154 155 if (m_iterator.IsError())156 return false;157 158 Finalize();159 160 return true;161}162 163uint32_t EHProgramBuilder::ConvertMachineToLLDBRegister(uint8_t machine_reg) {164 static uint32_t machine_to_lldb_register[] = {165 lldb_rax_x86_64, lldb_rcx_x86_64, lldb_rdx_x86_64, lldb_rbx_x86_64,166 lldb_rsp_x86_64, lldb_rbp_x86_64, lldb_rsi_x86_64, lldb_rdi_x86_64,167 lldb_r8_x86_64, lldb_r9_x86_64, lldb_r10_x86_64, lldb_r11_x86_64,168 lldb_r12_x86_64, lldb_r13_x86_64, lldb_r14_x86_64, lldb_r15_x86_64};169 170 if (machine_reg >= std::size(machine_to_lldb_register))171 return LLDB_INVALID_REGNUM;172 173 return machine_to_lldb_register[machine_reg];174}175 176uint32_t EHProgramBuilder::ConvertXMMToLLDBRegister(uint8_t xmm_reg) {177 static uint32_t xmm_to_lldb_register[] = {178 lldb_xmm0_x86_64, lldb_xmm1_x86_64, lldb_xmm2_x86_64,179 lldb_xmm3_x86_64, lldb_xmm4_x86_64, lldb_xmm5_x86_64,180 lldb_xmm6_x86_64, lldb_xmm7_x86_64, lldb_xmm8_x86_64,181 lldb_xmm9_x86_64, lldb_xmm10_x86_64, lldb_xmm11_x86_64,182 lldb_xmm12_x86_64, lldb_xmm13_x86_64, lldb_xmm14_x86_64,183 lldb_xmm15_x86_64};184 185 if (xmm_reg >= std::size(xmm_to_lldb_register))186 return LLDB_INVALID_REGNUM;187 188 return xmm_to_lldb_register[xmm_reg];189}190 191bool EHProgramBuilder::ProcessUnwindCode(UnwindCode code) {192 uint8_t o = m_iterator.IsChained() ? 0 : code.u.CodeOffset;193 uint8_t unwind_operation = code.getUnwindOp();194 uint8_t operation_info = code.getOpInfo();195 196 switch (unwind_operation) {197 case UOP_PushNonVol: {198 uint32_t r = ConvertMachineToLLDBRegister(operation_info);199 if (r == LLDB_INVALID_REGNUM)200 return false;201 202 m_program.emplace_back(203 EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, r, 8});204 205 return true;206 }207 case UOP_AllocLarge: {208 uint32_t fo;209 if (!ParseBigOrScaledFrameOffset(fo, operation_info, 8))210 return false;211 212 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,213 LLDB_INVALID_REGNUM, fo});214 215 return true;216 }217 case UOP_AllocSmall: {218 m_program.emplace_back(219 EHInstruction{o, EHInstruction::Type::ALLOCATE, LLDB_INVALID_REGNUM,220 static_cast<uint32_t>(operation_info) * 8 + 8});221 return true;222 }223 case UOP_SetFPReg: {224 uint32_t fpr = LLDB_INVALID_REGNUM;225 if (m_iterator.GetUnwindInfo()->getFrameRegister())226 fpr = ConvertMachineToLLDBRegister(227 m_iterator.GetUnwindInfo()->getFrameRegister());228 if (fpr == LLDB_INVALID_REGNUM)229 return false;230 231 uint32_t fpro =232 static_cast<uint32_t>(m_iterator.GetUnwindInfo()->getFrameOffset()) *233 16;234 235 m_program.emplace_back(EHInstruction{236 o, EHInstruction::Type::SET_FRAME_POINTER_REGISTER, fpr, fpro});237 238 return true;239 }240 case UOP_SaveNonVol:241 case UOP_SaveNonVolBig: {242 uint32_t r = ConvertMachineToLLDBRegister(operation_info);243 if (r == LLDB_INVALID_REGNUM)244 return false;245 246 uint32_t fo;247 if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveNonVolBig,248 8))249 return false;250 251 m_program.emplace_back(252 EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});253 254 return true;255 }256 case UOP_Epilog: {257 return m_iterator.GetNext();258 }259 case UOP_SpareCode: {260 // ReSharper disable once CppIdenticalOperandsInBinaryExpression261 return m_iterator.GetNext() && m_iterator.GetNext();262 }263 case UOP_SaveXMM128:264 case UOP_SaveXMM128Big: {265 uint32_t r = ConvertXMMToLLDBRegister(operation_info);266 if (r == LLDB_INVALID_REGNUM)267 return false;268 269 uint32_t fo;270 if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveXMM128Big,271 16))272 return false;273 274 m_program.emplace_back(275 EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});276 277 return true;278 }279 case UOP_PushMachFrame: {280 if (operation_info)281 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,282 LLDB_INVALID_REGNUM, 8});283 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,284 lldb_rip_x86_64, 8});285 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,286 lldb_cs_x86_64, 8});287 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,288 lldb_rflags_x86_64, 8});289 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,290 lldb_rsp_x86_64, 8});291 m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,292 lldb_ss_x86_64, 8});293 294 return true;295 }296 default:297 return false;298 }299}300 301void EHProgramBuilder::Finalize() {302 for (const EHInstruction &i : m_program)303 if (i.reg == lldb_rip_x86_64)304 return;305 306 m_program.emplace_back(307 EHInstruction{0, EHInstruction::Type::PUSH_REGISTER, lldb_rip_x86_64, 8});308}309 310bool EHProgramBuilder::ParseBigOrScaledFrameOffset(uint32_t &result, bool big,311 uint32_t scale) {312 if (big) {313 if (!ParseBigFrameOffset(result))314 return false;315 } else {316 if (!ParseFrameOffset(result))317 return false;318 319 result *= scale;320 }321 322 return true;323}324 325bool EHProgramBuilder::ParseBigFrameOffset(uint32_t &result) {326 if (!m_iterator.GetNext())327 return false;328 329 result = m_iterator.GetUnwindCode()->FrameOffset;330 331 if (!m_iterator.GetNext())332 return false;333 334 result += static_cast<uint32_t>(m_iterator.GetUnwindCode()->FrameOffset)335 << 16;336 337 return true;338}339 340bool EHProgramBuilder::ParseFrameOffset(uint32_t &result) {341 if (!m_iterator.GetNext())342 return false;343 344 result = m_iterator.GetUnwindCode()->FrameOffset;345 346 return true;347}348 349class EHProgramRange {350public:351 EHProgramRange(EHProgram::const_iterator begin,352 EHProgram::const_iterator end);353 354 UnwindPlan::Row BuildUnwindPlanRow() const;355 356private:357 int32_t GetCFAFrameOffset() const;358 359 EHProgram::const_iterator m_begin;360 EHProgram::const_iterator m_end;361};362 363EHProgramRange::EHProgramRange(EHProgram::const_iterator begin,364 EHProgram::const_iterator end)365 : m_begin(begin), m_end(end) {}366 367UnwindPlan::Row EHProgramRange::BuildUnwindPlanRow() const {368 UnwindPlan::Row row;369 370 if (m_begin != m_end)371 row.SetOffset(m_begin->offset);372 373 int32_t cfa_frame_offset = GetCFAFrameOffset();374 375 bool frame_pointer_found = false;376 for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {377 switch (it->type) {378 case EHInstruction::Type::SET_FRAME_POINTER_REGISTER:379 row.GetCFAValue().SetIsRegisterPlusOffset(it->reg, cfa_frame_offset -380 it->frame_offset);381 frame_pointer_found = true;382 break;383 default:384 break;385 }386 if (frame_pointer_found)387 break;388 }389 if (!frame_pointer_found)390 row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64,391 cfa_frame_offset);392 393 int32_t rsp_frame_offset = 0;394 for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {395 switch (it->type) {396 case EHInstruction::Type::PUSH_REGISTER:397 row.SetRegisterLocationToAtCFAPlusOffset(398 it->reg, rsp_frame_offset - cfa_frame_offset, false);399 rsp_frame_offset += it->frame_offset;400 break;401 case EHInstruction::Type::ALLOCATE:402 rsp_frame_offset += it->frame_offset;403 break;404 case EHInstruction::Type::SAVE_REGISTER:405 row.SetRegisterLocationToAtCFAPlusOffset(406 it->reg, it->frame_offset - cfa_frame_offset, false);407 break;408 default:409 break;410 }411 }412 413 row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, false);414 415 return row;416}417 418int32_t EHProgramRange::GetCFAFrameOffset() const {419 int32_t result = 0;420 421 for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {422 switch (it->type) {423 case EHInstruction::Type::PUSH_REGISTER:424 case EHInstruction::Type::ALLOCATE:425 result += it->frame_offset;426 break;427 default:428 break;429 }430 }431 432 return result;433}434 435PECallFrameInfo::PECallFrameInfo(ObjectFilePECOFF &object_file,436 uint32_t exception_dir_rva,437 uint32_t exception_dir_size)438 : m_object_file(object_file),439 m_exception_dir(object_file.ReadImageDataByRVA(exception_dir_rva,440 exception_dir_size)) {}441 442bool PECallFrameInfo::GetAddressRange(Address addr, AddressRange &range) {443 range.Clear();444 445 const RuntimeFunction *runtime_function =446 FindRuntimeFunctionIntersectsWithRange(AddressRange(addr, 1));447 if (!runtime_function)448 return false;449 450 range.GetBaseAddress() =451 m_object_file.GetAddress(runtime_function->StartAddress);452 range.SetByteSize(runtime_function->EndAddress -453 runtime_function->StartAddress);454 455 return true;456}457 458std::unique_ptr<UnwindPlan> PECallFrameInfo::GetUnwindPlan(459 llvm::ArrayRef<lldb_private::AddressRange> ranges,460 const lldb_private::Address &addr) {461 // Only continuous functions are supported.462 if (ranges.size() != 1)463 return nullptr;464 const AddressRange &range = ranges[0];465 466 const RuntimeFunction *runtime_function =467 FindRuntimeFunctionIntersectsWithRange(range);468 if (!runtime_function)469 return nullptr;470 471 auto plan_up = std::make_unique<UnwindPlan>(eRegisterKindLLDB);472 plan_up->SetSourceName("PE EH info");473 plan_up->SetSourcedFromCompiler(eLazyBoolYes);474 475 EHProgramBuilder builder(m_object_file, runtime_function->UnwindInfoOffset);476 if (!builder.Build())477 return nullptr;478 479 std::vector<UnwindPlan::Row> rows;480 481 uint32_t last_offset = UINT32_MAX;482 for (auto it = builder.GetProgram().begin(); it != builder.GetProgram().end();483 ++it) {484 if (it->offset == last_offset)485 continue;486 487 EHProgramRange program_range =488 EHProgramRange(it, builder.GetProgram().end());489 rows.push_back(program_range.BuildUnwindPlanRow());490 491 last_offset = it->offset;492 }493 494 for (auto it = rows.rbegin(); it != rows.rend(); ++it)495 plan_up->AppendRow(std::move(*it));496 497 plan_up->SetPlanValidAddressRanges({AddressRange(498 m_object_file.GetAddress(runtime_function->StartAddress),499 runtime_function->EndAddress - runtime_function->StartAddress)});500 plan_up->SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);501 502 return plan_up;503}504 505const RuntimeFunction *PECallFrameInfo::FindRuntimeFunctionIntersectsWithRange(506 const AddressRange &range) const {507 uint32_t rva = m_object_file.GetRVA(range.GetBaseAddress());508 addr_t size = range.GetByteSize();509 510 uint32_t begin = 0;511 uint32_t end = m_exception_dir.GetByteSize() / sizeof(RuntimeFunction);512 while (begin < end) {513 uint32_t curr = (begin + end) / 2;514 515 offset_t offset = curr * sizeof(RuntimeFunction);516 const auto *runtime_function =517 TypedRead<RuntimeFunction>(m_exception_dir, offset);518 if (!runtime_function)519 break;520 521 if (runtime_function->StartAddress < rva + size &&522 runtime_function->EndAddress > rva)523 return runtime_function;524 525 if (runtime_function->StartAddress >= rva + size)526 end = curr;527 528 if (runtime_function->EndAddress <= rva)529 begin = curr + 1;530 }531 532 return nullptr;533}534