diff --git a/lldb/include/lldb/Symbol/CallFrameInfo.h b/lldb/include/lldb/Symbol/CallFrameInfo.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Symbol/CallFrameInfo.h @@ -0,0 +1,28 @@ +//===-- CallFrameInfo.h -----------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_CallFrameInfo_h_ +#define liblldb_CallFrameInfo_h_ + +#include "lldb/Core/Address.h" + +namespace lldb_private { + +class CallFrameInfo { +public: + virtual ~CallFrameInfo() = default; + + virtual bool GetAddressRange(Address addr, AddressRange &range) = 0; + + virtual bool GetUnwindPlan(const Address &addr, UnwindPlan &unwind_plan) = 0; + virtual bool GetUnwindPlan(const AddressRange &range, UnwindPlan &unwind_plan) = 0; +}; + +} // namespace lldb_private + +#endif // liblldb_CallFrameInfo_h_ diff --git a/lldb/include/lldb/Symbol/FuncUnwinders.h b/lldb/include/lldb/Symbol/FuncUnwinders.h --- a/lldb/include/lldb/Symbol/FuncUnwinders.h +++ b/lldb/include/lldb/Symbol/FuncUnwinders.h @@ -76,6 +76,11 @@ lldb::UnwindPlanSP GetAssemblyUnwindPlan(Target &target, Thread &thread); + lldb::UnwindPlanSP GetObjectFileUnwindPlan(Target &target); + + lldb::UnwindPlanSP GetObjectFileAugmentedUnwindPlan(Target &target, + Thread &thread); + lldb::UnwindPlanSP GetEHFrameUnwindPlan(Target &target); lldb::UnwindPlanSP GetEHFrameAugmentedUnwindPlan(Target &target, @@ -113,10 +118,12 @@ std::recursive_mutex m_mutex; lldb::UnwindPlanSP m_unwind_plan_assembly_sp; + lldb::UnwindPlanSP m_unwind_plan_object_file_sp; lldb::UnwindPlanSP m_unwind_plan_eh_frame_sp; lldb::UnwindPlanSP m_unwind_plan_debug_frame_sp; // augmented by assembly inspection so it's valid everywhere + lldb::UnwindPlanSP m_unwind_plan_object_file_augmented_sp; lldb::UnwindPlanSP m_unwind_plan_eh_frame_augmented_sp; lldb::UnwindPlanSP m_unwind_plan_debug_frame_augmented_sp; @@ -130,7 +137,9 @@ // Fetching the UnwindPlans can be expensive - if we've already attempted to // get one & failed, don't try again. bool m_tried_unwind_plan_assembly : 1, m_tried_unwind_plan_eh_frame : 1, + m_tried_unwind_plan_object_file : 1, m_tried_unwind_plan_debug_frame : 1, + m_tried_unwind_plan_object_file_augmented : 1, m_tried_unwind_plan_eh_frame_augmented : 1, m_tried_unwind_plan_debug_frame_augmented : 1, m_tried_unwind_plan_compact_unwind : 1, diff --git a/lldb/include/lldb/Symbol/ObjectFile.h b/lldb/include/lldb/Symbol/ObjectFile.h --- a/lldb/include/lldb/Symbol/ObjectFile.h +++ b/lldb/include/lldb/Symbol/ObjectFile.h @@ -656,6 +656,9 @@ /// \return virtual std::vector GetLoadableData(Target &target); + /// Creates a plugin-specific call frame info + virtual std::unique_ptr CreateCallFrameInfo(); + protected: // Member variables. FileSpec m_file; diff --git a/lldb/include/lldb/Symbol/UnwindTable.h b/lldb/include/lldb/Symbol/UnwindTable.h --- a/lldb/include/lldb/Symbol/UnwindTable.h +++ b/lldb/include/lldb/Symbol/UnwindTable.h @@ -27,6 +27,8 @@ ~UnwindTable(); + lldb_private::CallFrameInfo *GetObjectFileUnwindInfo(); + lldb_private::DWARFCallFrameInfo *GetEHFrameInfo(); lldb_private::DWARFCallFrameInfo *GetDebugFrameInfo(); @@ -71,6 +73,7 @@ bool m_initialized; // delay some initialization until ObjectFile is set up std::mutex m_mutex; + std::unique_ptr m_object_file_unwind_up; std::unique_ptr m_eh_frame_up; std::unique_ptr m_debug_frame_up; std::unique_ptr m_compact_unwind_up; diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -44,6 +44,7 @@ class BroadcastEventSpec; class Broadcaster; class BroadcasterManager; +class CallFrameInfo; class ClangASTContext; class ClangASTImporter; class ClangASTMetadata; diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -3445,6 +3445,25 @@ result.GetOutputStream().Printf("\n"); } + UnwindPlanSP of_unwind_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(*target); + if (of_unwind_sp) { + result.GetOutputStream().Printf("object file UnwindPlan:\n"); + of_unwind_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + + UnwindPlanSP of_unwind_augmented_sp = + func_unwinders_sp->GetObjectFileAugmentedUnwindPlan(*target, + *thread); + if (of_unwind_augmented_sp) { + result.GetOutputStream().Printf("object file augmented UnwindPlan:\n"); + of_unwind_augmented_sp->Dump(result.GetOutputStream(), thread.get(), + LLDB_INVALID_ADDRESS); + result.GetOutputStream().Printf("\n"); + } + UnwindPlanSP ehframe_sp = func_unwinders_sp->GetEHFrameUnwindPlan(*target); if (ehframe_sp) { diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/CMakeLists.txt b/lldb/source/Plugins/ObjectFile/PECOFF/CMakeLists.txt --- a/lldb/source/Plugins/ObjectFile/PECOFF/CMakeLists.txt +++ b/lldb/source/Plugins/ObjectFile/PECOFF/CMakeLists.txt @@ -7,6 +7,7 @@ add_lldb_library(lldbPluginObjectFilePECOFF PLUGIN ObjectFilePECOFF.cpp + PECallFrameInfo.cpp WindowsMiniDump.cpp LINK_LIBS diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h @@ -135,7 +135,14 @@ bool IsWindowsSubsystem(); + uint32_t GetRVA(const lldb_private::Address &addr) const; + lldb_private::Address GetAddress(uint32_t rva); + lldb::addr_t GetFileAddress(uint32_t rva) const; + lldb_private::DataExtractor ReadImageData(uint32_t offset, size_t size); + lldb_private::DataExtractor ReadImageDataByRVA(uint32_t rva, size_t size); + + std::unique_ptr CreateCallFrameInfo() override; protected: bool NeedsEndianSwap() const; @@ -216,6 +223,7 @@ enum coff_data_dir_type { coff_data_dir_export_table = 0, coff_data_dir_import_table = 1, + coff_data_dir_exception_table = 3 }; typedef struct section_header { diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ObjectFilePECOFF.h" +#include "PECallFrameInfo.h" #include "WindowsMiniDump.h" #include "lldb/Core/FileSpecList.h" @@ -518,7 +519,26 @@ return success; } +uint32_t ObjectFilePECOFF::GetRVA(const Address &addr) const { + return addr.GetFileAddress() - m_image_base; +} + +Address ObjectFilePECOFF::GetAddress(uint32_t rva) { + SectionList *sect_list = GetSectionList(); + if (!sect_list) + return Address(GetFileAddress(rva)); + + return Address(GetFileAddress(rva), sect_list); +} + +lldb::addr_t ObjectFilePECOFF::GetFileAddress(uint32_t rva) const { + return m_image_base + rva; +} + DataExtractor ObjectFilePECOFF::ReadImageData(uint32_t offset, size_t size) { + if (!size) + return {}; + if (m_file) { // A bit of a hack, but we intend to write to this buffer, so we can't // mmap it. @@ -541,6 +561,15 @@ return data; } +DataExtractor ObjectFilePECOFF::ReadImageDataByRVA(uint32_t rva, size_t size) { + if (m_file) { + Address addr = GetAddress(rva); + rva = addr.GetSection()->GetFileOffset() + addr.GetOffset(); + } + + return ReadImageData(rva, size); +} + // ParseSectionHeaders bool ObjectFilePECOFF::ParseSectionHeaders( uint32_t section_header_data_offset) { @@ -678,14 +707,8 @@ uint32_t data_start = m_coff_header_opt.data_dirs[coff_data_dir_export_table].vmaddr; - uint32_t address_rva = data_start; - if (m_file) { - Address address(m_coff_header_opt.image_base + data_start, sect_list); - address_rva = - address.GetSection()->GetFileOffset() + address.GetOffset(); - } - DataExtractor symtab_data = - ReadImageData(address_rva, m_coff_header_opt.data_dirs[0].vmsize); + DataExtractor symtab_data = ReadImageDataByRVA( + data_start, m_coff_header_opt.data_dirs[0].vmsize); lldb::offset_t offset = 0; // Read export_table header @@ -740,6 +763,19 @@ return m_symtab_up.get(); } +std::unique_ptr ObjectFilePECOFF::CreateCallFrameInfo() { + if (coff_data_dir_exception_table >= m_coff_header_opt.data_dirs.size()) + return {}; + + data_directory data_dir_exception = + m_coff_header_opt.data_dirs[coff_data_dir_exception_table]; + if (!data_dir_exception.vmaddr) + return {}; + + return std::make_unique(*this, data_dir_exception.vmaddr, + data_dir_exception.vmsize); +} + bool ObjectFilePECOFF::IsStripped() { // TODO: determine this for COFF return false; diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.h b/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.h @@ -0,0 +1,47 @@ +//===-- PECallFrameInfo.h ---------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_PECallFrameInfo_h_ +#define liblldb_PECallFrameInfo_h_ + +#include "lldb/Symbol/CallFrameInfo.h" +#include "lldb/Utility/DataExtractor.h" + +class ObjectFilePECOFF; + +namespace llvm { +namespace Win64EH { + +struct RuntimeFunction; + +} +} // namespace llvm + +class PECallFrameInfo : public virtual lldb_private::CallFrameInfo { +public: + explicit PECallFrameInfo(ObjectFilePECOFF &object_file, + uint32_t exception_dir_rva, + uint32_t exception_dir_size); + + bool GetAddressRange(lldb_private::Address addr, + lldb_private::AddressRange &range) override; + + bool GetUnwindPlan(const lldb_private::Address &addr, + lldb_private::UnwindPlan &unwind_plan) override; + bool GetUnwindPlan(const lldb_private::AddressRange &range, + lldb_private::UnwindPlan &unwind_plan) override; + +private: + const llvm::Win64EH::RuntimeFunction *FindRuntimeFunctionIntersectsWithRange( + const lldb_private::AddressRange &range) const; + + ObjectFilePECOFF &m_object_file; + lldb_private::DataExtractor m_exception_dir; +}; + +#endif // liblldb_PECallFrameInfo_h_ diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/PECOFF/PECallFrameInfo.cpp @@ -0,0 +1,535 @@ +#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; + 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; +} diff --git a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp --- a/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterContextLLDB.cpp @@ -12,6 +12,7 @@ #include "lldb/Core/Value.h" #include "lldb/Expression/DWARFExpression.h" #include "lldb/Symbol/ArmUnwindInfo.h" +#include "lldb/Symbol/CallFrameInfo.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" #include "lldb/Symbol/FuncUnwinders.h" #include "lldb/Symbol/Function.h" @@ -784,6 +785,16 @@ unwind_plan_sp.reset(); } + CallFrameInfo *object_file_unwind = + pc_module_sp->GetUnwindTable().GetObjectFileUnwindInfo(); + if (object_file_unwind) { + unwind_plan_sp = std::make_shared(lldb::eRegisterKindGeneric); + if (object_file_unwind->GetUnwindPlan(m_current_pc, *unwind_plan_sp)) + return unwind_plan_sp; + else + unwind_plan_sp.reset(); + } + return arch_default_unwind_plan_sp; } @@ -796,6 +807,9 @@ m_fast_unwind_plan_sp.reset(); unwind_plan_sp = func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget()); + if (!unwind_plan_sp) + unwind_plan_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(process->GetTarget()); if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress(m_current_pc) && unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolYes) { return unwind_plan_sp; @@ -818,6 +832,9 @@ // intend) or compact unwind (this won't work) unwind_plan_sp = func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget()); + if (!unwind_plan_sp) + unwind_plan_sp = + func_unwinders_sp->GetObjectFileUnwindPlan(process->GetTarget()); if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress(m_current_pc)) { UnwindLogMsgVerbose("frame uses %s for full UnwindPlan because the " "DynamicLoader suggested we prefer it", diff --git a/lldb/source/Symbol/FuncUnwinders.cpp b/lldb/source/Symbol/FuncUnwinders.cpp --- a/lldb/source/Symbol/FuncUnwinders.cpp +++ b/lldb/source/Symbol/FuncUnwinders.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Address.h" #include "lldb/Core/AddressRange.h" #include "lldb/Symbol/ArmUnwindInfo.h" +#include "lldb/Symbol/CallFrameInfo.h" #include "lldb/Symbol/CompactUnwindInfo.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" #include "lldb/Symbol/ObjectFile.h" @@ -58,6 +59,8 @@ Thread &thread) { std::lock_guard guard(m_mutex); + if (UnwindPlanSP plan_sp = GetObjectFileUnwindPlan(target)) + return plan_sp; if (UnwindPlanSP plan_sp = GetSymbolFileUnwindPlan(thread)) return plan_sp; if (UnwindPlanSP plan_sp = GetDebugFrameUnwindPlan(target)) @@ -97,6 +100,26 @@ return UnwindPlanSP(); } +lldb::UnwindPlanSP FuncUnwinders::GetObjectFileUnwindPlan(Target &target) { + std::lock_guard guard(m_mutex); + if (m_unwind_plan_object_file_sp.get() || + m_tried_unwind_plan_object_file) + return m_unwind_plan_object_file_sp; + + m_tried_unwind_plan_object_file = true; + if (m_range.GetBaseAddress().IsValid()) { + CallFrameInfo *object_file_frame = m_unwind_table.GetObjectFileUnwindInfo(); + if (object_file_frame) { + m_unwind_plan_object_file_sp = + std::make_shared(lldb::eRegisterKindGeneric); + if (!object_file_frame->GetUnwindPlan(m_range, + *m_unwind_plan_object_file_sp)) + m_unwind_plan_object_file_sp.reset(); + } + } + return m_unwind_plan_object_file_sp; +} + UnwindPlanSP FuncUnwinders::GetEHFrameUnwindPlan(Target &target) { std::lock_guard guard(m_mutex); if (m_unwind_plan_eh_frame_sp.get() || m_tried_unwind_plan_eh_frame) @@ -185,6 +208,38 @@ return m_unwind_plan_symbol_file_sp; } +UnwindPlanSP +FuncUnwinders::GetObjectFileAugmentedUnwindPlan(Target &target, + Thread &thread) { + std::lock_guard guard(m_mutex); + if (m_unwind_plan_object_file_augmented_sp.get() || + m_tried_unwind_plan_object_file_augmented) + return m_unwind_plan_object_file_augmented_sp; + + m_tried_unwind_plan_object_file_augmented = true; + + UnwindPlanSP object_file_unwind_plan = GetObjectFileUnwindPlan(target); + if (!object_file_unwind_plan) + return m_unwind_plan_object_file_augmented_sp; + + m_unwind_plan_object_file_augmented_sp = + std::make_shared(*object_file_unwind_plan); + + // Augment the instructions with epilogue descriptions if necessary + // so the UnwindPlan can be used at any instruction in the function. + + UnwindAssemblySP assembly_profiler_sp(GetUnwindAssemblyProfiler(target)); + if (assembly_profiler_sp) { + if (!assembly_profiler_sp->AugmentUnwindPlanFromCallSite( + m_range, thread, *m_unwind_plan_object_file_augmented_sp)) { + m_unwind_plan_object_file_augmented_sp.reset(); + } + } else { + m_unwind_plan_object_file_augmented_sp.reset(); + } + return m_unwind_plan_object_file_augmented_sp; +} + UnwindPlanSP FuncUnwinders::GetEHFrameAugmentedUnwindPlan(Target &target, Thread &thread) { std::lock_guard guard(m_mutex); @@ -328,6 +383,8 @@ UnwindPlanSP eh_frame_sp = GetEHFrameUnwindPlan(target); if (!eh_frame_sp) eh_frame_sp = GetDebugFrameUnwindPlan(target); + if (!eh_frame_sp) + eh_frame_sp = GetObjectFileUnwindPlan(target); UnwindPlanSP arch_default_at_entry_sp = GetUnwindPlanArchitectureDefaultAtFunctionEntry(thread); UnwindPlanSP arch_default_sp = GetUnwindPlanArchitectureDefault(thread); @@ -366,6 +423,8 @@ return plan_sp; if (UnwindPlanSP plan_sp = GetEHFrameAugmentedUnwindPlan(target, thread)) return plan_sp; + if (UnwindPlanSP plan_sp = GetObjectFileAugmentedUnwindPlan(target, thread)) + return plan_sp; return assembly_sp; } @@ -473,6 +532,9 @@ if (unwind_plan_sp.get() == nullptr) { unwind_plan_sp = GetCompactUnwindUnwindPlan(target); } + if (unwind_plan_sp.get() == nullptr) { + unwind_plan_sp = GetObjectFileUnwindPlan(target); + } if (unwind_plan_sp.get() && unwind_plan_sp->GetLSDAAddress().IsValid()) { lsda_addr = unwind_plan_sp->GetLSDAAddress(); } @@ -486,6 +548,9 @@ if (unwind_plan_sp.get() == nullptr) { unwind_plan_sp = GetCompactUnwindUnwindPlan(target); } + if (unwind_plan_sp.get() == nullptr) { + unwind_plan_sp = GetObjectFileUnwindPlan(target); + } if (unwind_plan_sp.get() && unwind_plan_sp->GetPersonalityFunctionPtr().IsValid()) { personality_addr = unwind_plan_sp->GetPersonalityFunctionPtr(); diff --git a/lldb/source/Symbol/ObjectFile.cpp b/lldb/source/Symbol/ObjectFile.cpp --- a/lldb/source/Symbol/ObjectFile.cpp +++ b/lldb/source/Symbol/ObjectFile.cpp @@ -11,6 +11,7 @@ #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" +#include "lldb/Symbol/CallFrameInfo.h" #include "lldb/Symbol/ObjectContainer.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Target/Process.h" @@ -670,6 +671,10 @@ return loadables; } +std::unique_ptr ObjectFile::CreateCallFrameInfo() { + return {}; +} + void ObjectFile::RelocateSection(lldb_private::Section *section) { } diff --git a/lldb/source/Symbol/UnwindTable.cpp b/lldb/source/Symbol/UnwindTable.cpp --- a/lldb/source/Symbol/UnwindTable.cpp +++ b/lldb/source/Symbol/UnwindTable.cpp @@ -13,6 +13,7 @@ #include "lldb/Core/Module.h" #include "lldb/Core/Section.h" #include "lldb/Symbol/ArmUnwindInfo.h" +#include "lldb/Symbol/CallFrameInfo.h" #include "lldb/Symbol/CompactUnwindInfo.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" #include "lldb/Symbol/FuncUnwinders.h" @@ -29,7 +30,8 @@ UnwindTable::UnwindTable(Module &module) : m_module(module), m_unwinds(), m_initialized(false), m_mutex(), - m_eh_frame_up(), m_compact_unwind_up(), m_arm_unwind_up() {} + m_object_file_unwind_up(), m_eh_frame_up(), m_compact_unwind_up(), + m_arm_unwind_up() {} // We can't do some of this initialization when the ObjectFile is running its // ctor; delay doing it until needed for something. @@ -47,6 +49,8 @@ if (!object_file) return; + m_object_file_unwind_up = object_file->CreateCallFrameInfo(); + SectionList *sl = m_module.GetSectionList(); if (!sl) return; @@ -83,7 +87,12 @@ SymbolContext &sc) { AddressRange range; - // First check the symbol context + // First check the unwind info from the object file plugin + if (m_object_file_unwind_up && + m_object_file_unwind_up->GetAddressRange(addr, range)) + return range; + + // Check the symbol context if (sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0, false, range) && range.GetBaseAddress().IsValid()) @@ -162,6 +171,11 @@ s.EOL(); } +lldb_private::CallFrameInfo *UnwindTable::GetObjectFileUnwindInfo() { + Initialize(); + return m_object_file_unwind_up.get(); +} + DWARFCallFrameInfo *UnwindTable::GetEHFrameInfo() { Initialize(); return m_eh_frame_up.get(); diff --git a/lldb/unittests/ObjectFile/CMakeLists.txt b/lldb/unittests/ObjectFile/CMakeLists.txt --- a/lldb/unittests/ObjectFile/CMakeLists.txt +++ b/lldb/unittests/ObjectFile/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(Breakpad) add_subdirectory(ELF) +add_subdirectory(PECOFF) diff --git a/lldb/unittests/ObjectFile/PECOFF/CMakeLists.txt b/lldb/unittests/ObjectFile/PECOFF/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/unittests/ObjectFile/PECOFF/CMakeLists.txt @@ -0,0 +1,8 @@ +add_lldb_unittest(ObjectFilePECOFFTests + TestPECallFrameInfo.cpp + + LINK_LIBS + lldbUtilityHelpers + lldbPluginObjectFilePECOFF + LLVMTestingSupport + ) diff --git a/lldb/unittests/ObjectFile/PECOFF/TestPECallFrameInfo.cpp b/lldb/unittests/ObjectFile/PECOFF/TestPECallFrameInfo.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/ObjectFile/PECOFF/TestPECallFrameInfo.cpp @@ -0,0 +1,336 @@ +//===-- TestPECallFrameInfo.cpp ------------------------------*- C++ -*-===// +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h" +#include "Plugins/Process/Utility/lldb-x86-register-enums.h" +#include "TestingSupport/TestUtilities.h" + +#include "lldb/Core/Module.h" +#include "lldb/Symbol/CallFrameInfo.h" +#include "lldb/Symbol/UnwindPlan.h" +#include "llvm/Testing/Support/Error.h" + +using namespace lldb_private; +using namespace lldb; + +class PECallFrameInfoTest : public testing::Test { +public: + void SetUp() override { + FileSystem::Initialize(); + ObjectFilePECOFF::Initialize(); + } + + void TearDown() override { + ObjectFilePECOFF::Terminate(); + FileSystem::Terminate(); + } + +protected: + void GetUnwindPlan(addr_t file_addr, UnwindPlan &plan) const; +}; + +void PECallFrameInfoTest::GetUnwindPlan(addr_t file_addr, UnwindPlan &plan) const { + llvm::Expected ExpectedFile = TestFile::fromYaml( + R"( +--- !COFF +OptionalHeader: + AddressOfEntryPoint: 0 + ImageBase: 16777216 + SectionAlignment: 4096 + FileAlignment: 512 + MajorOperatingSystemVersion: 6 + MinorOperatingSystemVersion: 0 + MajorImageVersion: 0 + MinorImageVersion: 0 + MajorSubsystemVersion: 6 + MinorSubsystemVersion: 0 + Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI + DLLCharacteristics: [ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA, IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLL_CHARACTERISTICS_NX_COMPAT ] + SizeOfStackReserve: 1048576 + SizeOfStackCommit: 4096 + SizeOfHeapReserve: 1048576 + SizeOfHeapCommit: 4096 + ExportTable: + RelativeVirtualAddress: 0 + Size: 0 + ImportTable: + RelativeVirtualAddress: 0 + Size: 0 + ResourceTable: + RelativeVirtualAddress: 0 + Size: 0 + ExceptionTable: + RelativeVirtualAddress: 12288 + Size: 60 + CertificateTable: + RelativeVirtualAddress: 0 + Size: 0 + BaseRelocationTable: + RelativeVirtualAddress: 0 + Size: 0 + Debug: + RelativeVirtualAddress: 0 + Size: 0 + Architecture: + RelativeVirtualAddress: 0 + Size: 0 + GlobalPtr: + RelativeVirtualAddress: 0 + Size: 0 + TlsTable: + RelativeVirtualAddress: 0 + Size: 0 + LoadConfigTable: + RelativeVirtualAddress: 0 + Size: 0 + BoundImport: + RelativeVirtualAddress: 0 + Size: 0 + IAT: + RelativeVirtualAddress: 0 + Size: 0 + DelayImportDescriptor: + RelativeVirtualAddress: 0 + Size: 0 + ClrRuntimeHeader: + RelativeVirtualAddress: 0 + Size: 0 +header: + Machine: IMAGE_FILE_MACHINE_AMD64 + Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ] +sections: + - Name: .text + Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ] + VirtualAddress: 4096 + VirtualSize: 4096 + - Name: .rdata + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ] + VirtualAddress: 8192 + VirtualSize: 68 + SectionData: 010C06000C3208F006E00470036002302105020005540D0000100000001100000020000019400E352F74670028646600213465001A3315015E000EF00CE00AD008C00650 + + +# Unwind info at 0x2000: +# 01 0C 06 00 No chained info, prolog size = 0xC, unwind codes size is 6 words, no frame register +# 0C 32 UOP_AllocSmall(2) 3 * 8 + 8 bytes, offset in prolog is 0xC +# 08 F0 UOP_PushNonVol(0) R15(0xF), offset in prolog is 8 +# 06 E0 UOP_PushNonVol(0) R14(0xE), offset in prolog is 6 +# 04 70 UOP_PushNonVol(0) RDI(7), offset in prolog is 4 +# 03 60 UOP_PushNonVol(0) RSI(6), offset in prolog is 3 +# 02 30 UOP_PushNonVol(0) RBX(3), offset in prolog is 2 +# Corresponding prolog: +# 00 push rbx +# 02 push rsi +# 03 push rdi +# 04 push r14 +# 06 push r15 +# 08 sub rsp, 20h + +# Unwind info at 0x2010: +# 21 05 02 00 Has chained info, prolog size = 5, unwind codes size is 2 words, no frame register +# 05 54 0D 00 UOP_SaveNonVol(4) RBP(5) to RSP + 0xD * 8, offset in prolog is 5 +# Chained runtime function: +# 00 10 00 00 Start address is 0x1000 +# 00 11 00 00 End address is 0x1100 +# 00 20 00 00 Unwind info RVA is 0x2000 +# Corresponding prolog: +# 00 mov [rsp+68h], rbp + +# Unwind info at 0x2024: +# 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 +# 2F 74 67 00 UOP_SaveNonVol(4) RDI(7) to RSP + 0x67 * 8, offset in prolog is 0x2F +# 28 64 66 00 UOP_SaveNonVol(4) RSI(6) to RSP + 0x66 * 8, offset in prolog is 0x28 +# 21 34 65 00 UOP_SaveNonVol(4) RBX(3) to RSP + 0x65 * 8, offset in prolog is 0x21 +# 1A 33 UOP_SetFPReg(3), offset in prolog is 0x1A +# 15 01 5E 00 UOP_AllocLarge(1) 0x5E * 8 bytes, offset in prolog is 0x15 +# 0E F0 UOP_PushNonVol(0) R15(0xF), offset in prolog is 0xE +# 0C E0 UOP_PushNonVol(0) R14(0xE), offset in prolog is 0xC +# 0A D0 UOP_PushNonVol(0) R13(0xD), offset in prolog is 0xA +# 08 C0 UOP_PushNonVol(0) R12(0xC), offset in prolog is 8 +# 06 50 UOP_PushNonVol(0) RBP(5), offset in prolog is 6 +# Corresponding prolog: +# 00 mov [rsp+8], rcx +# 05 push rbp +# 06 push r12 +# 08 push r13 +# 0A push r14 +# 0C push r15 +# 0E sub rsp, 2F0h +# 15 lea rbp, [rsp+30h] +# 1A mov [rbp+2F8h], rbx +# 21 mov [rbp+300h], rsi +# 28 mov [rbp+308h], rdi + + - Name: .pdata + Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ] + VirtualAddress: 12288 + VirtualSize: 60 + SectionData: 000000000000000000000000000000000000000000000000001000000011000000200000001100000012000010200000001200000013000024200000 + +# 00 00 00 00 +# 00 00 00 00 Test correct processing of empty runtime functions at begin +# 00 00 00 00 + +# 00 00 00 00 +# 00 00 00 00 Test correct processing of empty runtime functions at begin +# 00 00 00 00 + +# 00 10 00 00 Start address is 0x1000 +# 00 11 00 00 End address is 0x1100 +# 00 20 00 00 Unwind info RVA is 0x2000 + +# 00 11 00 00 Start address is 0x1100 +# 00 12 00 00 End address is 0x1200 +# 10 20 00 00 Unwind info RVA is 0x2010 + +# 00 12 00 00 Start address is 0x1200 +# 00 13 00 00 End address is 0x1300 +# 24 20 00 00 Unwind info RVA is 0x2024 + +symbols: [] +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + ModuleSP module_sp = std::make_shared(ModuleSpec(FileSpec(ExpectedFile->name()))); + ObjectFile *object_file = module_sp->GetObjectFile(); + ASSERT_NE(object_file, nullptr); + + std::unique_ptr cfi = object_file->CreateCallFrameInfo(); + ASSERT_NE(cfi.get(), nullptr); + + SectionList *sect_list = object_file->GetSectionList(); + ASSERT_NE(sect_list, nullptr); + + EXPECT_TRUE(cfi->GetUnwindPlan(Address(file_addr, sect_list), plan)); +} + +TEST_F(PECallFrameInfoTest, Basic_eh) { + UnwindPlan plan(eRegisterKindLLDB); + GetUnwindPlan(0x1001080, plan); + EXPECT_EQ(plan.GetRowCount(), 7); + + UnwindPlan::Row row; + row.SetOffset(0); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 8); + row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true); + EXPECT_EQ(*plan.GetRowAtIndex(0), row); + + row.SetOffset(2); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x10); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, -0x10, true); + EXPECT_EQ(*plan.GetRowAtIndex(1), row); + + row.SetOffset(3); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x18); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, -0x18, true); + EXPECT_EQ(*plan.GetRowAtIndex(2), row); + + row.SetOffset(4); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x20); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, -0x20, true); + EXPECT_EQ(*plan.GetRowAtIndex(3), row); + + row.SetOffset(6); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x28); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true); + EXPECT_EQ(*plan.GetRowAtIndex(4), row); + + row.SetOffset(8); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x30); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true); + EXPECT_EQ(*plan.GetRowAtIndex(5), row); + + row.SetOffset(0xC); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x50); + EXPECT_EQ(*plan.GetRowAtIndex(6), row); +} + +TEST_F(PECallFrameInfoTest, Chained_eh) { + UnwindPlan plan(eRegisterKindLLDB); + GetUnwindPlan(0x1001180, plan); + EXPECT_EQ(plan.GetRowCount(), 2); + + UnwindPlan::Row row; + row.SetOffset(0); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x50); + row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, -0x10, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, -0x18, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, -0x20, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true); + EXPECT_EQ(*plan.GetRowAtIndex(0), row); + + row.SetOffset(5); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbp_x86_64, 0x18, true); + EXPECT_EQ(*plan.GetRowAtIndex(1), row); +} + +TEST_F(PECallFrameInfoTest, Frame_reg_eh) { + UnwindPlan plan(eRegisterKindLLDB); + GetUnwindPlan(0x1001280, plan); + EXPECT_EQ(plan.GetRowCount(), 11); + + UnwindPlan::Row row; + row.SetOffset(0); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 8); + row.SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, true); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rip_x86_64, -8, true); + EXPECT_EQ(*plan.GetRowAtIndex(0), row); + + row.SetOffset(6); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x10); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbp_x86_64, -0x10, true); + EXPECT_EQ(*plan.GetRowAtIndex(1), row); + + row.SetOffset(8); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x18); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r12_x86_64, -0x18, true); + EXPECT_EQ(*plan.GetRowAtIndex(2), row); + + row.SetOffset(0xA); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x20); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r13_x86_64, -0x20, true); + EXPECT_EQ(*plan.GetRowAtIndex(3), row); + + row.SetOffset(0xC); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x28); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r14_x86_64, -0x28, true); + EXPECT_EQ(*plan.GetRowAtIndex(4), row); + + row.SetOffset(0xE); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x30); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_r15_x86_64, -0x30, true); + EXPECT_EQ(*plan.GetRowAtIndex(5), row); + + row.SetOffset(0x15); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64, 0x320); + EXPECT_EQ(*plan.GetRowAtIndex(6), row); + + row.SetOffset(0x1A); + row.GetCFAValue().SetIsRegisterPlusOffset(lldb_rbp_x86_64, 0x2F0); + EXPECT_EQ(*plan.GetRowAtIndex(7), row); + + row.SetOffset(0x21); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rbx_x86_64, 8, true); + EXPECT_EQ(*plan.GetRowAtIndex(8), row); + + row.SetOffset(0x28); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rsi_x86_64, 0x10, true); + EXPECT_EQ(*plan.GetRowAtIndex(9), row); + + row.SetOffset(0x2F); + row.SetRegisterLocationToAtCFAPlusOffset(lldb_rdi_x86_64, 0x18, true); + EXPECT_EQ(*plan.GetRowAtIndex(10), row); +} diff --git a/llvm/include/llvm/Support/Win64EH.h b/llvm/include/llvm/Support/Win64EH.h --- a/llvm/include/llvm/Support/Win64EH.h +++ b/llvm/include/llvm/Support/Win64EH.h @@ -30,7 +30,9 @@ UOP_SetFPReg, UOP_SaveNonVol, UOP_SaveNonVolBig, - UOP_SaveXMM128 = 8, + UOP_Epilog, + UOP_SpareCode, + UOP_SaveXMM128, UOP_SaveXMM128Big, UOP_PushMachFrame, // The following set of unwind opcodes is for ARM64. They are documented at