| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/unwinder/cfi_module.h" |
| |
| #include <elf.h> |
| |
| #include <algorithm> |
| #include <cinttypes> |
| #include <cstdint> |
| #include <cstdio> |
| #include <limits> |
| #include <map> |
| #include <string> |
| |
| #include "src/lib/unwinder/cfi_parser.h" |
| #include "src/lib/unwinder/elf_utils.h" |
| #include "src/lib/unwinder/error.h" |
| #include "src/lib/unwinder/module.h" |
| #include "src/lib/unwinder/registers.h" |
| |
| namespace unwinder { |
| |
| namespace { |
| |
| // The CIE ID that distinguishes a CIE from an FDE. In the eh_frame section (version 1), the CIE ID |
| // is always zero. In the debug_frame section (version 4), the value is either a 4 byte or 8 byte |
| // value, depending on whether the section is encoded as 32 bit or 64 bit DWARF. Regardless of the |
| // size of the value, the value shall always be filled with 0xFF. |
| const constexpr uint32_t kDwarf32CieId = std::numeric_limits<uint32_t>::max(); |
| const constexpr uint64_t kDwarf64CieId = std::numeric_limits<uint64_t>::max(); |
| |
| // Check and return the size of each entry in the table. It's doubled because each entry contains |
| // 2 addresses, i.e., the start_pc and the fde_offset. |
| [[nodiscard]] Error DecodeTableEntrySize(uint8_t table_enc, uint64_t& res) { |
| if (table_enc == 0xFF) { // DW_EH_PE_omit |
| return Error("no binary search table"); |
| } |
| if ((table_enc & 0xF0) != 0x30) { |
| return Error("invalid table_enc"); |
| } |
| switch (table_enc & 0x0F) { |
| case 0x02: // DW_EH_PE_udata2 A 2 bytes unsigned value. |
| case 0x0A: // DW_EH_PE_sdata2 A 2 bytes signed value. |
| res = 4; |
| return Success(); |
| case 0x03: // DW_EH_PE_udata4 A 4 bytes unsigned value. |
| case 0x0B: // DW_EH_PE_sdata4 A 4 bytes signed value. |
| res = 8; |
| return Success(); |
| case 0x04: // DW_EH_PE_udata8 An 8 bytes unsigned value. |
| case 0x0C: // DW_EH_PE_sdata8 An 8 bytes signed value. |
| res = 16; |
| return Success(); |
| default: |
| return Error("unsupported table_enc: %#x", table_enc); |
| } |
| } |
| |
| // Decode the length and cie_ptr field in CIE/FDE. It's awkward because we want to support both |
| // .eh_frame format and .debug_frame format. |
| // |
| // When returned, |ptr| will point to the beginning of the body, and |end| will point to the end |
| // of the entry. |
| [[nodiscard]] Error DecodeCieFdeHdrAndAdvance(Memory* elf, UnwindTableSectionType section_type, |
| uint64_t& ptr, uint64_t& end, uint64_t& cie_id) { |
| uint32_t short_length; |
| if (auto err = elf->ReadAndAdvance(ptr, short_length); err.has_err()) { |
| return err; |
| } |
| if (short_length == 0) { |
| return Error("not a valid CIE/FDE"); |
| } |
| |
| // The first 4 bytes of the length set to 0xFFFFFFFF indicates that this is 64 bit DWARF format |
| // and we can read the cie_id directly below after reading the full 8 byte length. |
| if (short_length != 0xFFFFFFFF) { |
| end = ptr + short_length; |
| } else { |
| uint64_t length; |
| if (auto err = elf->ReadAndAdvance(ptr, length); err.has_err()) { |
| return err; |
| } |
| end = ptr + length; |
| } |
| // The cie_id is 8-bytes only in debug_frame and it's a 64-bit DWARF format. |
| if (section_type == UnwindTableSectionType::kDebugFrame && short_length == 0xFFFFFFFF) { |
| if (auto err = elf->ReadAndAdvance(ptr, cie_id); err.has_err()) { |
| return err; |
| } |
| } else { |
| uint32_t short_cie_id; |
| if (auto err = elf->ReadAndAdvance(ptr, short_cie_id); err.has_err()) { |
| return err; |
| } |
| |
| cie_id = short_cie_id; |
| } |
| return Success(); |
| } |
| |
| // Returns true if the given |cie_id| value is correct for |section_type|. |
| [[nodiscard]] bool CheckCieId(UnwindTableSectionType section_type, uint64_t cie_id) { |
| switch (section_type) { |
| case UnwindTableSectionType::kEhFrame: |
| return cie_id == 0; |
| case UnwindTableSectionType::kDebugFrame: |
| return cie_id == kDwarf32CieId || cie_id == kDwarf64CieId; |
| } |
| } |
| |
| Elf64_Phdr UpcastPhdr(const Elf32_Phdr& phdr32) { |
| Elf64_Phdr phdr; |
| phdr.p_type = phdr32.p_type; |
| phdr.p_offset = phdr32.p_offset; |
| phdr.p_vaddr = phdr32.p_vaddr; |
| phdr.p_paddr = phdr32.p_paddr; |
| phdr.p_filesz = phdr32.p_filesz; |
| phdr.p_memsz = phdr32.p_memsz; |
| phdr.p_flags = phdr32.p_flags; |
| phdr.p_align = phdr32.p_align; |
| return phdr; |
| } |
| |
| } // namespace |
| |
| // Load the .eh_frame and/or .debug_frame. |
| // |
| // If |address_mode_| is kProcess, then .eh_frame will be loaded from the loaded segment in process |
| // memory, and .debug_frame will never be loaded. Otherwise, the module will read from the given ELF |
| // file on disk. Depending on the method of compilation for the particular TU of a given PC, the |
| // .eh_frame or .debug_frame section may be used. .debug_frame is preferred and therefore inspected |
| // first for a PC. |
| // |
| // See the Linux Standard Base Core Specification |
| // https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html |
| // and a reference implementation in LLVM |
| // https://github.com/llvm/llvm-project/blob/main/libunwind/src/DwarfParser.hpp |
| // https://github.com/llvm/llvm-project/blob/main/libunwind/src/EHHeaderParser.hpp |
| Error CfiModule::Load() { |
| if (!elf_) { |
| return Error("no elf memory"); |
| } |
| |
| Elf64_Ehdr ehdr; |
| |
| // Probe the ELF identification header from the larger ELF header to distinguish between 32 and 64 |
| // bit modules. We'll translate any 32 bit headers into their 64 bit ELF type counterparts so the |
| // below code can not worry about the difference. |
| switch (address_size_) { |
| case Module::AddressSize::k32Bit: { |
| Elf32_Ehdr ehdr32; |
| if (auto err = elf_->Read(elf_ptr_, ehdr32); err.has_err()) { |
| return err; |
| } |
| |
| memcpy(ehdr.e_ident, ehdr32.e_ident, EI_NIDENT); |
| ehdr.e_type = ehdr32.e_type; |
| ehdr.e_machine = ehdr32.e_machine; |
| ehdr.e_version = ehdr32.e_version; |
| ehdr.e_entry = ehdr32.e_entry; |
| ehdr.e_phoff = ehdr32.e_phoff; |
| ehdr.e_shoff = ehdr32.e_shoff; |
| ehdr.e_flags = ehdr32.e_flags; |
| ehdr.e_ehsize = ehdr32.e_ehsize; |
| ehdr.e_phentsize = ehdr32.e_phentsize; |
| ehdr.e_phnum = ehdr32.e_phnum; |
| ehdr.e_shentsize = ehdr32.e_shentsize; |
| ehdr.e_shnum = ehdr32.e_shnum; |
| ehdr.e_shstrndx = ehdr32.e_shstrndx; |
| break; |
| } |
| case Module::AddressSize::k64Bit: { |
| // 64 Bit modules can just read the 64 bit ELF header directly. |
| if (auto err = elf_->Read(elf_ptr_, ehdr); err.has_err()) { |
| return err; |
| } |
| break; |
| } |
| default: |
| return Error("Unknown ELF class."); |
| } |
| |
| // Header magic should be correct. |
| if (strncmp(reinterpret_cast<const char*>(ehdr.e_ident), ELFMAG, SELFMAG) != 0) { |
| return Error("not an ELF image"); |
| } |
| |
| if (auto err = LoadPhdrs(ehdr); err.has_err()) { |
| return err; |
| } |
| |
| auto eh_frame_err = LoadEhFrame(ehdr); |
| auto debug_frame_err = LoadDebugFrame(ehdr); |
| |
| if (eh_frame_err.has_err() && debug_frame_err.has_err()) { |
| return Error("Failed to load both eh_frame (err=\"%s\") and debug_frame (\"%s\") sections\n.", |
| eh_frame_err.msg().c_str(), debug_frame_err.msg().c_str()); |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::LoadEhFrame(const Elf64_Ehdr& ehdr) { |
| if (auto err = LoadEhFrameHdr(ehdr); err.has_err()) { |
| return err; |
| } |
| |
| // ============================================================================================== |
| // Load from the .eh_frame_hdr section. |
| // This section may not always be present - it's purely an optimization when we get to use it. |
| // When not present, we have to resort to linearly scanning the FDE list. |
| // ============================================================================================== |
| if (eh_frame_hdr_ptr_ > 0) { |
| auto p = eh_frame_hdr_ptr_; |
| uint8_t version; |
| if (auto err = elf_->ReadAndAdvance(p, version); err.has_err()) { |
| return err; |
| } |
| if (version != 1) { |
| return Error("unknown eh_frame_hdr version %d", version); |
| } |
| |
| uint8_t eh_frame_ptr_enc; |
| uint8_t fde_count_enc; |
| uint64_t eh_frame_ptr; // not used |
| if (auto err = elf_->ReadAndAdvance(p, eh_frame_ptr_enc); err.has_err()) { |
| return err; |
| } |
| if (auto err = elf_->ReadAndAdvance(p, fde_count_enc); err.has_err()) { |
| return err; |
| } |
| if (auto err = elf_->ReadAndAdvance(p, table_enc_); err.has_err()) { |
| return err; |
| } |
| if (auto err = DecodeTableEntrySize(table_enc_, table_entry_size_); err.has_err()) { |
| return err; |
| } |
| if (auto err = |
| elf_->ReadEncodedAndAdvance(p, eh_frame_ptr, eh_frame_ptr_enc, eh_frame_hdr_ptr_); |
| err.has_err()) { |
| return err; |
| } |
| if (auto err = elf_->ReadEncodedAndAdvance(p, fde_count_, fde_count_enc, eh_frame_hdr_ptr_); |
| err.has_err()) { |
| return err; |
| } |
| table_ptr_ = p; |
| |
| if (fde_count_ == 0) { |
| return Error("empty binary search table"); |
| } |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::LoadPhdrs(const Elf64_Ehdr& ehdr) { |
| const bool is_64bit = ehdr.e_ident[EI_CLASS] == ELFCLASS64; |
| |
| phdrs_.resize(ehdr.e_phnum); |
| if (is_64bit) { |
| if (auto err = elf_->ReadBytes(elf_ptr_ + ehdr.e_phoff, ehdr.e_phnum * ehdr.e_phentsize, |
| phdrs_.data()); |
| err.has_err()) { |
| return err; |
| } |
| } else { |
| std::vector<Elf32_Phdr> phdr32_buf(ehdr.e_phnum); |
| if (auto err = elf_->ReadBytes(elf_ptr_ + ehdr.e_phoff, ehdr.e_phnum * ehdr.e_phentsize, |
| phdr32_buf.data()); |
| err.has_err()) { |
| return err; |
| } |
| |
| for (size_t i = 0; i < ehdr.e_phnum; i++) { |
| phdrs_[i] = UpcastPhdr(phdr32_buf[i]); |
| } |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::LoadEhFrameHdr(const Elf64_Ehdr& ehdr) { |
| pc_begin_ = std::numeric_limits<uint64_t>::max(); |
| pc_end_ = std::numeric_limits<uint64_t>::min(); |
| |
| for (const auto& phdr : phdrs_) { |
| if (phdr.p_type == PT_GNU_EH_FRAME) { |
| if (address_mode_ == Module::AddressMode::kProcess) { |
| eh_frame_hdr_ptr_ = elf_ptr_ + phdr.p_vaddr; |
| } else { |
| eh_frame_hdr_ptr_ = elf_ptr_ + phdr.p_offset; |
| } |
| } else if (phdr.p_type == PT_LOAD) { |
| // Note that we cannot limit the inspection of PT_LOAD segments to those that are marked |
| // executable because this does not necessarily match the actual mapping of executable |
| // VMOs. If we want to narrow the range of PCs further we should consult the |zx_info_maps_t| |
| // for this process. |
| pc_begin_ = std::min(pc_begin_, elf_ptr_ + phdr.p_vaddr); |
| pc_end_ = std::max(pc_end_, elf_ptr_ + phdr.p_vaddr + phdr.p_memsz); |
| } |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::LoadDebugFrame(const Elf64_Ehdr& ehdr) { |
| // ============================================================================================== |
| // Load from the .debug_frame section, if present. |
| // ============================================================================================== |
| debug_frame_ptr_ = 0; |
| debug_frame_end_ = 0; |
| |
| // Section headers and .debug_frame section are not loaded. |
| if (address_mode_ == Module::AddressMode::kProcess) { |
| return Error("debug_frame section not present when AddressMode == kProcess"); |
| } |
| |
| // if ehdr.e_shstrndx is 0, it means there's no section info, i.e., the binary is stripped. |
| if (!ehdr.e_shstrndx) { |
| return Error("no section info, is this a stripped binary?"); |
| } |
| |
| Elf64_Shdr shdr; |
| if (auto err = elf_utils::GetSectionByName<Elf64_Ehdr, Elf64_Shdr>(elf_, elf_ptr_, ".debug_frame", |
| ehdr, shdr); |
| err.has_err()) { |
| return err; |
| } |
| |
| debug_frame_ptr_ = elf_ptr_ + shdr.sh_offset; |
| debug_frame_end_ = debug_frame_ptr_ + shdr.sh_size; |
| |
| return Error("no debug_frame section found"); |
| } |
| |
| fit::result<Error, bool> CfiModule::Step(Memory* stack, const Registers& current, Registers& next) { |
| DwarfCie cie; |
| if (auto err = PrepareToStep(current, cie); err.has_err()) { |
| return fit::error(err); |
| } |
| |
| if (auto err = cfi_parser_->Step(stack, cie.return_address_register, current, next); |
| err.has_err()) { |
| return fit::error(err); |
| } |
| |
| return fit::ok(cie.is_signal_frame); |
| } |
| |
| void CfiModule::AsyncStep(AsyncMemory* stack, const Registers& current, |
| fit::callback<void(Error, Registers)> cb) { |
| DwarfCie cie; |
| if (auto err = PrepareToStep(current, cie); err.has_err()) { |
| return cb(err, Registers(current.arch())); |
| } |
| |
| cfi_parser_->AsyncStep(stack, cie.return_address_register, current, std::move(cb)); |
| } |
| |
| Error CfiModule::PrepareToStep(const Registers& current, DwarfCie& cie) { |
| uint64_t pc; |
| if (auto err = current.GetPC(pc); err.has_err()) { |
| return err; |
| } |
| if (!IsValidPC(pc)) { |
| return Error("pc %#" PRIx64 " is outside of the executable area", pc); |
| } |
| |
| DwarfFde fde; |
| |
| // Search for .debug_frame first. This is preferred over .eh_frame sections in situations where we |
| // might encounter both since debug_frame is likely to contain more complete information about all |
| // callsites that might be optimized out of eh_frame sections for binary size or performance |
| // reasons. Callers must provide an appropriate binary for the debug_frame section to be found, |
| // namely from either an unstripped binary or a separated debug info file corresponding to the |
| // program being unwound. |
| if (auto debug_frame_err = SearchDebugFrame(pc, cie, fde); debug_frame_err.has_err()) { |
| if (auto eh_frame_err = SearchEhFrame(pc, cie, fde); eh_frame_err.has_err()) { |
| return Error(debug_frame_err.msg() + "; " + eh_frame_err.msg()); |
| } |
| } |
| |
| cfi_parser_ = std::make_unique<CfiParser>(current.arch(), address_size_, |
| cie.code_alignment_factor, cie.data_alignment_factor); |
| |
| // Parse instructions in CIE first. |
| if (auto err = |
| cfi_parser_->ParseInstructions(elf_, cie.instructions_begin, cie.instructions_end, -1); |
| err.has_err()) { |
| return err; |
| } |
| |
| cfi_parser_->Snapshot(); |
| |
| // Parse instructions in FDE until pc. |
| if (auto err = cfi_parser_->ParseInstructions(elf_, fde.instructions_begin, fde.instructions_end, |
| pc - fde.pc_begin); |
| err.has_err()) { |
| return err; |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::BinarySearchEhFrame(uint64_t pc, DwarfCie& cie, DwarfFde& fde) { |
| // Binary search for fde_ptr in the range [low, high). |
| uint64_t low = 0; |
| uint64_t high = fde_count_; |
| while (low + 1 < high) { |
| uint64_t mid = (low + high) / 2; |
| uint64_t addr = table_ptr_ + mid * table_entry_size_; |
| uint64_t mid_pc; |
| if (auto err = elf_->ReadEncoded(addr, mid_pc, table_enc_, eh_frame_hdr_ptr_); err.has_err()) { |
| return err; |
| } |
| if (pc < mid_pc) { |
| high = mid; |
| } else { |
| low = mid; |
| } |
| } |
| |
| uint64_t addr = table_ptr_ + low * table_entry_size_ + table_entry_size_ / 2; |
| |
| uint64_t fde_ptr; |
| if (auto err = elf_->ReadEncoded(addr, fde_ptr, table_enc_, eh_frame_hdr_ptr_); err.has_err()) { |
| return err; |
| } |
| |
| if (auto err = DecodeFde(UnwindTableSectionType::kEhFrame, fde_ptr, cie, fde); err.has_err()) { |
| return err; |
| } |
| return Success(); |
| } |
| |
| Error CfiModule::LinearSearchEhFrame(uint64_t pc, DwarfCie& cie, DwarfFde& fde) { |
| // A length of 0 indicates the terminator FDE. |
| uint64_t addr = eh_frame_begin_; |
| |
| while (true) { |
| if (auto err = DecodeFde(UnwindTableSectionType::kEhFrame, addr, cie, fde); err.has_err()) { |
| return err; |
| } |
| |
| if (fde.instructions_end == 0) { |
| // Got the terminator FDE without finding an FDE containing pc, now we give up. |
| return Error("Failed to find an FDE containing pc!"); |
| } else if (pc >= fde.pc_begin && pc < fde.pc_end) { |
| break; |
| } |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::SearchEhFrame(uint64_t pc, DwarfCie& cie, DwarfFde& fde) { |
| if (eh_frame_hdr_ptr_ != 0) { |
| // We always expect the eh_frame_hdr section to be complete, so there's no fallback mechanism |
| // for errors. |
| if (auto err = BinarySearchEhFrame(pc, cie, fde); err.has_err()) { |
| return err; |
| } |
| } else if (eh_frame_begin_ != 0) { |
| auto err = LinearSearchEhFrame(pc, cie, fde); |
| if (err.ok()) { |
| return Success(); |
| } |
| |
| } else { |
| return Error("Do not have eh_frame_hdr or eh_frame pointers"); |
| } |
| |
| if (pc < fde.pc_begin || pc >= fde.pc_end) { |
| return Error("cannot find FDE for pc %#" PRIx64, pc); |
| } |
| return Success(); |
| } |
| |
| Error CfiModule::SearchDebugFrame(uint64_t pc, DwarfCie& cie, DwarfFde& fde) { |
| if (!debug_frame_ptr_) { |
| return Error("no .debug_frame section"); |
| } |
| if (debug_frame_map_.empty()) { |
| if (auto err = BuildDebugFrameMap(); err.has_err()) { |
| return err; |
| } |
| } |
| |
| auto debug_frame_map_it = debug_frame_map_.upper_bound(pc); |
| if (debug_frame_map_it == debug_frame_map_.begin()) { |
| return Error("cannot find FDE for pc %#" PRIx64 " in .debug_frame", pc); |
| } |
| debug_frame_map_it--; |
| uint64_t fde_ptr = debug_frame_map_it->second; |
| |
| if (auto err = DecodeFde(UnwindTableSectionType::kDebugFrame, fde_ptr, cie, fde); err.has_err()) { |
| return err; |
| } |
| if (pc < fde.pc_begin || pc >= fde.pc_end) { |
| return Error("cannot find FDE for pc %#" PRIx64 " in .debug_frame", pc); |
| } |
| return Success(); |
| } |
| |
| // In order to read less memory, this function assumes the address encoding of all CIEs is the same, |
| // so that it only needs to decode the first CIE. |
| Error CfiModule::BuildDebugFrameMap() { |
| debug_frame_map_.clear(); |
| uint8_t fde_address_encoding = 0; |
| for (uint64_t p = debug_frame_ptr_, next_p; p < debug_frame_end_; p = next_p) { |
| uint64_t hdr_p = p; |
| uint64_t cie_id; |
| if (auto err = |
| DecodeCieFdeHdrAndAdvance(elf_, UnwindTableSectionType::kDebugFrame, p, next_p, cie_id); |
| err.has_err()) { |
| return err; |
| } |
| if (CheckCieId(UnwindTableSectionType::kDebugFrame, cie_id)) { |
| if (fde_address_encoding) { |
| // Assume address encoding is the same for all CIEs. Skip all other CIEs to accelerate. |
| continue; |
| } |
| DwarfCie cie; |
| // This will only be called once, so it's fine that |DecodeCie| decodes the header again. |
| if (auto err = DecodeCie(UnwindTableSectionType::kDebugFrame, hdr_p, cie); err.has_err()) { |
| return err; |
| } |
| fde_address_encoding = cie.fde_address_encoding; |
| } else { // is FDE |
| // We only need pc_begin, so don't go through |DecodeFde|. |
| uint64_t pc_begin; |
| if (auto err = elf_->ReadEncoded(p, pc_begin, fde_address_encoding, elf_ptr_); |
| err.has_err()) { |
| return err; |
| } |
| debug_frame_map_.emplace(pc_begin, hdr_p); |
| } |
| } |
| |
| if (debug_frame_map_.empty()) { |
| return Error("empty .debug_frame"); |
| } |
| return Success(); |
| } |
| |
| Error CfiModule::DecodeCie(UnwindTableSectionType type, uint64_t cie_ptr, DwarfCie& cie) { |
| uint64_t cie_id; |
| if (auto err = DecodeCieFdeHdrAndAdvance(elf_, type, cie_ptr, cie.instructions_end, cie_id); |
| err.has_err()) { |
| return err; |
| } |
| |
| if (!CheckCieId(type, cie_id)) { |
| return Error("CIE_ID invalid for this version = %d, cie_id = %" PRIx64, type, cie_id); |
| } |
| |
| // Versions should match. |
| uint8_t this_version; |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, this_version); err.has_err()) { |
| return err; |
| } |
| if (this_version != type) { |
| return Error("unexpected CIE version: %d", this_version); |
| } |
| |
| std::string augmentation_string; |
| while (true) { |
| char ch; |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, ch); err.has_err()) { |
| return err; |
| } |
| if (ch) { |
| augmentation_string.push_back(ch); |
| } else { |
| break; |
| } |
| } |
| |
| if (type == UnwindTableSectionType::kDebugFrame) { |
| // Read the address_size. |
| uint8_t address_size; |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, address_size); err.has_err()) { |
| return err; |
| } |
| // Set fde_address_encoding to DW_EH_PE_datarel so that we can set the base to elf_ptr_. |
| switch (address_size) { |
| case 2: |
| cie.fde_address_encoding = 0x32; |
| break; |
| case 4: |
| cie.fde_address_encoding = 0x33; |
| break; |
| case 8: |
| cie.fde_address_encoding = 0x34; |
| break; |
| default: |
| return Error("unsupported CIE address_size: %d", address_size); |
| } |
| // Skip the segment_selector_size. |
| cie_ptr++; |
| } |
| |
| if (auto err = elf_->ReadULEB128AndAdvance(cie_ptr, cie.code_alignment_factor); err.has_err()) { |
| return err; |
| } |
| if (auto err = elf_->ReadSLEB128AndAdvance(cie_ptr, cie.data_alignment_factor); err.has_err()) { |
| return err; |
| } |
| if (type == UnwindTableSectionType::kDebugFrame) { |
| uint64_t return_address_register; |
| if (auto err = elf_->ReadULEB128AndAdvance(cie_ptr, return_address_register); err.has_err()) { |
| return err; |
| } |
| cie.return_address_register = static_cast<RegisterID>(return_address_register); |
| } else { |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, cie.return_address_register); err.has_err()) { |
| return err; |
| } |
| } |
| |
| if (augmentation_string.empty()) { |
| cie.instructions_begin = cie_ptr; |
| cie.fde_have_augmentation_data = false; |
| } else { |
| // DWARF standard doesn't say anything about the possibility of the augmentation string and |
| // we have never seen a use case of augmentation string in .debug_frame, which is understandable |
| // because the augmentation string is mainly useful for unwinding during an exception. |
| // For now, we don't support it. |
| if (type == UnwindTableSectionType::kDebugFrame) { |
| return Error("unsupported augmentation string in .debug_frame: %s", |
| augmentation_string.c_str()); |
| } |
| if (augmentation_string[0] != 'z') { |
| return Error("invalid augmentation string: %s", augmentation_string.c_str()); |
| } |
| uint64_t augmentation_length; |
| if (auto err = elf_->ReadULEB128AndAdvance(cie_ptr, augmentation_length); err.has_err()) { |
| return err; |
| } |
| cie.instructions_begin = cie_ptr + augmentation_length; |
| cie.fde_have_augmentation_data = true; |
| |
| for (char ch : augmentation_string) { |
| switch (ch) { |
| case 'L': |
| // LSDA (language-specific data area) is used by some languages such as C++ to ensure |
| // the correct destruction of objects on stack. We don't need to handle it. |
| uint8_t lsda_encoding; |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, lsda_encoding); err.has_err()) { |
| return err; |
| } |
| break; |
| case 'P': |
| // The personality routine is used to handle language and vendor-specific tasks to ensure |
| // the correct unwinding. We don't need to handle it. |
| uint8_t enc; |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, enc); err.has_err()) { |
| return err; |
| } |
| uint64_t personality; |
| if (auto err = elf_->ReadEncodedAndAdvance(cie_ptr, personality, enc, 0); err.has_err()) { |
| return err; |
| } |
| break; |
| case 'R': |
| if (auto err = elf_->ReadAndAdvance(cie_ptr, cie.fde_address_encoding); err.has_err()) { |
| return err; |
| } |
| break; |
| case 'S': |
| cie.is_signal_frame = true; |
| break; |
| } |
| } |
| } |
| |
| return Success(); |
| } |
| |
| Error CfiModule::DecodeFde(UnwindTableSectionType section_type, uint64_t fde_ptr, DwarfCie& cie, |
| DwarfFde& fde) { |
| uint64_t cie_offset; |
| if (auto err = |
| DecodeCieFdeHdrAndAdvance(elf_, section_type, fde_ptr, fde.instructions_end, cie_offset); |
| err.has_err()) { |
| return err; |
| } |
| |
| if (fde.instructions_end == 0) { |
| // This is a terminator FDE. |
| return Success(); |
| } |
| |
| uint64_t cie_ptr; |
| if (section_type == UnwindTableSectionType::kDebugFrame) { |
| cie_ptr = debug_frame_ptr_ + cie_offset; |
| } else { |
| cie_ptr = fde_ptr - 4 - cie_offset; |
| } |
| if (auto err = DecodeCie(section_type, cie_ptr, cie); err.has_err()) { |
| return err; |
| } |
| |
| if (auto err = |
| elf_->ReadEncodedAndAdvance(fde_ptr, fde.pc_begin, cie.fde_address_encoding, elf_ptr_); |
| err.has_err()) { |
| return err; |
| } |
| if (auto err = elf_->ReadEncodedAndAdvance(fde_ptr, fde.pc_end, cie.fde_address_encoding & 0x0F); |
| err.has_err()) { |
| return err; |
| } |
| fde.pc_end += fde.pc_begin; |
| |
| if (cie.fde_have_augmentation_data) { |
| uint64_t augmentation_length; |
| if (auto err = elf_->ReadULEB128AndAdvance(fde_ptr, augmentation_length); err.has_err()) { |
| return err; |
| } |
| // We don't really care about the augmentation data. |
| fde_ptr += augmentation_length; |
| } |
| fde.instructions_begin = fde_ptr; |
| |
| return Success(); |
| } |
| |
| } // namespace unwinder |