| // Copyright 2018 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/elflib/elflib.h" |
| |
| #include <zircon/assert.h> |
| |
| #include <algorithm> |
| #include <string> |
| |
| namespace elflib { |
| namespace { |
| |
| // NT_GNU_BUILD_ID identifier. |
| constexpr uint64_t kNoteGnuBuildId = 3; |
| |
| // Pull a null-terminated string out of an array of bytes at an offset. Returns |
| // empty string if there is no null terminator. |
| std::string GetNullTerminatedStringAt(const uint8_t* data, size_t data_length, |
| size_t offset) { |
| size_t check = offset; |
| |
| while (check < data_length && data[check]) { |
| check++; |
| } |
| |
| if (check >= data_length) { |
| return std::string(); |
| } |
| |
| const char* start = reinterpret_cast<const char*>(data) + offset; |
| |
| return std::string(start); |
| } |
| |
| // Given a name, a symbol table (sized array of Elf64_Sym), and an accessor for |
| // a corresponding string table, find the symbol with the given name. |
| const Elf64_Sym* GetSymbolFromTable( |
| const std::string& name, const std::pair<const Elf64_Sym*, size_t>& symtab, |
| std::function<std::optional<std::string>(uint64_t)> get_string) { |
| if (!symtab.first) { |
| return nullptr; |
| } |
| |
| const Elf64_Sym* symbols = symtab.first; |
| const Elf64_Sym* end = symtab.first + symtab.second; |
| |
| for (auto symbol = symbols; symbol <= end; symbol++) { |
| auto got_name = get_string(symbol->st_name); |
| |
| if (got_name && *got_name == name) { |
| return symbol; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| std::optional<std::map<std::string, Elf64_Sym>> SymtabToMap( |
| const std::pair<const Elf64_Sym*, size_t>& symtab, |
| const ElfLib::MemoryRegion& strtab) { |
| auto [symtab_ptr, symtab_size] = symtab; |
| if (!symtab_ptr) |
| return std::nullopt; |
| |
| std::map<std::string, Elf64_Sym> out; |
| |
| const Elf64_Sym* symbols = symtab_ptr; |
| const Elf64_Sym* end = symtab_ptr + symtab_size; |
| for (auto symbol = symbols; symbol != end; symbol++) { |
| auto sym_name = |
| GetNullTerminatedStringAt(strtab.ptr, strtab.size, symbol->st_name); |
| out[sym_name] = *symbol; |
| } |
| |
| return out; |
| } |
| |
| } // namespace |
| |
| // Proxy object for whatever address space we're exploring. |
| class ElfLib::MemoryAccessor { |
| public: |
| virtual ~MemoryAccessor() = default; |
| |
| virtual const uint8_t* GetMemory(uint64_t mapped_address, |
| size_t mapped_size) = 0; |
| }; |
| |
| ElfLib::ElfLib(std::unique_ptr<MemoryAccessor>&& memory, |
| ElfLib::AddressMode address_mode) |
| : address_mode_(address_mode), memory_(std::move(memory)) {} |
| |
| ElfLib::~ElfLib() = default; |
| |
| std::unique_ptr<ElfLib> ElfLib::Create(std::unique_ptr<MemoryAccessor>&& memory, |
| ElfLib::AddressMode address_mode) { |
| std::unique_ptr<ElfLib> out = |
| std::make_unique<ElfLib>(std::move(memory), address_mode); |
| |
| auto header = reinterpret_cast<const Elf64_Ehdr*>( |
| out->memory_->GetMemory(0, sizeof(Elf64_Ehdr))); |
| |
| if (!header) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| out->header_ = *header; |
| |
| // Header magic should be correct. |
| if (!std::equal(ElfMagic, ElfMagic + 4, out->header_.e_ident)) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| // We only support 64-bit binaries. |
| if (out->header_.e_ident[EI_CLASS] != ELFCLASS64) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| const uint32_t kOne = 1; |
| |
| // Endianness of the file has to match the endianness of the host. To do the |
| // endianness check, we snip the first byte off of a 4-byte word. If it |
| // contains the LSB (a value of 1) we are on a little-endian machine. |
| if (out->header_.e_ident[EI_DATA] == ELFDATA2MSB && |
| *reinterpret_cast<const char*>(&kOne)) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| // Version field has only had one correct value for most of the life of the |
| // spec. |
| if (out->header_.e_ident[EI_VERSION] != EV_CURRENT) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| if (out->header_.e_version != EV_CURRENT) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| // We'll skip EI_OSABI and EI_ABIVERSION as well as e_machine and e_type. In |
| // either case any valid value should be fine. We just don't screen for |
| // invalid values. |
| |
| // We don't support non-standard section header sizes. Stripped binaries that |
| // don't have sections sometimes zero out the shentsize, so we can ignore it |
| // if we have no sections. |
| if (out->header_.e_shnum > 0 && |
| out->header_.e_shentsize != sizeof(Elf64_Shdr)) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| // We don't support non-standard program header sizes. |
| if (out->header_.e_phentsize != sizeof(Elf64_Phdr)) { |
| return std::unique_ptr<ElfLib>(); |
| } |
| |
| return out; |
| } |
| |
| std::unique_ptr<ElfLib> ElfLib::Create(FILE* fp, ElfLib::Ownership owned) { |
| class FileAccessor : public MemoryAccessor { |
| public: |
| FileAccessor(FILE* fp, bool take_ownership) |
| : file_(fp), take_ownership_(take_ownership) {} |
| |
| ~FileAccessor() { |
| if (file_ && take_ownership_) { |
| fclose(file_); |
| } |
| } |
| |
| const uint8_t* GetMemory(uint64_t offset, size_t size) override { |
| if (!file_) { |
| return nullptr; |
| } |
| |
| auto& ret = data_[std::make_pair(offset, size)]; |
| if (ret.size() == size) { |
| return ret.data(); |
| } |
| |
| ret.resize(size); |
| |
| fseek(file_, offset, SEEK_SET); |
| if (fread(ret.data(), 1, size, file_) != size) { |
| return nullptr; |
| } |
| |
| return ret.data(); |
| } |
| |
| private: |
| FILE* file_ = nullptr; |
| bool take_ownership_; |
| |
| // Elflib treats our read functions like they're random-access, so we cache |
| // the results of reads. We could end up with overlaps in this map, though |
| // in practice we don't in today's code. |
| std::map<std::pair<uint64_t, size_t>, std::vector<uint8_t>> data_; |
| }; |
| |
| return Create(std::make_unique<FileAccessor>( |
| fp, owned == ElfLib::Ownership::kTakeOwnership), |
| AddressMode::kFile); |
| } |
| |
| std::unique_ptr<ElfLib> ElfLib::Create(const uint8_t* mem, size_t size) { |
| class DataAccessor : public MemoryAccessor { |
| public: |
| DataAccessor(const uint8_t* mem, size_t size) : mem_(mem), size_(size) {} |
| |
| const uint8_t* GetMemory(uint64_t offset, size_t size) override { |
| if (size + offset > size_) { |
| return nullptr; |
| } |
| |
| return mem_ + offset; |
| } |
| |
| private: |
| const uint8_t* mem_; |
| size_t size_; |
| }; |
| |
| return Create(std::make_unique<DataAccessor>(mem, size), AddressMode::kFile); |
| } |
| |
| std::unique_ptr<ElfLib> ElfLib::Create( |
| std::function<bool(uint64_t, std::vector<uint8_t>*)> fetch, |
| ElfLib::AddressMode address_mode) { |
| class CallbackAccessor : public MemoryAccessor { |
| public: |
| CallbackAccessor(std::function<bool(uint64_t, std::vector<uint8_t>*)> fetch) |
| : fetch_(fetch) {} |
| |
| const uint8_t* GetMemory(uint64_t offset, size_t size) override { |
| #ifndef NDEBUG |
| auto iter = data_.upper_bound(offset); |
| |
| if (iter != data_.begin()) { |
| --iter; |
| } |
| |
| if (iter != data_.end() && iter->first <= offset && |
| iter->first + iter->second.size() > offset) { |
| ZX_DEBUG_ASSERT(iter->first == offset && iter->second.size() == size); |
| } |
| #endif // NDEBUG |
| for (const auto& range : data_[offset]) { |
| if (range.size() >= size) { |
| return range.data(); |
| } |
| } |
| |
| auto& vec = data_[offset].emplace_back(size, 0); |
| |
| if (!fetch_(offset, &vec)) { |
| return nullptr; |
| } |
| |
| return vec.data(); |
| } |
| |
| private: |
| std::function<bool(uint64_t, std::vector<uint8_t>*)> fetch_; |
| std::map<uint64_t, std::vector<std::vector<uint8_t>>> data_; |
| }; |
| |
| return Create(std::make_unique<CallbackAccessor>(std::move(fetch)), |
| address_mode); |
| } |
| |
| std::unique_ptr<ElfLib> ElfLib::Create(const std::string& path) { |
| return Create(fopen(path.c_str(), "r"), ElfLib::Ownership::kTakeOwnership); |
| } |
| |
| bool ElfLib::SetDebugData(std::unique_ptr<ElfLib> debug) { |
| if (debug_) { |
| return false; |
| } |
| |
| if (debug->debug_) { |
| return false; |
| } |
| |
| if (header_.e_shnum > 0) { |
| return false; |
| } |
| |
| debug_ = std::move(debug); |
| |
| debug_->LoadSectionNames(); |
| section_names_ = debug_->section_names_; |
| sections_ = debug_->sections_; |
| |
| LoadProgramHeaders(); |
| std::map<size_t, size_t> bounds; |
| for (size_t i = 0; i < segments_.size(); i++) { |
| if (segments_[i].p_type != PT_LOAD) { |
| continue; |
| } |
| |
| bounds[segments_[i].p_vaddr] = i; |
| } |
| |
| for (auto& section : sections_) { |
| if (section.sh_type != SHT_NOBITS) { |
| // When we encounter an SHT_NULL section and we have debug data, we'll |
| // consult the debug data for that section. |
| section.sh_type = SHT_NULL; |
| continue; |
| } |
| |
| // Find the first segment starting at or before this section by finding the |
| // first segment starting at or after this section, and backing up one |
| // entry if necessary. |
| auto found = bounds.lower_bound(section.sh_addr); |
| if (found == bounds.end()) { |
| continue; |
| } |
| if (found->first != section.sh_addr) { |
| if (found == bounds.begin()) { |
| continue; |
| } |
| |
| --found; |
| } |
| |
| auto& segment = segments_[found->second]; |
| |
| if (segment.p_vaddr + segment.p_memsz <= section.sh_addr) { |
| continue; |
| } |
| |
| section.sh_offset = segment.p_offset + (section.sh_addr - segment.p_vaddr); |
| section.sh_type = SHT_PROGBITS; |
| } |
| |
| return true; |
| } |
| |
| const Elf64_Shdr* ElfLib::GetSectionHeader(size_t section) { |
| // Processes may not map the section headers at all, so we don't look for |
| // section headers unless we're in file mode. |
| if (address_mode_ == AddressMode::kFile && sections_.empty()) { |
| auto sections = reinterpret_cast<const Elf64_Shdr*>(memory_->GetMemory( |
| header_.e_shoff, sizeof(Elf64_Shdr) * header_.e_shnum)); |
| |
| if (!sections) { |
| return nullptr; |
| } |
| |
| std::copy(sections, sections + header_.e_shnum, |
| std::back_inserter(sections_)); |
| } |
| |
| if (section >= sections_.size()) { |
| return nullptr; |
| } |
| |
| return §ions_[section]; |
| } |
| |
| bool ElfLib::LoadProgramHeaders() { |
| if (!segments_.empty()) { |
| return true; |
| } |
| |
| auto segments = reinterpret_cast<const Elf64_Phdr*>(memory_->GetMemory( |
| header_.e_phoff, sizeof(Elf64_Phdr) * header_.e_phnum)); |
| |
| if (!segments) { |
| return false; |
| } |
| |
| std::copy(segments, segments + header_.e_phnum, |
| std::back_inserter(segments_)); |
| return true; |
| } |
| |
| const std::vector<Elf64_Phdr>& ElfLib::GetSegmentHeaders() { |
| LoadProgramHeaders(); |
| return segments_; |
| } |
| |
| ElfLib::MemoryRegion ElfLib::GetSegmentData(size_t segment) { |
| LoadProgramHeaders(); |
| |
| if (segment > segments_.size()) { |
| return {}; |
| } |
| |
| const Elf64_Phdr* header = &segments_[segment]; |
| ElfLib::MemoryRegion result; |
| |
| if (address_mode_ == AddressMode::kFile) { |
| result.ptr = memory_->GetMemory(header->p_offset, header->p_filesz); |
| result.size = header->p_filesz; |
| } else { |
| result.ptr = memory_->GetMemory(header->p_vaddr, header->p_memsz); |
| result.size = header->p_memsz; |
| } |
| |
| return result; |
| } |
| |
| std::optional<std::vector<uint8_t>> ElfLib::GetNote(const std::string& name, |
| uint64_t type) { |
| LoadProgramHeaders(); |
| |
| for (size_t idx = 0; idx < segments_.size(); idx++) { |
| if (segments_[idx].p_type != PT_NOTE) { |
| continue; |
| } |
| |
| auto data = GetSegmentData(idx); |
| |
| const Elf64_Nhdr* header; |
| size_t namesz_padded; |
| size_t descsz_padded; |
| |
| for (const uint8_t* pos = data.ptr; pos < data.ptr + data.size; |
| pos += sizeof(Elf64_Nhdr) + namesz_padded + descsz_padded) { |
| header = reinterpret_cast<const Elf64_Nhdr*>(pos); |
| namesz_padded = (header->n_namesz + 3) & ~3UL; |
| descsz_padded = (header->n_descsz + 3) & ~3UL; |
| |
| if (header->n_type != type) { |
| continue; |
| } |
| |
| auto name_data = pos + sizeof(Elf64_Nhdr); |
| std::string entry_name(reinterpret_cast<const char*>(name_data), |
| header->n_namesz - 1); |
| |
| if (entry_name == name) { |
| auto desc_data = name_data + namesz_padded; |
| |
| return std::vector(desc_data, desc_data + header->n_descsz); |
| } |
| } |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::string ElfLib::GetGNUBuildID() { |
| auto note = GetNote("GNU", kNoteGnuBuildId); |
| if (!note) { |
| return std::string(); |
| } |
| |
| std::string ret; |
| |
| for (const auto& byte : *note) { |
| char buf[3]; |
| snprintf(buf, 3, "%02x", byte); |
| ret += buf; |
| } |
| |
| return ret; |
| } |
| |
| ElfLib::MemoryRegion ElfLib::GetSectionData(size_t section) { |
| const Elf64_Shdr* header = GetSectionHeader(section); |
| |
| if (!header) { |
| return {}; |
| } |
| |
| if (header->sh_type == SHT_NULL) { |
| if (debug_) { |
| return debug_->GetSectionData(section); |
| } |
| |
| return {}; |
| } |
| |
| if (address_mode_ == AddressMode::kFile && header->sh_type == SHT_NOBITS) { |
| return {}; |
| } |
| |
| ElfLib::MemoryRegion result; |
| result.size = header->sh_size; |
| |
| if (address_mode_ == AddressMode::kFile) { |
| result.ptr = memory_->GetMemory(header->sh_offset, header->sh_size); |
| } else { |
| result.ptr = memory_->GetMemory(header->sh_addr, header->sh_size); |
| } |
| |
| return result; |
| } |
| |
| bool ElfLib::LoadSectionNames() { |
| if (section_names_.size() != 0) { |
| return true; |
| } |
| |
| auto section_name_data = GetSectionData(header_.e_shstrndx); |
| |
| if (!section_name_data.ptr) { |
| return false; |
| } |
| |
| size_t idx = 0; |
| // We know sections_ is populated from the GetSectionData above |
| for (const auto& section : sections_) { |
| auto name = GetNullTerminatedStringAt( |
| section_name_data.ptr, section_name_data.size, section.sh_name); |
| section_names_[name] = idx; |
| |
| idx++; |
| } |
| |
| return true; |
| } |
| |
| ElfLib::MemoryRegion ElfLib::GetSectionData(const std::string& name) { |
| if (!LoadSectionNames()) { |
| return {}; |
| } |
| |
| const auto& iter = section_names_.find(name); |
| |
| if (iter == section_names_.end()) { |
| return {}; |
| } |
| |
| return GetSectionData(iter->second); |
| } |
| |
| bool ElfLib::LoadDynamicSymbols() { |
| if (did_load_dynamic_symbols_) { |
| return true; |
| } |
| |
| did_load_dynamic_symbols_ = true; |
| |
| LoadProgramHeaders(); |
| |
| for (size_t idx = 0; idx < segments_.size(); idx++) { |
| if (segments_[idx].p_type != PT_DYNAMIC) { |
| continue; |
| } |
| |
| auto data = GetSegmentData(idx); |
| |
| if (!data.ptr) { |
| return false; |
| } |
| |
| const Elf64_Dyn* start = reinterpret_cast<const Elf64_Dyn*>(data.ptr); |
| const Elf64_Dyn* end = start + (data.size / sizeof(Elf64_Dyn)); |
| |
| for (auto dyn = start; dyn != end; dyn++) { |
| if (dyn->d_tag == DT_STRTAB) { |
| if (dynstr_.offset) { |
| Warn("Multiple DT_STRTAB entries found."); |
| continue; |
| } |
| |
| dynstr_.offset = dyn->d_un.d_ptr; |
| } else if (dyn->d_tag == DT_SYMTAB) { |
| if (dynsym_.offset) { |
| Warn("Multiple DT_SYMTAB entries found."); |
| continue; |
| } |
| |
| dynsym_.offset = dyn->d_un.d_ptr; |
| } else if (dyn->d_tag == DT_STRSZ) { |
| if (dynstr_.size) { |
| Warn("Multiple DT_STRSZ entries found."); |
| continue; |
| } |
| |
| dynstr_.size = dyn->d_un.d_val; |
| } else if (dyn->d_tag == DT_HASH) { |
| // A note: The old DT_HASH style of hash table is considered legacy on |
| // Fuchsia. Technically a binary could provide both styles of hash |
| // table and we can produce a sane result in that case, so this code |
| // ignores DT_HASH. |
| Warn("Old style DT_HASH table found."); |
| } else if (dyn->d_tag == DT_GNU_HASH) { |
| if (dynsym_.size) { |
| Warn("Multiple DT_GNU_HASH entries found."); |
| continue; |
| } |
| auto addr = dyn->d_un.d_ptr; |
| |
| // Our elf header doesn't provide the DT_GNU_HASH header structure. |
| struct Header { |
| uint32_t nbuckets; |
| uint32_t symoffset; |
| uint32_t bloom_size; |
| uint32_t bloom_shift; |
| } header; |
| |
| static_assert(sizeof(Header) == 16); |
| |
| auto data = memory_->GetMemory(addr, sizeof(header)); |
| |
| if (!data) { |
| continue; |
| } |
| |
| header = *reinterpret_cast<const Header*>(data); |
| |
| addr += sizeof(header); |
| addr += 8 * header.bloom_size; |
| |
| size_t bucket_bytes = 4 * header.nbuckets; |
| auto bucket_data = memory_->GetMemory(addr, bucket_bytes); |
| |
| if (!bucket_data) { |
| continue; |
| } |
| |
| const uint32_t* buckets = |
| reinterpret_cast<const uint32_t*>(bucket_data); |
| uint32_t max_bucket = |
| *std::max_element(buckets, buckets + header.nbuckets); |
| |
| if (max_bucket < header.symoffset) { |
| dynsym_.size = max_bucket; |
| continue; |
| } |
| |
| addr += bucket_bytes; |
| addr += (max_bucket - header.symoffset) * 4; |
| |
| for (uint32_t nsyms = max_bucket + 1;; nsyms++, addr += 4) { |
| auto chain_entry_data = memory_->GetMemory(addr, 4); |
| |
| if (!chain_entry_data) { |
| break; |
| } |
| |
| uint32_t chain_entry = |
| *reinterpret_cast<const uint32_t*>(chain_entry_data); |
| |
| if (chain_entry & 1) { |
| dynsym_.size = nsyms; |
| break; |
| } |
| } |
| } else if (dyn->d_tag == DT_PLTREL) { |
| dynamic_plt_use_rela_ = dyn->d_un.d_val == DT_RELA; |
| } |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| std::map<std::string, uint64_t> ElfLib::GetPLTOffsets() { |
| // We assume Fuchsia's defaults for each architecture. We could perhaps check |
| // ELF_OSABI to firm up those assumptions. Fuchsia sets it to NONE. |
| switch (header_.e_machine) { |
| case EM_X86_64: |
| return GetPLTOffsetsX64(); |
| default: |
| Warn("Architecture doesn't support GetPLTOffsets."); |
| return {}; |
| } |
| } |
| |
| std::map<std::string, uint64_t> ElfLib::GetPLTOffsetsX64() { |
| // A PLT entry consists of 3 x86 instructions: a jump using a 6-byte |
| // encoding, a push of one 32 bit value on to the stack, and another jump, |
| // this one using a 5-byte encoding. |
| // |
| // We don't care about either of the jumps, but we want the value that is |
| // pushed as it is the index into the relocation table which will tell us |
| // what symbol this entry is for. |
| struct PltEntry { |
| char first_jump[6]; |
| char push_opcode; |
| uint32_t index; |
| char second_jump[5]; |
| } __attribute__((packed, aligned(1))); |
| |
| static_assert(sizeof(PltEntry) == 16); |
| |
| // We'd prefer if this works but we can get by without it, so we're not |
| // checking the return value. |
| LoadDynamicSymbols(); |
| |
| if (!LoadSectionNames()) { |
| return {}; |
| } |
| |
| if (!dynamic_plt_use_rela_) { |
| Warn("Assuming Elf64_Rela PLT relocation format."); |
| } else if (!*dynamic_plt_use_rela_) { |
| Warn("Elf64_Rel style PLT Relocations unsupported."); |
| return {}; |
| } |
| |
| auto plt_section = section_names_.find(".plt"); |
| |
| if (plt_section == section_names_.end()) { |
| return {}; |
| } |
| |
| auto plt_idx = plt_section->second; |
| |
| auto plt_shdr = GetSectionHeader(plt_idx); |
| auto plt_memory = GetSectionData(plt_idx); |
| |
| if (!plt_shdr || !plt_memory.ptr) { |
| return {}; |
| } |
| |
| auto plt_load_addr = plt_shdr->sh_addr; |
| |
| auto plt = reinterpret_cast<const PltEntry*>(plt_memory.ptr); |
| auto plt_end = plt + plt_memory.size / sizeof(PltEntry); |
| |
| auto reloc_memory = GetSectionData(".rela.plt"); |
| |
| if (!reloc_memory.ptr) { |
| return {}; |
| } |
| |
| auto reloc = reinterpret_cast<const Elf64_Rela*>(reloc_memory.ptr); |
| auto reloc_count = reloc_memory.size / sizeof(Elf64_Rela); |
| |
| ElfLib::MemoryRegion dynsym_mem = GetSectionData(".dynsym"); |
| |
| if (!dynsym_mem.ptr) { |
| return {}; |
| } |
| |
| auto symtab = reinterpret_cast<const Elf64_Sym*>(dynsym_mem.ptr); |
| auto sym_count = dynsym_mem.size / sizeof(Elf64_Sym); |
| |
| ElfLib::MemoryRegion dynstr_mem = GetSectionData(".dynstr"); |
| |
| if (!dynstr_mem.ptr) { |
| return {}; |
| } |
| |
| auto pos = plt + 1; // First PLT entry is special. Ignore it. |
| uint64_t idx = 1; |
| |
| std::map<std::string, uint64_t> ret; |
| |
| for (; pos != plt_end; pos++, idx++) { |
| if (pos->push_opcode != 0x68) { |
| Warn("Push OpCode not found where expected in PLT."); |
| continue; |
| } |
| |
| if (pos->index >= reloc_count) { |
| Warn("PLT referenced reloc outside reloc table."); |
| continue; |
| } |
| |
| auto sym_idx = reloc[pos->index].getSymbol(); |
| |
| if (sym_idx >= sym_count) { |
| Warn("PLT reloc referenced symbol outside symbol table."); |
| continue; |
| } |
| |
| auto name = GetNullTerminatedStringAt(dynstr_mem.ptr, dynstr_mem.size, |
| symtab[sym_idx].st_name); |
| |
| if (!name.size()) { |
| Warn("PLT symbol name could not be retrieved."); |
| continue; |
| } |
| |
| ret[name] = idx * sizeof(PltEntry) + plt_load_addr; |
| } |
| |
| return ret; |
| } |
| |
| std::optional<std::string> ElfLib::GetDynamicString(size_t offset) { |
| if (!LoadDynamicSymbols() || !dynstr_.IsValid()) { |
| return std::nullopt; |
| } |
| |
| auto data = memory_->GetMemory(*dynstr_.offset, *dynstr_.size); |
| |
| if (!data) { |
| return std::nullopt; |
| } |
| |
| return GetNullTerminatedStringAt(data, *dynstr_.size, offset); |
| } |
| |
| std::optional<std::string> ElfLib::GetString(size_t offset) { |
| auto string_data = GetSectionData(".strtab"); |
| |
| if (!string_data.ptr) { |
| return std::nullopt; |
| } |
| |
| return GetNullTerminatedStringAt(string_data.ptr, string_data.size, offset); |
| } |
| |
| std::pair<const Elf64_Sym*, size_t> ElfLib::GetSymtab() { |
| ElfLib::MemoryRegion symtab = GetSectionData(".symtab"); |
| |
| if (symtab.ptr) { |
| const Elf64_Sym* symbols = reinterpret_cast<const Elf64_Sym*>(symtab.ptr); |
| |
| return std::make_pair(symbols, symtab.size / sizeof(Elf64_Sym)); |
| } |
| |
| return std::make_pair(nullptr, 0); |
| } |
| |
| std::pair<const Elf64_Sym*, size_t> ElfLib::GetDynamicSymtab() { |
| if (!LoadDynamicSymbols()) { |
| return std::make_pair(nullptr, 0); |
| } |
| |
| if (!dynsym_.IsValid()) { |
| return std::make_pair(nullptr, 0); |
| } |
| |
| auto memory = |
| memory_->GetMemory(*dynsym_.offset, *dynsym_.size * sizeof(Elf64_Sym)); |
| |
| return std::make_pair(reinterpret_cast<const Elf64_Sym*>(memory), |
| *dynsym_.size); |
| } |
| |
| const Elf64_Sym* ElfLib::GetSymbol(const std::string& name) { |
| return GetSymbolFromTable(name, GetSymtab(), |
| [this](uint64_t idx) { return GetString(idx); }); |
| } |
| |
| const Elf64_Sym* ElfLib::GetDynamicSymbol(const std::string& name) { |
| return GetSymbolFromTable(name, GetDynamicSymtab(), [this](uint64_t idx) { |
| return GetDynamicString(idx); |
| }); |
| } |
| |
| std::optional<std::map<std::string, Elf64_Sym>> ElfLib::GetAllSymbols() { |
| return SymtabToMap(GetSymtab(), GetSectionData(".strtab")); |
| } |
| |
| std::optional<std::map<std::string, Elf64_Sym>> ElfLib::GetAllDynamicSymbols() { |
| if (!LoadDynamicSymbols() || !dynstr_.IsValid()) { |
| return std::nullopt; |
| } |
| |
| return SymtabToMap(GetDynamicSymtab(), |
| ElfLib::MemoryRegion{.ptr = memory_->GetMemory( |
| *dynstr_.offset, *dynstr_.size), |
| .size = *dynstr_.size}); |
| } |
| |
| } // namespace elflib |