#include "binary/elf.hpp" #if IS_LINUX #include #include #include #include #include #include "binary/defs/elf_defs.hpp" namespace cpptrace { namespace detail { elf::elf( file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64 ) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {} Result elf::open_elf(const std::string& object_path) { auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); if(file == nullptr) { return internal_error("Unable to read object file {}", object_path); } // Initial checks/metadata auto magic = load_bytes>(file, 0); if(magic.is_error()) { return std::move(magic).unwrap_error(); } if(magic.unwrap_value() != (std::array{0x7F, 'E', 'L', 'F'})) { return internal_error("File is not ELF " + object_path); } auto ei_class = load_bytes(file, 4); if(ei_class.is_error()) { return std::move(ei_class).unwrap_error(); } bool is_64 = ei_class.unwrap_value() == 2; auto ei_data = load_bytes(file, 5); if(ei_data.is_error()) { return std::move(ei_data).unwrap_error(); } bool is_little_endian = ei_data.unwrap_value() == 1; auto ei_version = load_bytes(file, 6); if(ei_version.is_error()) { return std::move(ei_version).unwrap_error(); } if(ei_version.unwrap_value() != 1) { return internal_error("Unexpected ELF version " + object_path); } return elf(std::move(file), object_path, is_little_endian, is_64); } Result elf::get_module_image_base() { // get image base if(is_64) { return get_module_image_base_impl<64>(); } else { return get_module_image_base_impl<32>(); } } template Result elf::get_module_image_base_impl() { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using PHeader = typename std::conditional::type; auto header = get_header_info(); if(header.is_error()) { return std::move(header).unwrap_error(); } const auto& header_info = header.unwrap_value(); // PT_PHDR will occur at most once // Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011 // It should occur at the beginning but may as well loop just in case for(unsigned i = 0; i < header_info.e_phnum; i++) { auto loaded_ph = load_bytes(file, header_info.e_phoff + header_info.e_phentsize * i); if(loaded_ph.is_error()) { return std::move(loaded_ph).unwrap_error(); } const PHeader& program_header = loaded_ph.unwrap_value(); if(byteswap_if_needed(program_header.p_type) == PT_PHDR) { return byteswap_if_needed(program_header.p_vaddr) - byteswap_if_needed(program_header.p_offset); } } // Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct. return 0; } std::string elf::lookup_symbol(frame_ptr pc) { // TODO: Also search the SHT_DYNSYM at some point, maybe auto symtab_ = get_symtab(); if(symtab_.is_error()) { return ""; } auto& symtab = symtab_.unwrap_value(); auto strtab_ = get_strtab(symtab.strtab_link); if(strtab_.is_error()) { return ""; } auto& strtab = strtab_.unwrap_value(); auto it = first_less_than_or_equal( symtab.entries.begin(), symtab.entries.end(), pc, [] (frame_ptr pc, const symtab_entry& entry) { return pc < entry.st_value; } ); if(it == symtab.entries.end()) { return ""; } if(pc <= it->st_value + it->st_size) { return strtab.data() + it->st_name; } return ""; } template::value, int>::type> T elf::byteswap_if_needed(T value) { if(cpptrace::detail::is_little_endian() == is_little_endian) { return value; } else { return byteswap(value); } } Result elf::get_header_info() { if(header) { Result r = header.unwrap(); return std::ref(header.unwrap()); } if(tried_to_load_header) { return internal_error("previous header load failed " + object_path); } tried_to_load_header = true; if(is_64) { return get_header_info_impl<64>(); } else { return get_header_info_impl<32>(); } } template Result elf::get_header_info_impl() { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using Header = typename std::conditional::type; auto loaded_header = load_bytes
(file, 0); if(loaded_header.is_error()) { return std::move(loaded_header).unwrap_error(); } const Header& file_header = loaded_header.unwrap_value(); if(file_header.e_ehsize != sizeof(Header)) { return internal_error("ELF file header size mismatch" + object_path); } header_info info; info.e_phoff = byteswap_if_needed(file_header.e_phoff); info.e_phnum = byteswap_if_needed(file_header.e_phnum); info.e_phentsize = byteswap_if_needed(file_header.e_phentsize); info.e_shoff = byteswap_if_needed(file_header.e_shoff); info.e_shnum = byteswap_if_needed(file_header.e_shnum); info.e_shentsize = byteswap_if_needed(file_header.e_shentsize); header = info; return header.unwrap(); } Result&, internal_error> elf::get_sections() { if(did_load_sections) { return sections; } if(tried_to_load_sections) { return internal_error("previous sections load failed " + object_path); } tried_to_load_sections = true; if(is_64) { return get_sections_impl<64>(); } else { return get_sections_impl<32>(); } } template Result&, internal_error> elf::get_sections_impl() { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using SHeader = typename std::conditional::type; auto header = get_header_info(); if(header.is_error()) { return std::move(header).unwrap_error(); } const auto& header_info = header.unwrap_value(); for(unsigned i = 0; i < header_info.e_shnum; i++) { auto loaded_sh = load_bytes(file, header_info.e_shoff + header_info.e_shentsize * i); if(loaded_sh.is_error()) { return std::move(loaded_sh).unwrap_error(); } const SHeader& section_header = loaded_sh.unwrap_value(); section_info info; info.sh_type = byteswap_if_needed(section_header.sh_type); info.sh_addr = byteswap_if_needed(section_header.sh_addr); info.sh_offset = byteswap_if_needed(section_header.sh_offset); info.sh_size = byteswap_if_needed(section_header.sh_size); info.sh_entsize = byteswap_if_needed(section_header.sh_entsize); info.sh_link = byteswap_if_needed(section_header.sh_link); sections.push_back(info); } did_load_sections = true; return sections; } Result&, internal_error> elf::get_strtab(std::size_t index) { auto res = strtab_entries.insert({index, {}}); auto it = res.first; auto did_insert = res.second; auto& entry = it->second; if(!did_insert) { if(entry.did_load_strtab) { return entry.data; } if(entry.tried_to_load_strtab) { return internal_error("previous strtab load failed {}", object_path); } } entry.tried_to_load_strtab = true; auto sections_ = get_sections(); if(sections_.is_error()) { return std::move(sections_).unwrap_error(); } const auto& sections = sections_.unwrap_value(); if(index >= sections.size()) { return internal_error("requested strtab section index out of range"); } const auto& section = sections[index]; if(section.sh_type != SHT_STRTAB) { return internal_error("requested strtab section not a strtab (requested {} of {})", index, object_path); } entry.data.resize(section.sh_size + 1); if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) { return internal_error("fseek error while loading elf string table"); } if(std::fread(entry.data.data(), sizeof(char), section.sh_size, file) != section.sh_size) { return internal_error("fread error while loading elf string table"); } entry.data[section.sh_size] = 0; // just out of an abundance of caution entry.did_load_strtab = true; return entry.data; } Result elf::get_symtab() { if(did_load_symtab) { return symtab; } if(tried_to_load_symtab) { return internal_error("previous strtab load failed {}", object_path); } tried_to_load_symtab = true; if(is_64) { return get_symtab_impl<64>(); } else { return get_symtab_impl<32>(); } } template Result elf::get_symtab_impl() { // https://refspecs.linuxfoundation.org/elf/elf.pdf // page 66: only one sht_symtab and sht_dynsym section per file // page 32: symtab spec static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using SymEntry = typename std::conditional::type; auto sections_ = get_sections(); if(sections_.is_error()) { return std::move(sections_).unwrap_error(); } const auto& sections = sections_.unwrap_value(); for(const auto& section : sections) { if(section.sh_type == SHT_SYMTAB) { if(section.sh_entsize != sizeof(SymEntry)) { return internal_error("elf seems corrupted, sym entry mismatch {}", object_path); } if(section.sh_size % section.sh_entsize != 0) { return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", object_path); } std::vector buffer(section.sh_size / section.sh_entsize); if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) { return internal_error("fseek error while loading elf symbol table"); } if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) { return internal_error("fread error while loading elf symbol table"); } symtab.entries.reserve(buffer.size()); for(const auto& entry : buffer) { symtab_entry normalized; normalized.st_name = byteswap_if_needed(entry.st_name); normalized.st_info = byteswap_if_needed(entry.st_info); normalized.st_other = byteswap_if_needed(entry.st_other); normalized.st_shndx = byteswap_if_needed(entry.st_shndx); normalized.st_value = byteswap_if_needed(entry.st_value); normalized.st_size = byteswap_if_needed(entry.st_size); symtab.entries.push_back(normalized); } std::sort(symtab.entries.begin(), symtab.entries.end(), [] (const symtab_entry& a, const symtab_entry& b) { return a.st_value < b.st_value; }); symtab.strtab_link = section.sh_link; did_load_symtab = true; return symtab; } } // OK to not have a symbol table did_load_symtab = true; return symtab; } } } #endif