#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 static const T *TypedRead(const DataExtractor &data_extractor, offset_t &offset, offset_t size = sizeof(T)) { return static_cast(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; 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; uint32_t m_unwind_info_rva; DataExtractor m_unwind_info_data; const UnwindInfo *m_unwind_info; DataExtractor m_unwind_code_data; offset_t m_unwind_code_offset; const UnwindCode *m_unwind_code; bool m_chained; }; UnwindCodesIterator::UnwindCodesIterator(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva) : m_object_file(object_file), m_error(false), m_unwind_info_rva(unwind_info_rva), m_unwind_info(nullptr), m_unwind_code_offset{}, m_unwind_code(nullptr), m_chained(false) {} 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(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(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(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 >= llvm::array_lengthof(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 >= llvm::array_lengthof(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(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(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(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); std::unique_ptr 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) {} std::unique_ptr EHProgramRange::BuildUnwindPlanRow() const { std::unique_ptr row = std::make_unique(); 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; } bool PECallFrameInfo::GetUnwindPlan(const Address &addr, UnwindPlan &unwind_plan) { return GetUnwindPlan(AddressRange(addr, 1), unwind_plan); } bool PECallFrameInfo::GetUnwindPlan(const AddressRange &range, UnwindPlan &unwind_plan) { unwind_plan.Clear(); unwind_plan.SetSourceName("PE EH info"); unwind_plan.SetSourcedFromCompiler(eLazyBoolYes); unwind_plan.SetRegisterKind(eRegisterKindLLDB); const RuntimeFunction *runtime_function = FindRuntimeFunctionIntersectsWithRange(range); if (!runtime_function) return false; EHProgramBuilder builder(m_object_file, runtime_function->UnwindInfoOffset); if (!builder.Build()) return false; std::vector 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) unwind_plan.AppendRow(*it); unwind_plan.SetPlanValidAddressRange(AddressRange( m_object_file.GetAddress(runtime_function->StartAddress), runtime_function->EndAddress - runtime_function->StartAddress)); unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo); return true; } 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(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; }