| #include "PECallFrameInfo.h" | |
| #include "ObjectFilePECOFF.h" | |
| #include "Plugins/Process/Utility/lldb-x86-register-enums.h" | |
| #include "lldb/Symbol/UnwindPlan.h" | |
| #include "llvm/Support/Win64EH.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace llvm::Win64EH; | |
| template <typename T> | |
| static const T *TypedRead(const DataExtractor &data_extractor, offset_t &offset, | |
| offset_t size = sizeof(T)) { | |
| return static_cast<const T *>(data_extractor.GetData(&offset, size)); | |
| } | |
| struct EHInstruction { | |
| enum class Type { | |
| PUSH_REGISTER, | |
| ALLOCATE, | |
| SET_FRAME_POINTER_REGISTER, | |
| SAVE_REGISTER | |
| }; | |
| uint8_t offset; | |
| Type type; | |
| uint32_t reg; | |
| uint32_t frame_offset; | |
| }; | |
| using EHProgram = std::vector<EHInstruction>; | |
| class UnwindCodesIterator { | |
| public: | |
| UnwindCodesIterator(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva); | |
| bool GetNext(); | |
| bool IsError() const { return m_error; } | |
| const UnwindInfo *GetUnwindInfo() const { return m_unwind_info; } | |
| const UnwindCode *GetUnwindCode() const { return m_unwind_code; } | |
| bool IsChained() const { return m_chained; } | |
| private: | |
| ObjectFilePECOFF &m_object_file; | |
| bool m_error = false; | |
| uint32_t m_unwind_info_rva; | |
| DataExtractor m_unwind_info_data; | |
| const UnwindInfo *m_unwind_info = nullptr; | |
| DataExtractor m_unwind_code_data; | |
| offset_t m_unwind_code_offset; | |
| const UnwindCode *m_unwind_code = nullptr; | |
| bool m_chained = false; | |
| }; | |
| UnwindCodesIterator::UnwindCodesIterator(ObjectFilePECOFF &object_file, | |
| uint32_t unwind_info_rva) | |
| : m_object_file(object_file), | |
| m_unwind_info_rva(unwind_info_rva), m_unwind_code_offset{} {} | |
| bool UnwindCodesIterator::GetNext() { | |
| static constexpr int UNWIND_INFO_SIZE = 4; | |
| m_error = false; | |
| m_unwind_code = nullptr; | |
| while (!m_unwind_code) { | |
| if (!m_unwind_info) { | |
| m_unwind_info_data = | |
| m_object_file.ReadImageDataByRVA(m_unwind_info_rva, UNWIND_INFO_SIZE); | |
| offset_t offset = 0; | |
| m_unwind_info = | |
| TypedRead<UnwindInfo>(m_unwind_info_data, offset, UNWIND_INFO_SIZE); | |
| if (!m_unwind_info) { | |
| m_error = true; | |
| break; | |
| } | |
| m_unwind_code_data = m_object_file.ReadImageDataByRVA( | |
| m_unwind_info_rva + UNWIND_INFO_SIZE, | |
| m_unwind_info->NumCodes * sizeof(UnwindCode)); | |
| m_unwind_code_offset = 0; | |
| } | |
| if (m_unwind_code_offset < m_unwind_code_data.GetByteSize()) { | |
| m_unwind_code = | |
| TypedRead<UnwindCode>(m_unwind_code_data, m_unwind_code_offset); | |
| m_error = !m_unwind_code; | |
| break; | |
| } | |
| if (!(m_unwind_info->getFlags() & UNW_ChainInfo)) | |
| break; | |
| uint32_t runtime_function_rva = | |
| m_unwind_info_rva + UNWIND_INFO_SIZE + | |
| ((m_unwind_info->NumCodes + 1) & ~1) * sizeof(UnwindCode); | |
| DataExtractor runtime_function_data = m_object_file.ReadImageDataByRVA( | |
| runtime_function_rva, sizeof(RuntimeFunction)); | |
| offset_t offset = 0; | |
| const auto *runtime_function = | |
| TypedRead<RuntimeFunction>(runtime_function_data, offset); | |
| if (!runtime_function) { | |
| m_error = true; | |
| break; | |
| } | |
| m_unwind_info_rva = runtime_function->UnwindInfoOffset; | |
| m_unwind_info = nullptr; | |
| m_chained = true; | |
| } | |
| return !!m_unwind_code; | |
| } | |
| class EHProgramBuilder { | |
| public: | |
| EHProgramBuilder(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva); | |
| bool Build(); | |
| const EHProgram &GetProgram() const { return m_program; } | |
| private: | |
| static uint32_t ConvertMachineToLLDBRegister(uint8_t machine_reg); | |
| static uint32_t ConvertXMMToLLDBRegister(uint8_t xmm_reg); | |
| bool ProcessUnwindCode(UnwindCode code); | |
| void Finalize(); | |
| bool ParseBigOrScaledFrameOffset(uint32_t &result, bool big, uint32_t scale); | |
| bool ParseBigFrameOffset(uint32_t &result); | |
| bool ParseFrameOffset(uint32_t &result); | |
| UnwindCodesIterator m_iterator; | |
| EHProgram m_program; | |
| }; | |
| EHProgramBuilder::EHProgramBuilder(ObjectFilePECOFF &object_file, | |
| uint32_t unwind_info_rva) | |
| : m_iterator(object_file, unwind_info_rva) {} | |
| bool EHProgramBuilder::Build() { | |
| while (m_iterator.GetNext()) | |
| if (!ProcessUnwindCode(*m_iterator.GetUnwindCode())) | |
| return false; | |
| if (m_iterator.IsError()) | |
| return false; | |
| Finalize(); | |
| return true; | |
| } | |
| uint32_t EHProgramBuilder::ConvertMachineToLLDBRegister(uint8_t machine_reg) { | |
| static uint32_t machine_to_lldb_register[] = { | |
| lldb_rax_x86_64, lldb_rcx_x86_64, lldb_rdx_x86_64, lldb_rbx_x86_64, | |
| lldb_rsp_x86_64, lldb_rbp_x86_64, lldb_rsi_x86_64, lldb_rdi_x86_64, | |
| lldb_r8_x86_64, lldb_r9_x86_64, lldb_r10_x86_64, lldb_r11_x86_64, | |
| lldb_r12_x86_64, lldb_r13_x86_64, lldb_r14_x86_64, lldb_r15_x86_64}; | |
| if (machine_reg >= std::size(machine_to_lldb_register)) | |
| return LLDB_INVALID_REGNUM; | |
| return machine_to_lldb_register[machine_reg]; | |
| } | |
| uint32_t EHProgramBuilder::ConvertXMMToLLDBRegister(uint8_t xmm_reg) { | |
| static uint32_t xmm_to_lldb_register[] = { | |
| lldb_xmm0_x86_64, lldb_xmm1_x86_64, lldb_xmm2_x86_64, | |
| lldb_xmm3_x86_64, lldb_xmm4_x86_64, lldb_xmm5_x86_64, | |
| lldb_xmm6_x86_64, lldb_xmm7_x86_64, lldb_xmm8_x86_64, | |
| lldb_xmm9_x86_64, lldb_xmm10_x86_64, lldb_xmm11_x86_64, | |
| lldb_xmm12_x86_64, lldb_xmm13_x86_64, lldb_xmm14_x86_64, | |
| lldb_xmm15_x86_64}; | |
| if (xmm_reg >= std::size(xmm_to_lldb_register)) | |
| return LLDB_INVALID_REGNUM; | |
| return xmm_to_lldb_register[xmm_reg]; | |
| } | |
| bool EHProgramBuilder::ProcessUnwindCode(UnwindCode code) { | |
| uint8_t o = m_iterator.IsChained() ? 0 : code.u.CodeOffset; | |
| uint8_t unwind_operation = code.getUnwindOp(); | |
| uint8_t operation_info = code.getOpInfo(); | |
| switch (unwind_operation) { | |
| case UOP_PushNonVol: { | |
| uint32_t r = ConvertMachineToLLDBRegister(operation_info); | |
| if (r == LLDB_INVALID_REGNUM) | |
| return false; | |
| m_program.emplace_back( | |
| EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, r, 8}); | |
| return true; | |
| } | |
| case UOP_AllocLarge: { | |
| uint32_t fo; | |
| if (!ParseBigOrScaledFrameOffset(fo, operation_info, 8)) | |
| return false; | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE, | |
| LLDB_INVALID_REGNUM, fo}); | |
| return true; | |
| } | |
| case UOP_AllocSmall: { | |
| m_program.emplace_back( | |
| EHInstruction{o, EHInstruction::Type::ALLOCATE, LLDB_INVALID_REGNUM, | |
| static_cast<uint32_t>(operation_info) * 8 + 8}); | |
| return true; | |
| } | |
| case UOP_SetFPReg: { | |
| uint32_t fpr = LLDB_INVALID_REGNUM; | |
| if (m_iterator.GetUnwindInfo()->getFrameRegister()) | |
| fpr = ConvertMachineToLLDBRegister( | |
| m_iterator.GetUnwindInfo()->getFrameRegister()); | |
| if (fpr == LLDB_INVALID_REGNUM) | |
| return false; | |
| uint32_t fpro = | |
| static_cast<uint32_t>(m_iterator.GetUnwindInfo()->getFrameOffset()) * | |
| 16; | |
| m_program.emplace_back(EHInstruction{ | |
| o, EHInstruction::Type::SET_FRAME_POINTER_REGISTER, fpr, fpro}); | |
| return true; | |
| } | |
| case UOP_SaveNonVol: | |
| case UOP_SaveNonVolBig: { | |
| uint32_t r = ConvertMachineToLLDBRegister(operation_info); | |
| if (r == LLDB_INVALID_REGNUM) | |
| return false; | |
| uint32_t fo; | |
| if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveNonVolBig, | |
| 8)) | |
| return false; | |
| m_program.emplace_back( | |
| EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo}); | |
| return true; | |
| } | |
| case UOP_Epilog: { | |
| return m_iterator.GetNext(); | |
| } | |
| case UOP_SpareCode: { | |
| // ReSharper disable once CppIdenticalOperandsInBinaryExpression | |
| return m_iterator.GetNext() && m_iterator.GetNext(); | |
| } | |
| case UOP_SaveXMM128: | |
| case UOP_SaveXMM128Big: { | |
| uint32_t r = ConvertXMMToLLDBRegister(operation_info); | |
| if (r == LLDB_INVALID_REGNUM) | |
| return false; | |
| uint32_t fo; | |
| if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveXMM128Big, | |
| 16)) | |
| return false; | |
| m_program.emplace_back( | |
| EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo}); | |
| return true; | |
| } | |
| case UOP_PushMachFrame: { | |
| if (operation_info) | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE, | |
| LLDB_INVALID_REGNUM, 8}); | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, | |
| lldb_rip_x86_64, 8}); | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, | |
| lldb_cs_x86_64, 8}); | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, | |
| lldb_rflags_x86_64, 8}); | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, | |
| lldb_rsp_x86_64, 8}); | |
| m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, | |
| lldb_ss_x86_64, 8}); | |
| return true; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| void EHProgramBuilder::Finalize() { | |
| for (const EHInstruction &i : m_program) | |
| if (i.reg == lldb_rip_x86_64) | |
| return; | |
| m_program.emplace_back( | |
| EHInstruction{0, EHInstruction::Type::PUSH_REGISTER, lldb_rip_x86_64, 8}); | |
| } | |
| bool EHProgramBuilder::ParseBigOrScaledFrameOffset(uint32_t &result, bool big, | |
| uint32_t scale) { | |
| if (big) { | |
| if (!ParseBigFrameOffset(result)) | |
| return false; | |
| } else { | |
| if (!ParseFrameOffset(result)) | |
| return false; | |
| result *= scale; | |
| } | |
| return true; | |
| } | |
| bool EHProgramBuilder::ParseBigFrameOffset(uint32_t &result) { | |
| if (!m_iterator.GetNext()) | |
| return false; | |
| result = m_iterator.GetUnwindCode()->FrameOffset; | |
| if (!m_iterator.GetNext()) | |
| return false; | |
| result += static_cast<uint32_t>(m_iterator.GetUnwindCode()->FrameOffset) | |
| << 16; | |
| return true; | |
| } | |
| bool EHProgramBuilder::ParseFrameOffset(uint32_t &result) { | |
| if (!m_iterator.GetNext()) | |
| return false; | |
| result = m_iterator.GetUnwindCode()->FrameOffset; | |
| return true; | |
| } | |
| class EHProgramRange { | |
| public: | |
| EHProgramRange(EHProgram::const_iterator begin, | |
| EHProgram::const_iterator end); | |
| UnwindPlan::Row BuildUnwindPlanRow() const; | |
| private: | |
| int32_t GetCFAFrameOffset() const; | |
| EHProgram::const_iterator m_begin; | |
| EHProgram::const_iterator m_end; | |
| }; | |
| EHProgramRange::EHProgramRange(EHProgram::const_iterator begin, | |
| EHProgram::const_iterator end) | |
| : m_begin(begin), m_end(end) {} | |
| UnwindPlan::Row EHProgramRange::BuildUnwindPlanRow() const { | |
| UnwindPlan::Row row; | |
| if (m_begin != m_end) | |
| row.SetOffset(m_begin->offset); | |
| int32_t cfa_frame_offset = GetCFAFrameOffset(); | |
| bool frame_pointer_found = false; | |
| for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) { | |
| switch (it->type) { | |
| case EHInstruction::Type::SET_FRAME_POINTER_REGISTER: | |
| row.GetCFAValue().SetIsRegisterPlusOffset(it->reg, cfa_frame_offset - | |
| it->frame_offset); | |
| frame_pointer_found = true; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (frame_pointer_found) | |
| break; | |
| } | |
| if (!frame_pointer_found) | |
| row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, | |
| cfa_frame_offset); | |
| int32_t rsp_frame_offset = 0; | |
| for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) { | |
| switch (it->type) { | |
| case EHInstruction::Type::PUSH_REGISTER: | |
| row.SetRegisterLocationToAtCFAPlusOffset( | |
| it->reg, rsp_frame_offset - cfa_frame_offset, false); | |
| rsp_frame_offset += it->frame_offset; | |
| break; | |
| case EHInstruction::Type::ALLOCATE: | |
| rsp_frame_offset += it->frame_offset; | |
| break; | |
| case EHInstruction::Type::SAVE_REGISTER: | |
| row.SetRegisterLocationToAtCFAPlusOffset( | |
| it->reg, it->frame_offset - cfa_frame_offset, false); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, false); | |
| return row; | |
| } | |
| int32_t EHProgramRange::GetCFAFrameOffset() const { | |
| int32_t result = 0; | |
| for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) { | |
| switch (it->type) { | |
| case EHInstruction::Type::PUSH_REGISTER: | |
| case EHInstruction::Type::ALLOCATE: | |
| result += it->frame_offset; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| PECallFrameInfo::PECallFrameInfo(ObjectFilePECOFF &object_file, | |
| uint32_t exception_dir_rva, | |
| uint32_t exception_dir_size) | |
| : m_object_file(object_file), | |
| m_exception_dir(object_file.ReadImageDataByRVA(exception_dir_rva, | |
| exception_dir_size)) {} | |
| bool PECallFrameInfo::GetAddressRange(Address addr, AddressRange &range) { | |
| range.Clear(); | |
| const RuntimeFunction *runtime_function = | |
| FindRuntimeFunctionIntersectsWithRange(AddressRange(addr, 1)); | |
| if (!runtime_function) | |
| return false; | |
| range.GetBaseAddress() = | |
| m_object_file.GetAddress(runtime_function->StartAddress); | |
| range.SetByteSize(runtime_function->EndAddress - | |
| runtime_function->StartAddress); | |
| return true; | |
| } | |
| std::unique_ptr<UnwindPlan> PECallFrameInfo::GetUnwindPlan( | |
| llvm::ArrayRef<lldb_private::AddressRange> ranges, | |
| const lldb_private::Address &addr) { | |
| // Only continuous functions are supported. | |
| if (ranges.size() != 1) | |
| return nullptr; | |
| const AddressRange &range = ranges[0]; | |
| const RuntimeFunction *runtime_function = | |
| FindRuntimeFunctionIntersectsWithRange(range); | |
| if (!runtime_function) | |
| return nullptr; | |
| auto plan_up = std::make_unique<UnwindPlan>(eRegisterKindLLDB); | |
| plan_up->SetSourceName("PE EH info"); | |
| plan_up->SetSourcedFromCompiler(eLazyBoolYes); | |
| EHProgramBuilder builder(m_object_file, runtime_function->UnwindInfoOffset); | |
| if (!builder.Build()) | |
| return nullptr; | |
| std::vector<UnwindPlan::Row> rows; | |
| uint32_t last_offset = UINT32_MAX; | |
| for (auto it = builder.GetProgram().begin(); it != builder.GetProgram().end(); | |
| ++it) { | |
| if (it->offset == last_offset) | |
| continue; | |
| EHProgramRange program_range = | |
| EHProgramRange(it, builder.GetProgram().end()); | |
| rows.push_back(program_range.BuildUnwindPlanRow()); | |
| last_offset = it->offset; | |
| } | |
| for (auto it = rows.rbegin(); it != rows.rend(); ++it) | |
| plan_up->AppendRow(std::move(*it)); | |
| plan_up->SetPlanValidAddressRanges({AddressRange( | |
| m_object_file.GetAddress(runtime_function->StartAddress), | |
| runtime_function->EndAddress - runtime_function->StartAddress)}); | |
| plan_up->SetUnwindPlanValidAtAllInstructions(eLazyBoolNo); | |
| return plan_up; | |
| } | |
| const RuntimeFunction *PECallFrameInfo::FindRuntimeFunctionIntersectsWithRange( | |
| const AddressRange &range) const { | |
| uint32_t rva = m_object_file.GetRVA(range.GetBaseAddress()); | |
| addr_t size = range.GetByteSize(); | |
| uint32_t begin = 0; | |
| uint32_t end = m_exception_dir.GetByteSize() / sizeof(RuntimeFunction); | |
| while (begin < end) { | |
| uint32_t curr = (begin + end) / 2; | |
| offset_t offset = curr * sizeof(RuntimeFunction); | |
| const auto *runtime_function = | |
| TypedRead<RuntimeFunction>(m_exception_dir, offset); | |
| if (!runtime_function) | |
| break; | |
| if (runtime_function->StartAddress < rva + size && | |
| runtime_function->EndAddress > rva) | |
| return runtime_function; | |
| if (runtime_function->StartAddress >= rva + size) | |
| end = curr; | |
| if (runtime_function->EndAddress <= rva) | |
| begin = curr + 1; | |
| } | |
| return nullptr; | |
| } |