| //===-- DWARFCallFrameInfo.cpp --------------------------------------------===// | |
| // | |
| // 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 "lldb/Symbol/DWARFCallFrameInfo.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/Section.h" | |
| #include "lldb/Core/dwarf.h" | |
| #include "lldb/Host/Host.h" | |
| #include "lldb/Symbol/ObjectFile.h" | |
| #include "lldb/Symbol/UnwindPlan.h" | |
| #include "lldb/Target/RegisterContext.h" | |
| #include "lldb/Target/Thread.h" | |
| #include "lldb/Utility/ArchSpec.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/Timer.h" | |
| #include "llvm/BinaryFormat/Dwarf.h" | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <list> | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace llvm::dwarf; | |
| // GetDwarfEHPtr | |
| // | |
| // Used for calls when the value type is specified by a DWARF EH Frame pointer | |
| // encoding. | |
| static uint64_t | |
| GetGNUEHPointer(const DataExtractor &DE, lldb::offset_t *offset_ptr, | |
| uint32_t eh_ptr_enc, addr_t pc_rel_addr, addr_t text_addr, | |
| addr_t data_addr) //, BSDRelocs *data_relocs) const | |
| { | |
| if (eh_ptr_enc == DW_EH_PE_omit) | |
| return ULLONG_MAX; // Value isn't in the buffer... | |
| uint64_t baseAddress = 0; | |
| uint64_t addressValue = 0; | |
| const uint32_t addr_size = DE.GetAddressByteSize(); | |
| assert(addr_size == 4 || addr_size == 8); | |
| bool signExtendValue = false; | |
| // Decode the base part or adjust our offset | |
| switch (eh_ptr_enc & 0x70) { | |
| case DW_EH_PE_pcrel: | |
| signExtendValue = true; | |
| baseAddress = *offset_ptr; | |
| if (pc_rel_addr != LLDB_INVALID_ADDRESS) | |
| baseAddress += pc_rel_addr; | |
| // else | |
| // Log::GlobalWarning ("PC relative pointer encoding found with | |
| // invalid pc relative address."); | |
| break; | |
| case DW_EH_PE_textrel: | |
| signExtendValue = true; | |
| if (text_addr != LLDB_INVALID_ADDRESS) | |
| baseAddress = text_addr; | |
| // else | |
| // Log::GlobalWarning ("text relative pointer encoding being | |
| // decoded with invalid text section address, setting base address | |
| // to zero."); | |
| break; | |
| case DW_EH_PE_datarel: | |
| signExtendValue = true; | |
| if (data_addr != LLDB_INVALID_ADDRESS) | |
| baseAddress = data_addr; | |
| // else | |
| // Log::GlobalWarning ("data relative pointer encoding being | |
| // decoded with invalid data section address, setting base address | |
| // to zero."); | |
| break; | |
| case DW_EH_PE_funcrel: | |
| signExtendValue = true; | |
| break; | |
| case DW_EH_PE_aligned: { | |
| // SetPointerSize should be called prior to extracting these so the pointer | |
| // size is cached | |
| assert(addr_size != 0); | |
| if (addr_size) { | |
| // Align to a address size boundary first | |
| uint32_t alignOffset = *offset_ptr % addr_size; | |
| if (alignOffset) | |
| offset_ptr += addr_size - alignOffset; | |
| } | |
| } break; | |
| default: | |
| break; | |
| } | |
| // Decode the value part | |
| switch (eh_ptr_enc & DW_EH_PE_MASK_ENCODING) { | |
| case DW_EH_PE_absptr: { | |
| addressValue = DE.GetAddress(offset_ptr); | |
| // if (data_relocs) | |
| // addressValue = data_relocs->Relocate(*offset_ptr - | |
| // addr_size, *this, addressValue); | |
| } break; | |
| case DW_EH_PE_uleb128: | |
| addressValue = DE.GetULEB128(offset_ptr); | |
| break; | |
| case DW_EH_PE_udata2: | |
| addressValue = DE.GetU16(offset_ptr); | |
| break; | |
| case DW_EH_PE_udata4: | |
| addressValue = DE.GetU32(offset_ptr); | |
| break; | |
| case DW_EH_PE_udata8: | |
| addressValue = DE.GetU64(offset_ptr); | |
| break; | |
| case DW_EH_PE_sleb128: | |
| addressValue = DE.GetSLEB128(offset_ptr); | |
| break; | |
| case DW_EH_PE_sdata2: | |
| addressValue = (int16_t)DE.GetU16(offset_ptr); | |
| break; | |
| case DW_EH_PE_sdata4: | |
| addressValue = (int32_t)DE.GetU32(offset_ptr); | |
| break; | |
| case DW_EH_PE_sdata8: | |
| addressValue = (int64_t)DE.GetU64(offset_ptr); | |
| break; | |
| default: | |
| // Unhandled encoding type | |
| assert(eh_ptr_enc); | |
| break; | |
| } | |
| // Since we promote everything to 64 bit, we may need to sign extend | |
| if (signExtendValue && addr_size < sizeof(baseAddress)) { | |
| uint64_t sign_bit = 1ull << ((addr_size * 8ull) - 1ull); | |
| if (sign_bit & addressValue) { | |
| uint64_t mask = ~sign_bit + 1; | |
| addressValue |= mask; | |
| } | |
| } | |
| return baseAddress + addressValue; | |
| } | |
| // Check if the given cie_id value indicates a CIE (Common Information Entry) | |
| // as opposed to an FDE (Frame Description Entry). | |
| static bool IsCIEMarker(uint64_t cie_id, bool is_64bit, | |
| DWARFCallFrameInfo::Type type) { | |
| // Check eh_frame CIE marker | |
| if (type == DWARFCallFrameInfo::EH) | |
| return cie_id == 0; | |
| // Check debug_frame CIE marker | |
| // DWARF64 | |
| if (is_64bit) | |
| return cie_id == llvm::dwarf::DW64_CIE_ID; | |
| // DWARF32 | |
| return cie_id == llvm::dwarf::DW_CIE_ID; | |
| } | |
| DWARFCallFrameInfo::DWARFCallFrameInfo(ObjectFile &objfile, | |
| SectionSP §ion_sp, Type type) | |
| : m_objfile(objfile), m_section_sp(section_sp), m_type(type) {} | |
| std::unique_ptr<UnwindPlan> | |
| DWARFCallFrameInfo::GetUnwindPlan(const Address &addr) { | |
| return GetUnwindPlan({AddressRange(addr, 1)}, addr); | |
| } | |
| std::unique_ptr<UnwindPlan> | |
| DWARFCallFrameInfo::GetUnwindPlan(llvm::ArrayRef<AddressRange> ranges, | |
| const Address &addr) { | |
| FDEEntryMap::Entry fde_entry; | |
| // Make sure that the Address we're searching for is the same object file as | |
| // this DWARFCallFrameInfo, we only store File offsets in m_fde_index. | |
| ModuleSP module_sp = addr.GetModule(); | |
| if (module_sp.get() == nullptr || module_sp->GetObjectFile() == nullptr || | |
| module_sp->GetObjectFile() != &m_objfile) | |
| return nullptr; | |
| std::vector<AddressRange> valid_ranges; | |
| auto result = std::make_unique<UnwindPlan>(GetRegisterKind()); | |
| result->SetSourceName(m_type == EH ? "eh_frame CFI" : "DWARF CFI"); | |
| // In theory the debug_frame info should be valid at all call sites | |
| // ("asynchronous unwind info" as it is sometimes called) but in practice | |
| // gcc et al all emit call frame info for the prologue and call sites, but | |
| // not for the epilogue or all the other locations during the function | |
| // reliably. | |
| result->SetUnwindPlanValidAtAllInstructions(eLazyBoolNo); | |
| result->SetSourcedFromCompiler(eLazyBoolYes); | |
| result->SetUnwindPlanForSignalTrap(eLazyBoolNo); | |
| for (const AddressRange &range : ranges) { | |
| std::optional<FDEEntryMap::Entry> entry = GetFirstFDEEntryInRange(range); | |
| if (!entry) | |
| continue; | |
| std::optional<FDE> fde = ParseFDE(entry->data, addr); | |
| if (!fde) | |
| continue; | |
| int64_t slide = | |
| fde->range.GetBaseAddress().GetFileAddress() - addr.GetFileAddress(); | |
| valid_ranges.push_back(std::move(fde->range)); | |
| if (fde->for_signal_trap) | |
| result->SetUnwindPlanForSignalTrap(eLazyBoolYes); | |
| result->SetReturnAddressRegister(fde->return_addr_reg_num); | |
| for (UnwindPlan::Row &row : fde->rows) { | |
| row.SlideOffset(slide); | |
| result->AppendRow(std::move(row)); | |
| } | |
| } | |
| result->SetPlanValidAddressRanges(std::move(valid_ranges)); | |
| if (result->GetRowCount() == 0) | |
| return nullptr; | |
| return result; | |
| } | |
| bool DWARFCallFrameInfo::GetAddressRange(Address addr, AddressRange &range) { | |
| // Make sure that the Address we're searching for is the same object file as | |
| // this DWARFCallFrameInfo, we only store File offsets in m_fde_index. | |
| ModuleSP module_sp = addr.GetModule(); | |
| if (module_sp.get() == nullptr || module_sp->GetObjectFile() == nullptr || | |
| module_sp->GetObjectFile() != &m_objfile) | |
| return false; | |
| if (m_section_sp.get() == nullptr || m_section_sp->IsEncrypted()) | |
| return false; | |
| GetFDEIndex(); | |
| FDEEntryMap::Entry *fde_entry = | |
| m_fde_index.FindEntryThatContains(addr.GetFileAddress()); | |
| if (!fde_entry) | |
| return false; | |
| range = AddressRange(fde_entry->base, fde_entry->size, | |
| m_objfile.GetSectionList()); | |
| return true; | |
| } | |
| std::optional<DWARFCallFrameInfo::FDEEntryMap::Entry> | |
| DWARFCallFrameInfo::GetFirstFDEEntryInRange(const AddressRange &range) { | |
| if (!m_section_sp || m_section_sp->IsEncrypted()) | |
| return std::nullopt; | |
| GetFDEIndex(); | |
| addr_t start_file_addr = range.GetBaseAddress().GetFileAddress(); | |
| const FDEEntryMap::Entry *fde = | |
| m_fde_index.FindEntryThatContainsOrFollows(start_file_addr); | |
| if (fde && fde->DoesIntersect( | |
| FDEEntryMap::Range(start_file_addr, range.GetByteSize()))) | |
| return *fde; | |
| return std::nullopt; | |
| } | |
| void DWARFCallFrameInfo::GetFunctionAddressAndSizeVector( | |
| FunctionAddressAndSizeVector &function_info) { | |
| GetFDEIndex(); | |
| const size_t count = m_fde_index.GetSize(); | |
| function_info.Clear(); | |
| if (count > 0) | |
| function_info.Reserve(count); | |
| for (size_t i = 0; i < count; ++i) { | |
| const FDEEntryMap::Entry *func_offset_data_entry = | |
| m_fde_index.GetEntryAtIndex(i); | |
| if (func_offset_data_entry) { | |
| FunctionAddressAndSizeVector::Entry function_offset_entry( | |
| func_offset_data_entry->base, func_offset_data_entry->size); | |
| function_info.Append(function_offset_entry); | |
| } | |
| } | |
| } | |
| const DWARFCallFrameInfo::CIE * | |
| DWARFCallFrameInfo::GetCIE(dw_offset_t cie_offset) { | |
| cie_map_t::iterator pos = m_cie_map.find(cie_offset); | |
| if (pos != m_cie_map.end()) { | |
| // Parse and cache the CIE | |
| if (pos->second == nullptr) | |
| pos->second = ParseCIE(cie_offset); | |
| return pos->second.get(); | |
| } | |
| return nullptr; | |
| } | |
| DWARFCallFrameInfo::CIESP | |
| DWARFCallFrameInfo::ParseCIE(const dw_offset_t cie_offset) { | |
| CIESP cie_sp(new CIE(cie_offset)); | |
| lldb::offset_t offset = cie_offset; | |
| if (!m_cfi_data_initialized) | |
| GetCFIData(); | |
| uint32_t length = m_cfi_data.GetU32(&offset); | |
| dw_offset_t cie_id, end_offset; | |
| bool is_64bit = (length == llvm::dwarf::DW_LENGTH_DWARF64); | |
| if (is_64bit) { | |
| length = m_cfi_data.GetU64(&offset); | |
| cie_id = m_cfi_data.GetU64(&offset); | |
| end_offset = cie_offset + length + 12; | |
| } else { | |
| cie_id = m_cfi_data.GetU32(&offset); | |
| end_offset = cie_offset + length + 4; | |
| } | |
| // Check if this is a CIE or FDE based on the CIE ID marker | |
| if (length > 0 && IsCIEMarker(cie_id, is_64bit, m_type)) { | |
| size_t i; | |
| // cie.offset = cie_offset; | |
| // cie.length = length; | |
| // cie.cieID = cieID; | |
| cie_sp->ptr_encoding = DW_EH_PE_absptr; // default | |
| cie_sp->version = m_cfi_data.GetU8(&offset); | |
| if (cie_sp->version > CFI_VERSION4) { | |
| Debugger::ReportError( | |
| llvm::formatv("CIE parse error: CFI version {0} is not supported", | |
| cie_sp->version)); | |
| return nullptr; | |
| } | |
| for (i = 0; i < CFI_AUG_MAX_SIZE; ++i) { | |
| cie_sp->augmentation[i] = m_cfi_data.GetU8(&offset); | |
| if (cie_sp->augmentation[i] == '\0') { | |
| // Zero out remaining bytes in augmentation string | |
| for (size_t j = i + 1; j < CFI_AUG_MAX_SIZE; ++j) | |
| cie_sp->augmentation[j] = '\0'; | |
| break; | |
| } | |
| } | |
| if (i == CFI_AUG_MAX_SIZE && | |
| cie_sp->augmentation[CFI_AUG_MAX_SIZE - 1] != '\0') { | |
| Debugger::ReportError(llvm::formatv( | |
| "CIE parse error: CIE augmentation string was too large " | |
| "for the fixed sized buffer of {0} bytes.", | |
| CFI_AUG_MAX_SIZE)); | |
| return nullptr; | |
| } | |
| // m_cfi_data uses address size from target architecture of the process may | |
| // ignore these fields? | |
| if (m_type == DWARF && cie_sp->version >= CFI_VERSION4) { | |
| cie_sp->address_size = m_cfi_data.GetU8(&offset); | |
| cie_sp->segment_size = m_cfi_data.GetU8(&offset); | |
| } | |
| cie_sp->code_align = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| cie_sp->data_align = (int32_t)m_cfi_data.GetSLEB128(&offset); | |
| cie_sp->return_addr_reg_num = | |
| m_type == DWARF && cie_sp->version >= CFI_VERSION3 | |
| ? static_cast<uint32_t>(m_cfi_data.GetULEB128(&offset)) | |
| : m_cfi_data.GetU8(&offset); | |
| if (cie_sp->augmentation[0]) { | |
| // Get the length of the eh_frame augmentation data which starts with a | |
| // ULEB128 length in bytes | |
| const size_t aug_data_len = (size_t)m_cfi_data.GetULEB128(&offset); | |
| const size_t aug_data_end = offset + aug_data_len; | |
| const size_t aug_str_len = strlen(cie_sp->augmentation); | |
| // A 'z' may be present as the first character of the string. | |
| // If present, the Augmentation Data field shall be present. The contents | |
| // of the Augmentation Data shall be interpreted according to other | |
| // characters in the Augmentation String. | |
| if (cie_sp->augmentation[0] == 'z') { | |
| // Extract the Augmentation Data | |
| size_t aug_str_idx = 0; | |
| for (aug_str_idx = 1; aug_str_idx < aug_str_len; aug_str_idx++) { | |
| char aug = cie_sp->augmentation[aug_str_idx]; | |
| switch (aug) { | |
| case 'L': | |
| // Indicates the presence of one argument in the Augmentation Data | |
| // of the CIE, and a corresponding argument in the Augmentation | |
| // Data of the FDE. The argument in the Augmentation Data of the | |
| // CIE is 1-byte and represents the pointer encoding used for the | |
| // argument in the Augmentation Data of the FDE, which is the | |
| // address of a language-specific data area (LSDA). The size of the | |
| // LSDA pointer is specified by the pointer encoding used. | |
| cie_sp->lsda_addr_encoding = m_cfi_data.GetU8(&offset); | |
| break; | |
| case 'P': | |
| // Indicates the presence of two arguments in the Augmentation Data | |
| // of the CIE. The first argument is 1-byte and represents the | |
| // pointer encoding used for the second argument, which is the | |
| // address of a personality routine handler. The size of the | |
| // personality routine pointer is specified by the pointer encoding | |
| // used. | |
| // | |
| // The address of the personality function will be stored at this | |
| // location. Pre-execution, it will be all zero's so don't read it | |
| // until we're trying to do an unwind & the reloc has been | |
| // resolved. | |
| { | |
| uint8_t arg_ptr_encoding = m_cfi_data.GetU8(&offset); | |
| const lldb::addr_t pc_rel_addr = m_section_sp->GetFileAddress(); | |
| cie_sp->personality_loc = GetGNUEHPointer( | |
| m_cfi_data, &offset, arg_ptr_encoding, pc_rel_addr, | |
| LLDB_INVALID_ADDRESS, LLDB_INVALID_ADDRESS); | |
| } | |
| break; | |
| case 'R': | |
| // A 'R' may be present at any position after the | |
| // first character of the string. The Augmentation Data shall | |
| // include a 1 byte argument that represents the pointer encoding | |
| // for the address pointers used in the FDE. Example: 0x1B == | |
| // DW_EH_PE_pcrel | DW_EH_PE_sdata4 | |
| cie_sp->ptr_encoding = m_cfi_data.GetU8(&offset); | |
| break; | |
| } | |
| } | |
| } else if (strcmp(cie_sp->augmentation, "eh") == 0) { | |
| // If the Augmentation string has the value "eh", then the EH Data | |
| // field shall be present | |
| } | |
| // Set the offset to be the end of the augmentation data just in case we | |
| // didn't understand any of the data. | |
| offset = (uint32_t)aug_data_end; | |
| } | |
| if (end_offset > offset) { | |
| cie_sp->inst_offset = offset; | |
| cie_sp->inst_length = end_offset - offset; | |
| } | |
| while (offset < end_offset) { | |
| uint8_t inst = m_cfi_data.GetU8(&offset); | |
| uint8_t primary_opcode = inst & 0xC0; | |
| uint8_t extended_opcode = inst & 0x3F; | |
| if (!HandleCommonDwarfOpcode(primary_opcode, extended_opcode, | |
| cie_sp->data_align, offset, | |
| cie_sp->initial_row)) | |
| break; // Stop if we hit an unrecognized opcode | |
| } | |
| } | |
| return cie_sp; | |
| } | |
| void DWARFCallFrameInfo::GetCFIData() { | |
| if (!m_cfi_data_initialized) { | |
| Log *log = GetLog(LLDBLog::Unwind); | |
| if (log) | |
| m_objfile.GetModule()->LogMessage(log, "Reading EH frame info"); | |
| m_objfile.ReadSectionData(m_section_sp.get(), m_cfi_data); | |
| m_cfi_data_initialized = true; | |
| } | |
| } | |
| // Scan through the eh_frame or debug_frame section looking for FDEs and noting | |
| // the start/end addresses of the functions and a pointer back to the | |
| // function's FDE for later expansion. Internalize CIEs as we come across them. | |
| void DWARFCallFrameInfo::GetFDEIndex() { | |
| if (m_section_sp.get() == nullptr || m_section_sp->IsEncrypted()) | |
| return; | |
| if (m_fde_index_initialized) | |
| return; | |
| std::lock_guard<std::mutex> guard(m_fde_index_mutex); | |
| if (m_fde_index_initialized) // if two threads hit the locker | |
| return; | |
| LLDB_SCOPED_TIMERF("%s", m_objfile.GetFileSpec().GetFilename().str().c_str()); | |
| bool clear_address_zeroth_bit = false; | |
| if (ArchSpec arch = m_objfile.GetArchitecture()) { | |
| if (arch.GetTriple().getArch() == llvm::Triple::arm || | |
| arch.GetTriple().getArch() == llvm::Triple::thumb) | |
| clear_address_zeroth_bit = true; | |
| } | |
| lldb::offset_t offset = 0; | |
| if (!m_cfi_data_initialized) | |
| GetCFIData(); | |
| while (m_cfi_data.ValidOffsetForDataOfSize(offset, 8)) { | |
| const dw_offset_t current_entry = offset; | |
| dw_offset_t cie_id, next_entry, cie_offset; | |
| uint32_t len = m_cfi_data.GetU32(&offset); | |
| bool is_64bit = (len == llvm::dwarf::DW_LENGTH_DWARF64); | |
| if (is_64bit) { | |
| len = m_cfi_data.GetU64(&offset); | |
| cie_id = m_cfi_data.GetU64(&offset); | |
| next_entry = current_entry + len + 12; | |
| cie_offset = current_entry + 12 - cie_id; | |
| } else { | |
| cie_id = m_cfi_data.GetU32(&offset); | |
| next_entry = current_entry + len + 4; | |
| cie_offset = current_entry + 4 - cie_id; | |
| } | |
| if (next_entry > m_cfi_data.GetByteSize() + 1) { | |
| Debugger::ReportError(llvm::formatv("Invalid fde/cie next entry offset " | |
| "of {0:x} found in cie/fde at {1:x}", | |
| next_entry, current_entry)); | |
| // Don't trust anything in this eh_frame section if we find blatantly | |
| // invalid data. | |
| m_fde_index.Clear(); | |
| m_fde_index_initialized = true; | |
| return; | |
| } | |
| // Check if this is a CIE or FDE based on the CIE ID marker | |
| if (IsCIEMarker(cie_id, is_64bit, m_type) || len == 0) { | |
| auto cie_sp = ParseCIE(current_entry); | |
| if (!cie_sp) { | |
| // Cannot parse, the reason is already logged | |
| m_fde_index.Clear(); | |
| m_fde_index_initialized = true; | |
| return; | |
| } | |
| m_cie_map[current_entry] = std::move(cie_sp); | |
| offset = next_entry; | |
| continue; | |
| } | |
| if (m_type == DWARF) | |
| cie_offset = cie_id; | |
| if (cie_offset > m_cfi_data.GetByteSize()) { | |
| Debugger::ReportError(llvm::formatv("Invalid cie offset of {0:x} " | |
| "found in cie/fde at {1:x}", | |
| cie_offset, current_entry)); | |
| // Don't trust anything in this eh_frame section if we find blatantly | |
| // invalid data. | |
| m_fde_index.Clear(); | |
| m_fde_index_initialized = true; | |
| return; | |
| } | |
| const CIE *cie = GetCIE(cie_offset); | |
| if (cie) { | |
| const lldb::addr_t pc_rel_addr = m_section_sp->GetFileAddress(); | |
| const lldb::addr_t text_addr = LLDB_INVALID_ADDRESS; | |
| const lldb::addr_t data_addr = LLDB_INVALID_ADDRESS; | |
| lldb::addr_t addr = | |
| GetGNUEHPointer(m_cfi_data, &offset, cie->ptr_encoding, pc_rel_addr, | |
| text_addr, data_addr); | |
| if (clear_address_zeroth_bit) | |
| addr &= ~1ull; | |
| lldb::addr_t length = GetGNUEHPointer( | |
| m_cfi_data, &offset, cie->ptr_encoding & DW_EH_PE_MASK_ENCODING, | |
| pc_rel_addr, text_addr, data_addr); | |
| FDEEntryMap::Entry fde(addr, length, current_entry); | |
| m_fde_index.Append(fde); | |
| } else { | |
| Debugger::ReportError(llvm::formatv( | |
| "unable to find CIE at {0:x} for cie_id = {1:x} for entry at {2:x}.", | |
| cie_offset, cie_id, current_entry)); | |
| } | |
| offset = next_entry; | |
| } | |
| m_fde_index.Sort(); | |
| m_fde_index_initialized = true; | |
| } | |
| std::optional<DWARFCallFrameInfo::FDE> | |
| DWARFCallFrameInfo::ParseFDE(dw_offset_t dwarf_offset, | |
| const Address &startaddr) { | |
| Log *log = GetLog(LLDBLog::Unwind); | |
| lldb::offset_t offset = dwarf_offset; | |
| lldb::offset_t current_entry = offset; | |
| if (!m_section_sp || m_section_sp->IsEncrypted()) | |
| return std::nullopt; | |
| if (!m_cfi_data_initialized) | |
| GetCFIData(); | |
| uint32_t length = m_cfi_data.GetU32(&offset); | |
| dw_offset_t cie_offset; | |
| bool is_64bit = (length == llvm::dwarf::DW_LENGTH_DWARF64); | |
| if (is_64bit) { | |
| length = m_cfi_data.GetU64(&offset); | |
| cie_offset = m_cfi_data.GetU64(&offset); | |
| } else { | |
| cie_offset = m_cfi_data.GetU32(&offset); | |
| } | |
| // FDE entries with zeroth cie_offset may occur for debug_frame. | |
| assert(!(m_type == EH && 0 == cie_offset) && | |
| cie_offset != | |
| (is_64bit ? llvm::dwarf::DW64_CIE_ID : llvm::dwarf::DW_CIE_ID)); | |
| // Translate the CIE_id from the eh_frame format, which is relative to the | |
| // FDE offset, into a __eh_frame section offset | |
| if (m_type == EH) | |
| cie_offset = current_entry + (is_64bit ? 12 : 4) - cie_offset; | |
| const CIE *cie = GetCIE(cie_offset); | |
| assert(cie != nullptr); | |
| const dw_offset_t end_offset = current_entry + length + (is_64bit ? 12 : 4); | |
| const lldb::addr_t pc_rel_addr = m_section_sp->GetFileAddress(); | |
| const lldb::addr_t text_addr = LLDB_INVALID_ADDRESS; | |
| const lldb::addr_t data_addr = LLDB_INVALID_ADDRESS; | |
| lldb::addr_t range_base = | |
| GetGNUEHPointer(m_cfi_data, &offset, cie->ptr_encoding, pc_rel_addr, | |
| text_addr, data_addr); | |
| lldb::addr_t range_len = GetGNUEHPointer( | |
| m_cfi_data, &offset, cie->ptr_encoding & DW_EH_PE_MASK_ENCODING, | |
| pc_rel_addr, text_addr, data_addr); | |
| AddressRange range(range_base, m_objfile.GetAddressByteSize(), | |
| m_objfile.GetSectionList()); | |
| range.SetByteSize(range_len); | |
| // Skip the LSDA, if present. | |
| if (cie->augmentation[0] == 'z') | |
| offset += (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| FDE fde; | |
| fde.for_signal_trap = strchr(cie->augmentation, 'S') != nullptr; | |
| fde.range = range; | |
| fde.return_addr_reg_num = cie->return_addr_reg_num; | |
| uint32_t code_align = cie->code_align; | |
| int32_t data_align = cie->data_align; | |
| UnwindPlan::Row row = cie->initial_row; | |
| std::vector<UnwindPlan::Row> stack; | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| while (m_cfi_data.ValidOffset(offset) && offset < end_offset) { | |
| uint8_t inst = m_cfi_data.GetU8(&offset); | |
| uint8_t primary_opcode = inst & 0xC0; | |
| uint8_t extended_opcode = inst & 0x3F; | |
| if (!HandleCommonDwarfOpcode(primary_opcode, extended_opcode, data_align, | |
| offset, row)) { | |
| if (primary_opcode) { | |
| switch (primary_opcode) { | |
| case DW_CFA_advance_loc: // (Row Creation Instruction) | |
| { // 0x40 - high 2 bits are 0x1, lower 6 bits are delta | |
| // takes a single argument that represents a constant delta. The | |
| // required action is to create a new table row with a location value | |
| // that is computed by taking the current entry's location value and | |
| // adding (delta * code_align). All other values in the new row are | |
| // initially identical to the current row. | |
| fde.rows.push_back(row); | |
| row.SlideOffset(extended_opcode * code_align); | |
| break; | |
| } | |
| case DW_CFA_restore: { // 0xC0 - high 2 bits are 0x3, lower 6 bits are | |
| // register | |
| // takes a single argument that represents a register number. The | |
| // required action is to change the rule for the indicated register | |
| // to the rule assigned it by the initial_instructions in the CIE. | |
| uint32_t reg_num = extended_opcode; | |
| // We only keep enough register locations around to unwind what is in | |
| // our thread, and these are organized by the register index in that | |
| // state, so we need to convert our eh_frame register number from the | |
| // EH frame info, to a register index | |
| if (fde.rows[0].GetRegisterInfo(reg_num, reg_location)) | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| else { | |
| // If the register was not set in the first row, remove the | |
| // register info to keep the unmodified value from the caller. | |
| row.RemoveRegisterInfo(reg_num); | |
| } | |
| break; | |
| } | |
| } | |
| } else { | |
| switch (extended_opcode) { | |
| case DW_CFA_set_loc: // 0x1 (Row Creation Instruction) | |
| { | |
| // DW_CFA_set_loc takes a single argument that represents an address. | |
| // The required action is to create a new table row using the | |
| // specified address as the location. All other values in the new row | |
| // are initially identical to the current row. The new location value | |
| // should always be greater than the current one. | |
| fde.rows.push_back(row); | |
| row.SetOffset(m_cfi_data.GetAddress(&offset) - | |
| startaddr.GetFileAddress()); | |
| break; | |
| } | |
| case DW_CFA_advance_loc1: // 0x2 (Row Creation Instruction) | |
| { | |
| // takes a single uword argument that represents a constant delta. | |
| // This instruction is identical to DW_CFA_advance_loc except for the | |
| // encoding and size of the delta argument. | |
| fde.rows.push_back(row); | |
| row.SlideOffset(m_cfi_data.GetU8(&offset) * code_align); | |
| break; | |
| } | |
| case DW_CFA_advance_loc2: // 0x3 (Row Creation Instruction) | |
| { | |
| // takes a single uword argument that represents a constant delta. | |
| // This instruction is identical to DW_CFA_advance_loc except for the | |
| // encoding and size of the delta argument. | |
| fde.rows.push_back(row); | |
| row.SlideOffset(m_cfi_data.GetU16(&offset) * code_align); | |
| break; | |
| } | |
| case DW_CFA_advance_loc4: // 0x4 (Row Creation Instruction) | |
| { | |
| // takes a single uword argument that represents a constant delta. | |
| // This instruction is identical to DW_CFA_advance_loc except for the | |
| // encoding and size of the delta argument. | |
| fde.rows.push_back(row); | |
| row.SlideOffset(m_cfi_data.GetU32(&offset) * code_align); | |
| break; | |
| } | |
| case DW_CFA_restore_extended: // 0x6 | |
| { | |
| // takes a single unsigned LEB128 argument that represents a register | |
| // number. This instruction is identical to DW_CFA_restore except for | |
| // the encoding and size of the register argument. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| if (fde.rows[0].GetRegisterInfo(reg_num, reg_location)) | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| break; | |
| } | |
| case DW_CFA_remember_state: // 0xA | |
| { | |
| // These instructions define a stack of information. Encountering the | |
| // DW_CFA_remember_state instruction means to save the rules for | |
| // every register on the current row on the stack. Encountering the | |
| // DW_CFA_restore_state instruction means to pop the set of rules off | |
| // the stack and place them in the current row. (This operation is | |
| // useful for compilers that move epilogue code into the body of a | |
| // function.) | |
| stack.push_back(row); | |
| break; | |
| } | |
| case DW_CFA_restore_state: // 0xB | |
| { | |
| // These instructions define a stack of information. Encountering the | |
| // DW_CFA_remember_state instruction means to save the rules for | |
| // every register on the current row on the stack. Encountering the | |
| // DW_CFA_restore_state instruction means to pop the set of rules off | |
| // the stack and place them in the current row. (This operation is | |
| // useful for compilers that move epilogue code into the body of a | |
| // function.) | |
| if (stack.empty()) { | |
| LLDB_LOG(log, | |
| "DWARFCallFrameInfo::{0}(dwarf_offset: " | |
| "{1:x16}, startaddr: [{2:x16}] encountered " | |
| "DW_CFA_restore_state but state stack " | |
| "is empty. Corrupt unwind info?", | |
| __FUNCTION__, dwarf_offset, startaddr.GetFileAddress()); | |
| break; | |
| } | |
| int64_t offset = row.GetOffset(); | |
| row = std::move(stack.back()); | |
| stack.pop_back(); | |
| row.SetOffset(offset); | |
| break; | |
| } | |
| case DW_CFA_GNU_args_size: // 0x2e | |
| { | |
| // The DW_CFA_GNU_args_size instruction takes an unsigned LEB128 | |
| // operand representing an argument size. This instruction specifies | |
| // the total of the size of the arguments which have been pushed onto | |
| // the stack. | |
| // TODO: Figure out how we should handle this. | |
| m_cfi_data.GetULEB128(&offset); | |
| break; | |
| } | |
| case DW_CFA_val_offset: { // 0x14 | |
| // takes two unsigned LEB128 operands representing a register number | |
| // and a factored offset. The required action is to change the rule | |
| // for the register indicated by the register number to be a | |
| // val_offset(N) rule where the value of N is factored_offset* | |
| // data_alignment_factor | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = | |
| (int32_t)m_cfi_data.GetULEB128(&offset) * data_align; | |
| reg_location.SetIsCFAPlusOffset(op_offset); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| break; | |
| } | |
| case DW_CFA_val_offset_sf: { // 0x15 | |
| // takes two operands: an unsigned LEB128 value representing a | |
| // register number and a signed LEB128 factored offset. This | |
| // instruction is identical to DW_CFA_val_offset except that the | |
| // second operand is signed and factored. The resulting offset is | |
| // factored_offset* data_alignment_factor. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = | |
| (int32_t)m_cfi_data.GetSLEB128(&offset) * data_align; | |
| reg_location.SetIsCFAPlusOffset(op_offset); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| fde.rows.push_back(row); | |
| return fde; | |
| } | |
| bool DWARFCallFrameInfo::HandleCommonDwarfOpcode(uint8_t primary_opcode, | |
| uint8_t extended_opcode, | |
| int32_t data_align, | |
| lldb::offset_t &offset, | |
| UnwindPlan::Row &row) { | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| if (primary_opcode) { | |
| switch (primary_opcode) { | |
| case DW_CFA_offset: { // 0x80 - high 2 bits are 0x2, lower 6 bits are | |
| // register | |
| // takes two arguments: an unsigned LEB128 constant representing a | |
| // factored offset and a register number. The required action is to | |
| // change the rule for the register indicated by the register number to | |
| // be an offset(N) rule with a value of (N = factored offset * | |
| // data_align). | |
| uint8_t reg_num = extended_opcode; | |
| int32_t op_offset = (int32_t)m_cfi_data.GetULEB128(&offset) * data_align; | |
| reg_location.SetAtCFAPlusOffset(op_offset); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| } | |
| } else { | |
| switch (extended_opcode) { | |
| case DW_CFA_nop: // 0x0 | |
| return true; | |
| case DW_CFA_offset_extended: // 0x5 | |
| { | |
| // takes two unsigned LEB128 arguments representing a register number and | |
| // a factored offset. This instruction is identical to DW_CFA_offset | |
| // except for the encoding and size of the register argument. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = (int32_t)m_cfi_data.GetULEB128(&offset) * data_align; | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetAtCFAPlusOffset(op_offset); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_undefined: // 0x7 | |
| { | |
| // takes a single unsigned LEB128 argument that represents a register | |
| // number. The required action is to set the rule for the specified | |
| // register to undefined. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetUndefined(); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_same_value: // 0x8 | |
| { | |
| // takes a single unsigned LEB128 argument that represents a register | |
| // number. The required action is to set the rule for the specified | |
| // register to same value. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetSame(); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_register: // 0x9 | |
| { | |
| // takes two unsigned LEB128 arguments representing register numbers. The | |
| // required action is to set the rule for the first register to be the | |
| // second register. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| uint32_t other_reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetInRegister(other_reg_num); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa: // 0xC (CFA Definition Instruction) | |
| { | |
| // Takes two unsigned LEB128 operands representing a register number and | |
| // a (non-factored) offset. The required action is to define the current | |
| // CFA rule to use the provided register and offset. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = (int32_t)m_cfi_data.GetULEB128(&offset); | |
| row.GetCFAValue().SetIsRegisterPlusOffset(reg_num, op_offset); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa_register: // 0xD (CFA Definition Instruction) | |
| { | |
| // takes a single unsigned LEB128 argument representing a register | |
| // number. The required action is to define the current CFA rule to use | |
| // the provided register (but to keep the old offset). | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| row.GetCFAValue().SetIsRegisterPlusOffset(reg_num, | |
| row.GetCFAValue().GetOffset()); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa_offset: // 0xE (CFA Definition Instruction) | |
| { | |
| // Takes a single unsigned LEB128 operand representing a (non-factored) | |
| // offset. The required action is to define the current CFA rule to use | |
| // the provided offset (but to keep the old register). | |
| int32_t op_offset = (int32_t)m_cfi_data.GetULEB128(&offset); | |
| row.GetCFAValue().SetIsRegisterPlusOffset( | |
| row.GetCFAValue().GetRegisterNumber(), op_offset); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa_expression: // 0xF (CFA Definition Instruction) | |
| { | |
| size_t block_len = (size_t)m_cfi_data.GetULEB128(&offset); | |
| const uint8_t *block_data = | |
| static_cast<const uint8_t *>(m_cfi_data.GetData(&offset, block_len)); | |
| row.GetCFAValue().SetIsDWARFExpression(block_data, block_len); | |
| return true; | |
| } | |
| case DW_CFA_expression: // 0x10 | |
| { | |
| // Takes two operands: an unsigned LEB128 value representing a register | |
| // number, and a DW_FORM_block value representing a DWARF expression. The | |
| // required action is to change the rule for the register indicated by | |
| // the register number to be an expression(E) rule where E is the DWARF | |
| // expression. That is, the DWARF expression computes the address. The | |
| // value of the CFA is pushed on the DWARF evaluation stack prior to | |
| // execution of the DWARF expression. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| uint32_t block_len = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| const uint8_t *block_data = | |
| static_cast<const uint8_t *>(m_cfi_data.GetData(&offset, block_len)); | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetAtDWARFExpression(block_data, block_len); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_offset_extended_sf: // 0x11 | |
| { | |
| // takes two operands: an unsigned LEB128 value representing a register | |
| // number and a signed LEB128 factored offset. This instruction is | |
| // identical to DW_CFA_offset_extended except that the second operand is | |
| // signed and factored. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = (int32_t)m_cfi_data.GetSLEB128(&offset) * data_align; | |
| UnwindPlan::Row::AbstractRegisterLocation reg_location; | |
| reg_location.SetAtCFAPlusOffset(op_offset); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa_sf: // 0x12 (CFA Definition Instruction) | |
| { | |
| // Takes two operands: an unsigned LEB128 value representing a register | |
| // number and a signed LEB128 factored offset. This instruction is | |
| // identical to DW_CFA_def_cfa except that the second operand is signed | |
| // and factored. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| int32_t op_offset = (int32_t)m_cfi_data.GetSLEB128(&offset) * data_align; | |
| row.GetCFAValue().SetIsRegisterPlusOffset(reg_num, op_offset); | |
| return true; | |
| } | |
| case DW_CFA_def_cfa_offset_sf: // 0x13 (CFA Definition Instruction) | |
| { | |
| // takes a signed LEB128 operand representing a factored offset. This | |
| // instruction is identical to DW_CFA_def_cfa_offset except that the | |
| // operand is signed and factored. | |
| int32_t op_offset = (int32_t)m_cfi_data.GetSLEB128(&offset) * data_align; | |
| uint32_t cfa_regnum = row.GetCFAValue().GetRegisterNumber(); | |
| row.GetCFAValue().SetIsRegisterPlusOffset(cfa_regnum, op_offset); | |
| return true; | |
| } | |
| case DW_CFA_val_expression: // 0x16 | |
| { | |
| // takes two operands: an unsigned LEB128 value representing a register | |
| // number, and a DW_FORM_block value representing a DWARF expression. The | |
| // required action is to change the rule for the register indicated by | |
| // the register number to be a val_expression(E) rule where E is the | |
| // DWARF expression. That is, the DWARF expression computes the value of | |
| // the given register. The value of the CFA is pushed on the DWARF | |
| // evaluation stack prior to execution of the DWARF expression. | |
| uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| uint32_t block_len = (uint32_t)m_cfi_data.GetULEB128(&offset); | |
| const uint8_t *block_data = | |
| (const uint8_t *)m_cfi_data.GetData(&offset, block_len); | |
| reg_location.SetIsDWARFExpression(block_data, block_len); | |
| row.SetRegisterInfo(reg_num, reg_location); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| void DWARFCallFrameInfo::ForEachFDEEntries( | |
| const std::function<bool(lldb::addr_t, uint32_t, dw_offset_t)> &callback) { | |
| GetFDEIndex(); | |
| for (size_t i = 0, c = m_fde_index.GetSize(); i < c; ++i) { | |
| const FDEEntryMap::Entry &entry = m_fde_index.GetEntryRef(i); | |
| if (!callback(entry.base, entry.size, entry.data)) | |
| break; | |
| } | |
| } |