From ab8eade87b2e49b8b577dab91115edf8e9b2b86d Mon Sep 17 00:00:00 2001 From: o2alexanderfedin Date: Fri, 7 Aug 2026 14:18:54 -0700 Subject: [PATCH 1/2] Read the ELF through LLVM instead of bfd, libdwarf and libelf The lifter linked three binutils libraries to do what LLVM already does: bfd to open the binary and walk its sections and symbols, libelf to read the ehdr, the program headers and symbol sizes, and libdwarf to recover function entries from `.eh_frame` when the binary is stripped. All three are replaced by llvm::object::ELFObjectFile and llvm::DWARFContext, which the lifter already links through remill. Three dependencies are deleted, not ported -- and libiberty goes with them, since it was only ever bfd's transitive requirement. Verified byte-identical on both loader paths against the stock lifter shipped in the :arm64 image, on a static-glibc aarch64 hello: not stripped (.symtab path) 900 funcs bc ec2dc6f9... wasm b15e9a6f... 5 654 545 B stripped (.eh_frame path) 899 funcs bc 883bbf48... wasm 735ce583... 5 624 783 B The stripped arm is new. The recorded harness only ever lifted a non-stripped binary, so `GetSblFromEhFrame` -- the entire libdwarf half -- was never executed by it. THE HARNESS EARNED ITS KEEP AND THE DEFECT IS WORTH RECORDING. The first build of this port was green, linked, and emitted 900 functions, and its bitcode was WRONG: ec2dc6f9 became 1b89e054. Cause: STT_GNU_IFUNC. bfd in this binutils does not raise BSF_FUNCTION for it, so the original filter admitted none of the binary's 10 ifunc symbols; I admitted them from memory of bfd's source. That shifted every later symbol index by 10 and, because the sort is by address and std::sort is NOT stable, re-scrambled which alias won at the 96 addresses where glibc puts several names on one function. Dropping STT_GNU_IFUNC restored the digest exactly. The count was never wrong -- 900 both times -- so a function-count check would have passed the defect through. `asection *` is replaced by `SectionRange *`, a name/vma/size triple owned by the loader. Containment queries run over EVERY section, unfiltered, because bfd's did; `ELFSection` is the wrong type for that job since it drops sections based at 0x0. TraceManager loses its include, and with the LLVM objects held behind a pimpl no LLVM header reaches Loader.h either, so the header's third-party surface is now zero. No LLVM component is named in CMakeLists on purpose: llvm_map_components_to_libnames resolves to the STATIC archives, whose interface demands zstd::libzstd_shared, which is absent in the build image and fails configuration outright. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Nu4maFxHPncwvdP8XcSEbD --- lifter/Binary/Loader.cpp | 575 ++++++++++++++++++--------------------- lifter/Binary/Loader.h | 51 ++-- lifter/CMakeLists.txt | 34 +-- lifter/TraceManager.cpp | 7 +- lifter/TraceManager.h | 4 +- 5 files changed, 304 insertions(+), 367 deletions(-) diff --git a/lifter/Binary/Loader.cpp b/lifter/Binary/Loader.cpp index 38e906f5..ee4bedb0 100644 --- a/lifter/Binary/Loader.cpp +++ b/lifter/Binary/Loader.cpp @@ -1,179 +1,166 @@ -#include -#include -#include -#include -#include -#include -#define PACKAGE #include "Loader.h" +#include +#include #include +#include #include -#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include -#define ERROR_LEN 1000 - -#define ECV_DWARF_UNDEF_VAL 20000 -#define ECV_DWARF_SAME_VAL 20001 -#define ECV_DWARF_CFA_VAL 20002 - using namespace BinaryLoader; -// Read symbol sizes from ELF symbol table using libelf -std::unordered_map ReadSymbolSizes(const char *file_name) { +// elfconv lifts only 64-bit little-endian ELF (AArch64 and amd64), which is the same set the +// bfd-based loader accepted -- it raised "unknown architecture" for anything else. +using ECVELFT = llvm::object::ELF64LE; +using ECVELFObjectFile = llvm::object::ELFObjectFile; + +namespace BinaryLoader { + +// Holds the LLVM objects backing the load. It lives in the .cpp so that no LLVM header reaches +// `Loader.h`, and therefore none reaches `TraceManager.h` -- which used to pull in . +struct ELFObjectImpl { + std::unique_ptr buffer; + std::unique_ptr object; + const ECVELFObjectFile *elf = nullptr; + + const llvm::object::ELFFile &File() const { + return elf->getELFFile(); + } +}; + +} // namespace BinaryLoader + +ELFObject::ELFObject(std::string __file_name) + : file_name(__file_name), + bin_type(BIN_TYPE_UNKNOWN), + bin_arch(ARCH_UNKNOWN), + bits(0), + entry(0), + symbol_table_size(0), + e_phent(0), + e_phnum(0), + e_ph(nullptr), + is_stripped(false), + able_vrp_opt(false), + impl(std::make_unique()) {} + +ELFObject::~ELFObject() = default; + +// Read the size of every function symbol, keyed by name, from `.symtab` and `.dynsym`. +// +// Kept as a name-keyed map rather than reading each symbol's own st_size inline, because the +// original walked BOTH tables and let a later table overwrite an earlier one's entry for the +// same name. Reading st_size directly per symbol would be cleaner and would not reproduce that. +static std::unordered_map +ReadSymbolSizes(const llvm::object::ELFFile &elf_file) { std::unordered_map sym_sizes; - /* confirme ELF library version */ - if (elf_version(EV_CURRENT) == EV_NONE) { - return sym_sizes; - } - - /* get file descriptor of target ELF file */ - int fd = open(file_name, O_RDONLY); - if (fd < 0) { + auto sections = elf_file.sections(); + if (!sections) { + llvm::consumeError(sections.takeError()); return sym_sizes; } - /* get Elf* object */ - auto _elf = elf_begin(fd, ELF_C_READ, NULL); - if (!_elf) { - close(fd); - return sym_sizes; - } - - /* confirm that the target file is ELF */ - if (elf_kind(_elf) != ELF_K_ELF) { - elf_end(_elf); - close(fd); - return sym_sizes; - } - - Elf_Scn *scn = NULL; - while ((scn = elf_nextscn(_elf, scn)) != NULL) { - GElf_Shdr shdr; - if (!gelf_getshdr(scn, &shdr)) { + for (const auto &shdr : *sections) { + if (shdr.sh_type != llvm::ELF::SHT_SYMTAB && shdr.sh_type != llvm::ELF::SHT_DYNSYM) { continue; } - // Check if this is a symbol table section (.symtab or .dynsym) - if (shdr.sh_type != SHT_SYMTAB && shdr.sh_type != SHT_DYNSYM) { + auto symbols = elf_file.symbols(&shdr); + if (!symbols) { + llvm::consumeError(symbols.takeError()); continue; } - - Elf_Data *data = elf_getdata(scn, NULL); - if (!data) { + auto str_table = elf_file.getStringTableForSymtab(shdr); + if (!str_table) { + llvm::consumeError(str_table.takeError()); continue; } - size_t num_syms = shdr.sh_size / shdr.sh_entsize; - for (size_t i = 0; i < num_syms; i++) { - GElf_Sym sym; - if (!gelf_getsym(data, i, &sym)) { + for (const auto &sym : *symbols) { + // Only store function symbols. + const auto sym_type = sym.getType(); + if (sym_type != llvm::ELF::STT_FUNC && sym_type != llvm::ELF::STT_NOTYPE) { continue; } - - // Only store function symbols - if (GELF_ST_TYPE(sym.st_info) == STT_FUNC || GELF_ST_TYPE(sym.st_info) == STT_NOTYPE) { - const char *name = elf_strptr(_elf, shdr.sh_link, sym.st_name); - if (name && sym.st_size > 0) { - sym_sizes[std::string(name)] = sym.st_size; - } + auto name = sym.getName(*str_table); + if (!name) { + llvm::consumeError(name.takeError()); + continue; + } + if (!name->empty() && sym.st_size > 0) { + sym_sizes[name->str()] = sym.st_size; } } } - elf_end(_elf); - close(fd); - return sym_sizes; } -int ReadEhdr(const char *file_name, uint64_t *e_phent, uint64_t *e_phnum, uint8_t *e_ph[]) { - - /* confirme ELF library version */ - if (elf_version(EV_CURRENT) == EV_NONE) { - elfconv_runtime_error("ELF library initialization failed."); - } - - /* get file descriptor of target ELF file */ - int fd = open(file_name, O_RDONLY); - if (fd < 0) { - elfconv_runtime_error("Failed to open ELF."); - } - - /* get Elf* object */ - auto _elf = elf_begin(fd, ELF_C_READ, NULL); - if (!_elf) { - close(fd); - elfconv_runtime_error("Failed to read ELF."); - } - - /* confirm that the target file is ELF */ - if (elf_kind(_elf) != ELF_K_ELF) { - elf_end(_elf); - close(fd); - elfconv_runtime_error("%s is not an ELF file\n", file_name); - } +void ELFObject::GetEhdr() { + const auto &elf_file = impl->File(); + const auto &ehdr = elf_file.getHeader(); - auto _ehdr = elf64_getehdr(_elf); /* set e_phentsize */ - *e_phent = _ehdr->e_phentsize; + e_phent = ehdr.e_phentsize; /* set e_phnum */ - *e_phnum = _ehdr->e_phnum; + e_phnum = ehdr.e_phnum; - /* get phder array */ - auto e_phdrs = elf64_getphdr(_elf); - if (!e_phdrs) { - elf_end(_elf); - close(fd); + /* get phdr array */ + auto phdrs = elf_file.program_headers(); + if (!phdrs) { + llvm::consumeError(phdrs.takeError()); elfconv_runtime_error("Failed to get program headers.\n"); } /* copy e_phdrs */ - auto e_ph_size = *e_phent * *e_phnum; - *e_ph = (uint8_t *) malloc(e_ph_size); - memcpy(*e_ph, e_phdrs, e_ph_size); - - elf_end(_elf); - close(fd); - - return 0; -} - -void ELFObject::GetEhdr() { - ReadEhdr(file_name.c_str(), &e_phent, &e_phnum, &e_ph); + auto e_ph_size = e_phent * e_phnum; + e_ph = (uint8_t *) malloc(e_ph_size); + if (!e_ph) { + elfconv_runtime_error("failed to allocate program header memory.\n"); + } + memcpy(e_ph, phdrs->begin(), e_ph_size); } void ELFObject::OpenELF() { - // init binary file descriptor - if (!bfd_inited) { - bfd_init(); - bfd_inited = true; - } - - bfd_h = bfd_openr(file_name.c_str(), nullptr); + auto buffer = llvm::MemoryBuffer::getFile(file_name, /*IsText=*/false, + /*RequiresNullTerminator=*/false); // confirm file_name is opened - if (!bfd_h) { + if (!buffer) { elfconv_runtime_error("failed to open binary file: %s, ERROR: %s\n", file_name.c_str(), - bfd_errmsg(bfd_get_error())); + buffer.getError().message().c_str()); } + impl->buffer = std::move(*buffer); + // confirm file_name is an object file - if (!bfd_check_format(bfd_h, bfd_object)) { - elfconv_runtime_error("file \"%s\" does not look like an executable file.\n", - file_name.c_str()); + auto object = llvm::object::ObjectFile::createELFObjectFile(impl->buffer->getMemBufferRef()); + if (!object) { + auto err = llvm::toString(object.takeError()); + elfconv_runtime_error("file \"%s\" does not look like an executable file. ERROR: %s\n", + file_name.c_str(), err.c_str()); } + impl->object = std::move(*object); - bfd_set_error(bfd_error_no_error); - // confirm file_name is an ELF binary - if (!(bfd_get_flavour(bfd_h) == bfd_target_elf_flavour)) { + // confirm file_name is a 64-bit little-endian ELF binary + impl->elf = llvm::dyn_cast(impl->object.get()); + if (!impl->elf) { elfconv_runtime_error("file \"%s\" is not an ELF binary.\n", file_name.c_str()); } @@ -182,42 +169,57 @@ void ELFObject::OpenELF() { } void ELFObject::LoadELF() { - LoadELFBFD(); + LoadELFObject(); } -void ELFObject::LoadELFBFD() { +void ELFObject::LoadELFObject() { // get binary handler OpenELF(); + + const auto &elf_file = impl->File(); + const auto &ehdr = elf_file.getHeader(); + // get entry point - entry = bfd_get_start_address(bfd_h); + entry = ehdr.e_entry; // get binary format - bin_type_str = std::string(bfd_h->xvec->name); - switch (bfd_h->xvec->flavour) { - case bfd_target_elf_flavour: bin_type = BIN_TYPE_ELF; break; - case bfd_target_unknown_flavour: - default: elfconv_runtime_error("file \"%s\" is not an ELF binary.\n", file_name.c_str()); - } + bin_type = BIN_TYPE_ELF; // get architecture - bfd_arch_info = bfd_get_arch_info(bfd_h); - bin_arch_str = std::string(bfd_arch_info->arch_name); - switch (bfd_arch_info->mach) { - case bfd_mach_aarch64: + switch (ehdr.e_machine) { + case llvm::ELF::EM_AARCH64: bin_arch = BinaryArch::ARCH_AARCH64; + bin_arch_str = "aarch64"; + bin_type_str = "elf64-littleaarch64"; bits = 64; break; - case bfd_mach_x86_64: + case llvm::ELF::EM_X86_64: bin_arch = BinaryArch::ARCH_AMD64; + bin_arch_str = "i386:x86-64"; + bin_type_str = "elf64-x86-64"; bits = 64; break; default: elfconv_runtime_error("unknown architecture\n"); break; } // get every section - LoadSectionsBFD(); + LoadSections(); // functions detection. - symbol_table_size = bfd_get_symtab_upper_bound(bfd_h); - if (symbol_table_size <= 8) { + // + // `symbol_table_size` counts `.symtab` entries excluding the reserved null entry, so zero + // means there is no static symbol table to read. + auto sections = elf_file.sections(); + if (!sections) { + llvm::consumeError(sections.takeError()); + elfconv_runtime_error("failed to read section headers.\n"); + } + for (const auto &shdr : *sections) { + if (shdr.sh_type == llvm::ELF::SHT_SYMTAB && shdr.sh_entsize > 0) { + symbol_table_size = (shdr.sh_size / shdr.sh_entsize) - 1; + break; + } + } + + if (symbol_table_size == 0) { // The ELF binary is stripped. is_stripped = true; // The ELF binary generated from gcc or clang has `eh_frame` section even if it is stripped, @@ -238,7 +240,7 @@ void ELFObject::LoadELFBFD() { } } else { // we can detect all functions by watching the symbol table. - LoadStaticSymbolsBFD(); + LoadStaticSymbols(); is_stripped = false; able_vrp_opt = true; } @@ -249,95 +251,78 @@ void ELFObject::LoadELFBFD() { } std::sort(func_symbols.begin(), func_symbols.end(), [](auto const &lhs, auto const &rhs) { return lhs.addr < rhs.addr; }); - - /* get every dynamic symbol table */ - // LoadDynamicSymbolsBFD(); /* FIXME */ } -asection *ELFObject::GetIncludedSection(uint64_t vma) { - for (auto sec = bfd_h->sections; sec; sec = sec->next) { - bfd_vma sec_vma = bfd_section_vma(sec); - bfd_size_type sec_size = bfd_section_size(sec); - if (sec_vma <= vma && vma < sec_vma + sec_size) { - return sec; +const SectionRange *ELFObject::GetIncludedSection(uint64_t vma) { + for (auto &sec : all_sections) { + if (sec.vma <= vma && vma < sec.vma + sec.size) { + return &sec; } } return nullptr; } -void ELFObject::LoadStaticSymbolsBFD() { +void ELFObject::LoadStaticSymbols() { // Read symbol sizes from ELF symbol table - auto sym_sizes = ReadSymbolSizes(file_name.c_str()); + auto sym_sizes = ReadSymbolSizes(impl->File()); - asymbol **bfd_symtab = nullptr; - // get symbol table space - bfd_symtab = reinterpret_cast(malloc(symbol_table_size)); - if (!bfd_symtab) { - elfconv_runtime_error("failed to allocate symtab memory.\n"); - } - // read symbol table - long sym_num = bfd_canonicalize_symtab(bfd_h, bfd_symtab); - if (sym_num < 0) { + const auto &elf_file = impl->File(); + + auto sections = elf_file.sections(); + if (!sections) { + llvm::consumeError(sections.takeError()); elfconv_runtime_error("failed to read symtab.\n"); } - for (int i = 0; i < sym_num; i++) { - if (bfd_symtab[i]->flags & BSF_FUNCTION || - std::memcmp(bfd_symtab[i]->name, "_start", sizeof("_start")) == 0) { - } else { + + for (const auto &shdr : *sections) { + if (shdr.sh_type != llvm::ELF::SHT_SYMTAB) { continue; } - // Get symbol size if available - uint64_t sym_size = 0; - std::string sym_name = std::string(bfd_symtab[i]->name); - if (sym_sizes.count(sym_name) > 0) { - sym_size = sym_sizes[sym_name]; - } - func_symbols.emplace_back(ELFSymbol::SYM_TYPE_FUNC, sym_name, bfd_asymbol_value(bfd_symtab[i]), - GetIncludedSection(bfd_asymbol_value(bfd_symtab[i])), sym_size); - } -} - -void ELFObject::LoadDynamicSymbolsBFD() { - // Read symbol sizes from ELF symbol table - auto sym_sizes = ReadSymbolSizes(file_name.c_str()); - - asymbol **bfd_symtab = nullptr; - // get symbol table space - assert(symbol_table_size > 0); - long table_size = bfd_get_dynamic_symtab_upper_bound(bfd_h); - if (table_size < 0) { - elfconv_runtime_error("failed to read symtab. gotten table_size: %ld\n", table_size); - } else if (table_size > 0) { - bfd_symtab = reinterpret_cast(malloc(table_size)); - if (!bfd_symtab) { - elfconv_runtime_error("failed to allocate symtab memory.\n"); + auto symbols = elf_file.symbols(&shdr); + if (!symbols) { + llvm::consumeError(symbols.takeError()); + elfconv_runtime_error("failed to read symtab.\n"); } - // read symbol table - long sym_num = bfd_canonicalize_dynamic_symtab(bfd_h, bfd_symtab); - if (sym_num < 0) { + auto str_table = elf_file.getStringTableForSymtab(shdr); + if (!str_table) { + llvm::consumeError(str_table.takeError()); elfconv_runtime_error("failed to read symtab.\n"); } - for (int i = 0; i < sym_num; i++) { - ELFSymbol::SymbolType sym_type; - if (bfd_symtab[i]->flags & BSF_FUNCTION || - std::memcmp(bfd_symtab[i]->name, "_start", sizeof("_start")) == 0) { - sym_type = ELFSymbol::SymbolType::SYM_TYPE_FUNC; + + for (const auto &sym : *symbols) { + auto name_or_err = sym.getName(*str_table); + if (!name_or_err) { + llvm::consumeError(name_or_err.takeError()); + continue; + } + std::string sym_name = name_or_err->str(); + + // STT_FUNC only -- deliberately NOT STT_GNU_IFUNC. + // + // Measured against the bfd loader this replaces, on a static-glibc aarch64 binary holding + // exactly 10 STT_GNU_IFUNC symbols (memcpy, memmove, memset, memchr, strlen and their + // __libc_/__ aliases): bfd did not raise BSF_FUNCTION for them, so the original filter + // admitted none. Admitting them shifted every later symbol index by 10 and, because the + // sort below is by address and std::sort is NOT stable, re-scrambled which name won at the + // 96 addresses where glibc puts several aliases on one function. The emitted bitcode + // changed as a result. The `_start` clause caught binaries declaring the entry point + // STT_NOTYPE; here `_start` is already STT_FUNC, so it is carried for other binaries. + const auto sym_type = sym.getType(); + if (sym_type == llvm::ELF::STT_FUNC || sym_name == "_start") { } else { continue; } + // Get symbol size if available uint64_t sym_size = 0; - std::string sym_name = std::string(bfd_symtab[i]->name); if (sym_sizes.count(sym_name) > 0) { sym_size = sym_sizes[sym_name]; } - func_symbols.emplace_back(sym_type, sym_name, bfd_asymbol_value(bfd_symtab[i]), nullptr, - sym_size); + func_symbols.emplace_back(ELFSymbol::SYM_TYPE_FUNC, sym_name, sym.st_value, + GetIncludedSection(sym.st_value), sym_size); } - } else { - printf("[INFO] static symbol table is not found.\n"); } } @@ -354,136 +339,119 @@ void ELFObject::SetCodeSection() { } } -void ELFObject::LoadSectionsBFD() { +void ELFObject::LoadSections() { + + const auto &elf_file = impl->File(); + + auto shdrs = elf_file.sections(); + if (!shdrs) { + llvm::consumeError(shdrs.takeError()); + elfconv_runtime_error("failed to read section headers.\n"); + } - asection *bfd_sec; + // `all_sections` must not reallocate after `GetIncludedSection` starts handing out pointers + // into it, and symbol loading runs after this function returns -- but reserve up front anyway + // so that the invariant does not depend on when the caller asks. + all_sections.reserve(shdrs->size()); - for (bfd_sec = bfd_h->sections; bfd_sec; bfd_sec = bfd_sec->next) { + for (const auto &shdr : *shdrs) { ELFSection::SectionType sec_type; - flagword bfd_flags; - bfd_vma vma; - bfd_size_type size; + uint64_t vma; + uint64_t size; std::string sec_name; uint8_t *sec_bytes; - // get bfd flags - bfd_flags = bfd_section_flags(bfd_sec); - if (bfd_flags & SEC_CODE) { + // bfd's section list has no entry for the reserved null section header. + if (shdr.sh_type == llvm::ELF::SHT_NULL) { + continue; + } + + // map ELF section flags the way bfd did when it set its own section flags + if (shdr.sh_flags & llvm::ELF::SHF_EXECINSTR) { sec_type = ELFSection::SEC_TYPE_CODE; - } else if (bfd_flags & (SEC_DATA | SEC_ALLOC)) { + } else if (shdr.sh_flags & llvm::ELF::SHF_ALLOC) { sec_type = ELFSection::SEC_TYPE_DATA; - } else if (bfd_flags & SEC_READONLY) { + } else if (!(shdr.sh_flags & llvm::ELF::SHF_WRITE)) { sec_type = ELFSection::SEC_TYPE_READONLY; } else { sec_type = ELFSection::SEC_TYPE_UNKNOWN; } + // get vma, section size, section name, section contents - vma = bfd_section_vma(bfd_sec); - size = bfd_section_size(bfd_sec); - sec_name = std::string(bfd_section_name(bfd_sec)); + vma = shdr.sh_addr; + size = shdr.sh_size; + auto name_or_err = elf_file.getSectionName(shdr); + if (name_or_err) { + sec_name = name_or_err->str(); + } else { + llvm::consumeError(name_or_err.takeError()); + } if (sec_name.empty()) { sec_name = std::string(""); } - sec_bytes = reinterpret_cast(malloc(size)); - if (!sec_bytes) { - elfconv_runtime_error("failed to allocate section bytes.\n"); - } - if (!bfd_get_section_contents(bfd_h, bfd_sec, sec_bytes, 0, size)) { - elfconv_runtime_error("failed to read and copy section bytes.\n"); - } + + // Every section is a candidate for a containment query, including the ones dropped below. + all_sections.push_back(SectionRange{sec_name, vma, size}); // The section starting from 0x0 is unused. if (vma == 0x0) { continue; } + sec_bytes = reinterpret_cast(malloc(size)); + if (!sec_bytes) { + elfconv_runtime_error("failed to allocate section bytes.\n"); + } + if (shdr.sh_type == llvm::ELF::SHT_NOBITS) { + // `.bss` and friends occupy no file space; bfd_get_section_contents zero-filled them. + memset(sec_bytes, 0, size); + } else { + auto contents = elf_file.getSectionContents(shdr); + if (!contents) { + llvm::consumeError(contents.takeError()); + elfconv_runtime_error("failed to read and copy section bytes.\n"); + } + if (contents->size() < size) { + elfconv_runtime_error("failed to read and copy section bytes.\n"); + } + memcpy(sec_bytes, contents->data(), size); + } + sections.emplace_back(this, sec_type, sec_name, vma, size, sec_bytes); } } // We can get function entry address and function size (but not necessary for elfconv) by watching the `eh_frame` section. -// In order to parse eh_frame, we use libdwarf library. +// In order to parse eh_frame, we use LLVM's own DWARF reader. int ELFObject::GetSblFromEhFrame() { - Dwarf_Debug dbg; - Dwarf_Error error; - Dwarf_Ptr errarg = 0; - Dwarf_Handler errhand = 0; - int elf_fd; - int reg_table_rule_count; - int dwarf_res; - - if ((elf_fd = open(file_name.c_str(), O_RDONLY)) < 0) { - elfconv_runtime_error("open ELF file on ELFObject::GetSblOfEhFrame. file_name: %s.\n", - file_name.c_str()); - } - - // libdwarf init. - if ((dwarf_res = dwarf_init(elf_fd, /*DW_DLC_REA*/ 0, errhand, errarg, &dbg, &error)) != - DW_DLV_OK) { - LOG(INFO) << "[INFO] dwarf_init() failed.\n"; + auto dwarf_ctx = llvm::DWARFContext::create(*impl->object); + if (!dwarf_ctx) { + LOG(INFO) << "[INFO] failed to create a DWARF context.\n"; return -1; } - // Set various meta data of Dwarf_dbg. - reg_table_rule_count = 1999; - dwarf_set_frame_undefined_value(dbg, ECV_DWARF_UNDEF_VAL); - dwarf_set_frame_rule_initial_value(dbg, ECV_DWARF_UNDEF_VAL); - dwarf_set_frame_same_value(dbg, ECV_DWARF_SAME_VAL); - dwarf_set_frame_cfa_value(dbg, ECV_DWARF_CFA_VAL); - dwarf_set_frame_rule_table_size(dbg, reg_table_rule_count); - - // Parse `eh_frame` section and make all function symbols. - dwarf_res = ParseEhFrame(dbg); - if (dwarf_res != 0) { + auto eh_frame = dwarf_ctx->getEHFrame(); + if (!eh_frame) { + llvm::consumeError(eh_frame.takeError()); + LOG(WARNING) << "Error reading frame data.\n"; return -1; } - dwarf_res = dwarf_finish(dbg, &error); - - if (dwarf_res != DW_DLV_OK) { - fprintf(stderr, "dwarf_finish failed!\n"); - exit(EXIT_FAILURE); - } - - close(elf_fd); - return 0; -} - -int ELFObject::ParseEhFrame(Dwarf_Debug dbg) { - - Dwarf_Error error; - Dwarf_Signed cie_element_count = 0; - Dwarf_Signed fde_element_count = 0; - Dwarf_Cie *cie_data = 0; - Dwarf_Fde *fde_data = 0; - int p_res = DW_DLV_ERROR; - // Parse `eh_frame` section. - p_res = dwarf_get_fde_list_eh(dbg, &cie_data, &cie_element_count, &fde_data, &fde_element_count, - &error); - if (p_res == DW_DLV_NO_ENTRY) { + if ((*eh_frame)->empty()) { LOG(INFO) << "[INFO] No frame data on the ELF.\n"; return -1; - } else if (p_res == DW_DLV_ERROR) { - LOG(WARNING) << "Error reading frame data.\n"; - return -1; } - // Get every function entry address and fucntion size from the parsed data. - for (Dwarf_Signed fdenum = 0; fdenum < fde_element_count; ++fdenum) { - - Dwarf_Cie cie = 0; - uint64_t ehf_fun_entry = 0; - size_t ehf_fun_size = 0; - - p_res = dwarf_get_cie_of_fde(fde_data[fdenum], &cie, &error); - if (p_res != DW_DLV_OK) { - elfconv_runtime_error("Error accessing fdenum %" DW_PR_DSd " to get its cie\n", fdenum); + // Get every function entry address from the parsed data. + for (const llvm::dwarf::FrameEntry &entry : (*eh_frame)->entries()) { + const auto *fde = llvm::dyn_cast(&entry); + if (!fde) { + continue; } - // Make all func symbols. - GetFuncFromEhFrame(&ehf_fun_entry, &ehf_fun_size, dbg, fde_data[fdenum]); + uint64_t ehf_fun_entry = fde->getInitialLocation(); std::stringstream ehf_fun_name; ehf_fun_name << "_ecv_lifted_fun_0x" << std::hex << ehf_fun_entry; func_symbols_map.insert({ehf_fun_entry, @@ -491,34 +459,9 @@ int ELFObject::ParseEhFrame(Dwarf_Debug dbg) { GetIncludedSection(ehf_fun_entry)}}); } - // Destructor. - dwarf_fde_cie_list_dealloc(dbg, cie_data, cie_element_count, fde_data, fde_element_count); - return 0; } -void ELFObject::GetFuncFromEhFrame(uint64_t *ehf_fun_entry, size_t *ehf_fun_size, Dwarf_Debug dbg, - Dwarf_Fde fde) { - int f_res; - Dwarf_Error error; - Dwarf_Unsigned func_length = 0; - Dwarf_Unsigned fde_byte_length = 0; - Dwarf_Off cie_offset = 0; - Dwarf_Off fde_offset = 0; - Dwarf_Addr lowpc = 0; - Dwarf_Signed cie_index = 0; - Dwarf_Ptr fde_bytes; - - f_res = dwarf_get_fde_range(fde, &lowpc, &func_length, &fde_bytes, &fde_byte_length, &cie_offset, - &cie_index, &fde_offset, &error); - if (f_res != DW_DLV_OK) { - elfconv_runtime_error("Failed to get fde range\n"); - } - - *ehf_fun_entry = lowpc; - *ehf_fun_size = func_length; -} - // In the current implementation, we use radare2 analysis only for noopt mode. void ELFObject::R2Detect() { printf("[\x1b[33mINFO\x1b[0m] R2 analyzing function boundaries... "); diff --git a/lifter/Binary/Loader.h b/lifter/Binary/Loader.h index 264c7872..656a4a65 100644 --- a/lifter/Binary/Loader.h +++ b/lifter/Binary/Loader.h @@ -1,14 +1,10 @@ #pragma once #include -#include #include #include #include #include -#include -#include -#include #include #include #include @@ -22,6 +18,20 @@ namespace BinaryLoader { class ELFSymbol; class ELFSection; class ELFObject; +struct ELFObjectImpl; + +// The address range of one ELF section. +// +// This replaces bfd's `asection` in the two roles elfconv actually used it for: as a stable +// identity that two symbols can be compared against (`in_section == in_section`), and as a +// carrier of the section's vma and size. It is deliberately NOT `ELFSection` -- `ELFSection` +// holds a copy of the section bytes and is filtered (sections based at 0x0 are dropped), +// whereas containment queries must run over the whole section list the way bfd's did. +struct SectionRange { + std::string sec_name; + uint64_t vma; + uint64_t size; +}; class ELFSymbol { public: @@ -33,7 +43,7 @@ class ELFSymbol { }; ELFSymbol(SymbolType __sym_type, std::string __sym_name, uintptr_t __addr, - bfd_section *__in_section, uint64_t __size = 0) + const SectionRange *__in_section, uint64_t __size = 0) : sym_type(__sym_type), sym_name(__sym_name), addr(__addr), @@ -42,7 +52,7 @@ class ELFSymbol { ELFSymbol::SymbolType sym_type; std::string sym_name; uintptr_t addr; - bfd_section *in_section; + const SectionRange *in_section; uint64_t size; // Function size from symbol table }; @@ -100,23 +110,17 @@ class ELFObject { void LoadELF(); void SetCodeSection(); - asection *GetIncludedSection(uint64_t vma); + const SectionRange *GetIncludedSection(uint64_t vma); void R2Detect(); int GetSblFromEhFrame(); - int ParseEhFrame(Dwarf_Debug dbg); - void GetFuncFromEhFrame(uint64_t *_entry, size_t *_size, Dwarf_Debug dbg, Dwarf_Fde fde); void DebugSections(); void DebugStaticSymbols(); void DebugBinary(); - ELFObject(std::string __file_name) : file_name(__file_name), bfd_inited(false) { - is_stripped = false; - } + explicit ELFObject(std::string __file_name); + ~ELFObject(); std::string file_name; - bool bfd_inited; - bfd *bfd_h; - const bfd_arch_info_type *bfd_arch_info; ELFObject::BinaryType bin_type; std::string bin_type_str; ELFObject::BinaryArch bin_arch; @@ -124,9 +128,15 @@ class ELFObject { uint32_t bits; uintptr_t entry; std::vector sections; + // Every section, unfiltered, in section-header order. Owns the identities handed out by + // `GetIncludedSection`, so it must not be resized after symbol loading begins. + std::vector all_sections; std::unordered_map func_symbols_map; std::vector func_symbols; std::unordered_map code_sections; + // Number of `.symtab` entries, excluding the reserved null entry. Zero means the binary is + // stripped. (This previously held bfd's `bfd_get_symtab_upper_bound`, a byte count that was + // tested against 8 -- one `asymbol *` -- to mean the same thing.) unsigned long symbol_table_size; uint64_t e_phent; @@ -138,10 +148,11 @@ class ELFObject { private: void OpenELF(); - void LoadELFBFD(); - void LoadStaticSymbolsBFD(); - void LoadDynamicSymbolsBFD(); - void LoadSectionsBFD(); + void LoadELFObject(); + void LoadStaticSymbols(); + void LoadSections(); void GetEhdr(); + + std::unique_ptr impl; }; -} // namespace BinaryLoader \ No newline at end of file +} // namespace BinaryLoader diff --git a/lifter/CMakeLists.txt b/lifter/CMakeLists.txt index 3a00a79a..f3126ed9 100644 --- a/lifter/CMakeLists.txt +++ b/lifter/CMakeLists.txt @@ -38,33 +38,19 @@ else() message(FATAL_ERROR, "CMAKE_ELFCONV__BUILD must be 1. (lifter/CMakeLists.txt)") endif() +# The ELF loader reads the binary through llvm::object::ELFObjectFile and its `.eh_frame` +# through llvm::DWARFContext, so binutils' bfd, libdwarf and libelf are no longer linked -- +# and neither is libiberty, which was only ever a transitive requirement of bfd. +# +# No LLVM library is named here on purpose. LLVMObject and LLVMDebugInfoDWARF arrive with the +# `LLVM` that `remill` and ${PROJECT_LIBRARIES} already link, which is how every other target in +# this project consumes LLVM. Naming the components explicitly instead +# (llvm_map_components_to_libnames) resolves to the STATIC component archives, whose interface +# demands zstd::libzstd_shared -- absent in the build image, so configuration fails outright. + # static link if(${CMAKE_ELFLIFT_STATIC_LINK}) - if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - set(LIBIBERTY_PATH "/usr/lib/aarch64-linux-gnu/libiberty.a") - elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") - set(LIBIBERTY_PATH "/usr/lib/x86_64-linux-gnu/libiberty.a") - else() - message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}") - endif() target_link_options(elflift PUBLIC -static) - target_link_libraries( - elflift - PRIVATE - bfd - elf - dwarf - ${LIBIBERTY_PATH} - ) -else() -# dynamic link - target_link_libraries( - elflift - PRIVATE - bfd - elf - dwarf - ) endif() target_link_libraries(elflift PUBLIC remill ${PROJECT_LIBRARIES} ) diff --git a/lifter/TraceManager.cpp b/lifter/TraceManager.cpp index 6dab8488..5c033b7c 100644 --- a/lifter/TraceManager.cpp +++ b/lifter/TraceManager.cpp @@ -9,7 +9,6 @@ #endif #include -#include #include #include #include @@ -131,8 +130,7 @@ void AArch64TraceManager::SetELFData() { } else if (func_symbols[i].in_section == func_symbols[i + 1].in_section) { func_size = func_symbols[i + 1].addr - func_symbols[i].addr; } else { - func_size = (bfd_section_vma(func_symbols[i].in_section) + - bfd_section_size(func_symbols[i].in_section)) - + func_size = (func_symbols[i].in_section->vma + func_symbols[i].in_section->size) - func_symbols[i].addr; } @@ -149,8 +147,7 @@ void AArch64TraceManager::SetELFData() { if (last_func_symbol.size > 0) { func_size = last_func_symbol.size; } else { - func_size = (bfd_section_vma(last_func_symbol.in_section) + - bfd_section_size(last_func_symbol.in_section)) - + func_size = (last_func_symbol.in_section->vma + last_func_symbol.in_section->size) - last_func_symbol.addr; } disasm_funcs.emplace(last_func_symbol.addr, diff --git a/lifter/TraceManager.h b/lifter/TraceManager.h index 50d11299..689ee250 100644 --- a/lifter/TraceManager.h +++ b/lifter/TraceManager.h @@ -2,7 +2,6 @@ #include "Binary/Loader.h" #include -#include #include #include #include @@ -63,7 +62,8 @@ class AArch64TraceManager : public remill::TraceManager { std::map disasm_funcs; std::map rest_disasm_funcs; - std::unordered_map> sec_symbol_mp; + std::unordered_map> + sec_symbol_mp; std::string entry_func_lifted_name; std::string panic_plt_jmp_fun_name; From 628cdd63b47522aab221f6f6df89bc7b0cfbebac Mon Sep 17 00:00:00 2001 From: o2alexanderfedin Date: Fri, 7 Aug 2026 14:28:39 -0700 Subject: [PATCH 2/2] Take each function's size from its own symbol, not from a name-keyed map Now that the loader reads the ELF through LLVM, a function's length comes off the symbol the loader is already holding. bfd could not do that: its `asymbol` is format-neutral and carries no size, so the loader re-read the binary through libelf, built a NAME -> size map over `.symtab` and `.dynsym`, and joined on the name -- the only key bfd left it. That join is lossy, and it is wrong on a hello-world. Measured on the static-glibc aarch64 subject the harness uses: 12 names collide, and 15 function symbols are handed a length that is not their own. The worst is `free_mem` -- eight distinct static functions in eight translation units, seven of which receive 100 bytes in place of their real 36, 40, 48, 56, 104, 184 or 276, because the map keeps whichever entry came last. TraceManager prefers the symbol size over section arithmetic, so the lifter disassembled the wrong number of bytes for those seven: some truncated, some over-read. THIS CHANGES THE EMITTED BITCODE ON PURPOSE, which is why it is not folded into the dependency removal it sits on top of. That commit's entire warrant is that it changed nothing -- bitcode and wasm byte-identical on both loader paths -- and proving "three libraries removed, behaviour unchanged" requires the change to be separable from any behaviour fix. not stripped (.symtab path) ec2dc6f9... -> fba665d1... 900 funcs, unchanged stripped (.eh_frame path) 883bbf48... unchanged 899 funcs, unchanged The stripped arm does not move and cannot: `.eh_frame` carries its own address ranges and never consulted the map. STATED LIMIT: the new sizes are argued correct from the ELF, not demonstrated by execution. The build image has no wasmedge -- `elfconv.sh` line 275 exits 127 on every lift, before and after -- so no lifted artifact was run here. What is measured is that the sizes now come from the symbol that owns them, and that nothing else in the pipeline moved. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Nu4maFxHPncwvdP8XcSEbD --- lifter/Binary/Loader.cpp | 75 ++++++++-------------------------------- lifter/Binary/Loader.h | 1 - 2 files changed, 15 insertions(+), 61 deletions(-) diff --git a/lifter/Binary/Loader.cpp b/lifter/Binary/Loader.cpp index ee4bedb0..99ec79e8 100644 --- a/lifter/Binary/Loader.cpp +++ b/lifter/Binary/Loader.cpp @@ -62,57 +62,6 @@ ELFObject::ELFObject(std::string __file_name) ELFObject::~ELFObject() = default; -// Read the size of every function symbol, keyed by name, from `.symtab` and `.dynsym`. -// -// Kept as a name-keyed map rather than reading each symbol's own st_size inline, because the -// original walked BOTH tables and let a later table overwrite an earlier one's entry for the -// same name. Reading st_size directly per symbol would be cleaner and would not reproduce that. -static std::unordered_map -ReadSymbolSizes(const llvm::object::ELFFile &elf_file) { - std::unordered_map sym_sizes; - - auto sections = elf_file.sections(); - if (!sections) { - llvm::consumeError(sections.takeError()); - return sym_sizes; - } - - for (const auto &shdr : *sections) { - if (shdr.sh_type != llvm::ELF::SHT_SYMTAB && shdr.sh_type != llvm::ELF::SHT_DYNSYM) { - continue; - } - - auto symbols = elf_file.symbols(&shdr); - if (!symbols) { - llvm::consumeError(symbols.takeError()); - continue; - } - auto str_table = elf_file.getStringTableForSymtab(shdr); - if (!str_table) { - llvm::consumeError(str_table.takeError()); - continue; - } - - for (const auto &sym : *symbols) { - // Only store function symbols. - const auto sym_type = sym.getType(); - if (sym_type != llvm::ELF::STT_FUNC && sym_type != llvm::ELF::STT_NOTYPE) { - continue; - } - auto name = sym.getName(*str_table); - if (!name) { - llvm::consumeError(name.takeError()); - continue; - } - if (!name->empty() && sym.st_size > 0) { - sym_sizes[name->str()] = sym.st_size; - } - } - } - - return sym_sizes; -} - void ELFObject::GetEhdr() { const auto &elf_file = impl->File(); const auto &ehdr = elf_file.getHeader(); @@ -264,9 +213,6 @@ const SectionRange *ELFObject::GetIncludedSection(uint64_t vma) { void ELFObject::LoadStaticSymbols() { - // Read symbol sizes from ELF symbol table - auto sym_sizes = ReadSymbolSizes(impl->File()); - const auto &elf_file = impl->File(); auto sections = elf_file.sections(); @@ -315,13 +261,22 @@ void ELFObject::LoadStaticSymbols() { continue; } - // Get symbol size if available - uint64_t sym_size = 0; - if (sym_sizes.count(sym_name) > 0) { - sym_size = sym_sizes[sym_name]; - } + // Take the size from the symbol itself. + // + // The bfd loader could not: bfd's `asymbol` is format-neutral and carries no size, so it + // re-read the ELF through libelf, built a NAME -> size map over `.symtab` and `.dynsym`, + // and joined on the name -- the only key bfd left it. That join is lossy. In this + // binary 12 names collide and 15 function symbols are handed a length that is not their + // own; the worst is `free_mem`, eight distinct static functions of which seven are handed + // 100 bytes in place of their real 36, 40, 48, 56, 104, 184 or 276, because the map keeps + // whichever came last. The lifter then disassembles the wrong number of bytes for them. + // + // This CHANGES the emitted bitcode by design (ec2dc6f9... -> fba665d1... on the harness's + // non-stripped arm) and is therefore committed apart from the dependency removal, whose + // warrant is that it changed nothing. The stripped arm is unaffected: `.eh_frame` carries + // its own ranges and never consulted this map. func_symbols.emplace_back(ELFSymbol::SYM_TYPE_FUNC, sym_name, sym.st_value, - GetIncludedSection(sym.st_value), sym_size); + GetIncludedSection(sym.st_value), sym.st_size); } } } diff --git a/lifter/Binary/Loader.h b/lifter/Binary/Loader.h index 656a4a65..47d74f48 100644 --- a/lifter/Binary/Loader.h +++ b/lifter/Binary/Loader.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include