From 98ced6cfd0eb2c4b266161b6f13ace5da9c3e436 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 3 Apr 2026 15:16:31 -0500 Subject: [PATCH] [BOLT] Template patchELFPHDRTable and rewriteNoteSections for ELF32 (#189715) Template patchELFPHDRTable, rewriteNoteSections, markGnuRelroSections, and discoverStorage to support both ELF32LE and ELF64LE binaries. Previously these functions were hardcoded for ELF64LE, causing crashes when processing 32-bit ELF binaries. The RewriteInstance constructor now accepts ELF32LE objects in addition to ELF64LE. The ELF_FUNCTION macro is reused (and moved earlier in the header) to dispatch to the correct template instantiation. These changes are preparation for adding support to hexagon architecture in Bolt. --- bolt/include/bolt/Rewrite/RewriteInstance.h | 38 ++++--- bolt/lib/Rewrite/RewriteInstance.cpp | 117 +++++++++++--------- bolt/test/Inputs/elf32-basic.yaml | 12 ++ bolt/test/elf32-basic.test | 12 ++ 4 files changed, 108 insertions(+), 71 deletions(-) create mode 100644 bolt/test/Inputs/elf32-basic.yaml create mode 100644 bolt/test/elf32-basic.test diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h index 54744f03e5b1..166336cbb8fc 100644 --- a/bolt/include/bolt/Rewrite/RewriteInstance.h +++ b/bolt/include/bolt/Rewrite/RewriteInstance.h @@ -242,15 +242,29 @@ private: /// Return value for the symbol \p Name in the output. uint64_t getNewValueForSymbol(const StringRef Name); + /// ELF-specific part. TODO: refactor into new class. +#define ELF_FUNCTION(TYPE, FUNC) \ + template TYPE FUNC(object::ELFObjectFile *Obj); \ + TYPE FUNC() { \ + if (auto *ELF32LE = dyn_cast(InputFile)) \ + return FUNC(ELF32LE); \ + if (auto *ELF64LE = dyn_cast(InputFile)) \ + return FUNC(ELF64LE); \ + if (auto *ELF32BE = dyn_cast(InputFile)) \ + return FUNC(ELF32BE); \ + auto *ELF64BE = cast(InputFile); \ + return FUNC(ELF64BE); \ + } + /// Check for PT_GNU_RELRO segment presence, mark covered sections as /// (dynamically) read-only (written once), as specified in LSB Chapter 12: /// "segment which may be made read-only after relocations have been /// processed". - void markGnuRelroSections(); + ELF_FUNCTION(void, markGnuRelroSections); /// Detect addresses and offsets available in the binary for allocating /// new sections. - Error discoverStorage(); + ELF_FUNCTION(Error, discoverStorage); /// Adjust function sizes and set proper maximum size values after the whole /// symbol table has been processed. @@ -269,7 +283,7 @@ private: uint64_t &ExtractedValue) const; /// Rewrite non-allocatable sections with modifications. - void rewriteNoteSections(); + ELF_FUNCTION(void, rewriteNoteSections); /// Write .eh_frame_hdr. void writeEHFrameHeader(); @@ -298,25 +312,11 @@ private: /// Disassemble riscv-specific .plt \p Section auxiliary function void disassemblePLTSectionRISCV(BinarySection &Section); - /// ELF-specific part. TODO: refactor into new class. -#define ELF_FUNCTION(TYPE, FUNC) \ - template TYPE FUNC(object::ELFObjectFile *Obj); \ - TYPE FUNC() { \ - if (auto *ELF32LE = dyn_cast(InputFile)) \ - return FUNC(ELF32LE); \ - if (auto *ELF64LE = dyn_cast(InputFile)) \ - return FUNC(ELF64LE); \ - if (auto *ELF32BE = dyn_cast(InputFile)) \ - return FUNC(ELF32BE); \ - auto *ELF64BE = cast(InputFile); \ - return FUNC(ELF64BE); \ - } - /// Update loadable segment information based on new sections. void updateSegmentInfo(); /// Patch ELF book-keeping info. - void patchELFPHDRTable(); + ELF_FUNCTION(void, patchELFPHDRTable); /// Create section header table. ELF_FUNCTION(void, patchELFSectionHeaderTable); @@ -604,6 +604,8 @@ private: friend class RewriteInstanceDiff; }; +#undef ELF_FUNCTION + MCPlusBuilder *createMCPlusBuilder(const Triple::ArchType Arch, const MCInstrAnalysis *Analysis, const MCInstrInfo *Info, diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp index 417e93e78006..282c3cdb3fd4 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -374,8 +374,6 @@ MCPlusBuilder *createMCPlusBuilder(const Triple::ArchType Arch, } // namespace bolt } // namespace llvm -using ELF64LEPhdrTy = ELF64LEFile::Elf_Phdr; - namespace { bool refersToReorderedSection(ErrorOr Section) { @@ -405,16 +403,16 @@ RewriteInstance::RewriteInstance(ELFObjectFileBase *File, const int Argc, : InputFile(File), Argc(Argc), Argv(Argv), ToolPath(ToolPath), SHStrTab(StringTableBuilder::ELF) { ErrorAsOutParameter EAO(&Err); - auto ELF64LEFile = dyn_cast(InputFile); - if (!ELF64LEFile) { + if (!isa(InputFile) && + !isa(InputFile)) { Err = createStringError(errc::not_supported, - "Only 64-bit LE ELF binaries are supported"); + "Only 32-bit and 64-bit LE ELF binaries " + "are supported"); return; } bool IsPIC = false; - const ELFFile &Obj = ELF64LEFile->getELFFile(); - if (Obj.getHeader().e_type != ELF::ET_EXEC) { + if (File->getEType() != ELF::ET_EXEC) { Stdout << "BOLT-INFO: shared object or position-independent executable " "detected\n"; IsPIC = true; @@ -549,13 +547,12 @@ static bool checkVMA(const typename ELFT::Phdr &Phdr, return false; } -void RewriteInstance::markGnuRelroSections() { - using ELFT = ELF64LE; +template +void RewriteInstance::markGnuRelroSections(ELFObjectFile *ELFObjFile) { using ELFShdrTy = typename ELFObjectFile::Elf_Shdr; - auto ELF64LEFile = cast(InputFile); - const ELFFile &Obj = ELF64LEFile->getELFFile(); + const ELFFile &Obj = ELFObjFile->getELFFile(); - auto handleSection = [&](const ELFT::Phdr &Phdr, SectionRef SecRef) { + auto handleSection = [&](const typename ELFT::Phdr &Phdr, SectionRef SecRef) { BinarySection *BinarySection = BC->getSectionForSectionRef(SecRef); // If the section is non-allocatable, ignore it for GNU_RELRO purposes: // it can't be made read-only after runtime relocations processing. @@ -586,37 +583,39 @@ void RewriteInstance::markGnuRelroSections() { << " as GNU_RELRO\n"; }; - for (const ELFT::Phdr &Phdr : cantFail(Obj.program_headers())) + for (const typename ELFT::Phdr &Phdr : cantFail(Obj.program_headers())) if (Phdr.p_type == ELF::PT_GNU_RELRO) for (SectionRef SecRef : InputFile->sections()) handleSection(Phdr, SecRef); } -Error RewriteInstance::discoverStorage() { +template +Error RewriteInstance::discoverStorage(ELFObjectFile *ELFObjFile) { NamedRegionTimer T("discoverStorage", "discover storage", TimerGroupName, TimerGroupDesc, opts::TimeRewrite); - auto ELF64LEFile = cast(InputFile); - const ELFFile &Obj = ELF64LEFile->getELFFile(); + const ELFFile &Obj = ELFObjFile->getELFFile(); BC->StartFunctionAddress = Obj.getHeader().e_entry; NextAvailableAddress = 0; uint64_t NextAvailableOffset = 0; - Expected PHsOrErr = Obj.program_headers(); + auto PHsOrErr = Obj.program_headers(); if (Error E = PHsOrErr.takeError()) return E; - ELF64LE::PhdrRange PHs = PHsOrErr.get(); - for (const ELF64LE::Phdr &Phdr : PHs) { + auto PHs = PHsOrErr.get(); + for (const typename ELFT::Phdr &Phdr : PHs) { switch (Phdr.p_type) { case ELF::PT_LOAD: BC->FirstAllocAddress = std::min(BC->FirstAllocAddress, static_cast(Phdr.p_vaddr)); - NextAvailableAddress = std::max(NextAvailableAddress, - Phdr.p_vaddr + Phdr.p_memsz); - NextAvailableOffset = std::max(NextAvailableOffset, - Phdr.p_offset + Phdr.p_filesz); + NextAvailableAddress = + std::max(NextAvailableAddress, + static_cast(Phdr.p_vaddr) + Phdr.p_memsz); + NextAvailableOffset = + std::max(NextAvailableOffset, + static_cast(Phdr.p_offset) + Phdr.p_filesz); BC->SegmentMapInfo[Phdr.p_vaddr] = SegmentInfo{Phdr.p_vaddr, @@ -680,7 +679,7 @@ Error RewriteInstance::discoverStorage() { NextAvailableAddress = opts::CustomAllocationVMA; // Sanity check the user-supplied address and emit warnings if something // seems off. - for (const ELF64LE::Phdr &Phdr : PHs) { + for (const typename ELFT::Phdr &Phdr : PHs) { switch (Phdr.p_type) { case ELF::PT_LOAD: if (NextAvailableAddress >= Phdr.p_vaddr && @@ -743,8 +742,10 @@ Error RewriteInstance::discoverStorage() { if (opts::Instrument) Phnum += 2; - NextAvailableAddress += Phnum * sizeof(ELF64LEPhdrTy); - NextAvailableOffset += Phnum * sizeof(ELF64LEPhdrTy); + NextAvailableAddress += + Phnum * sizeof(typename ELFObjectFile::Elf_Phdr); + NextAvailableOffset += + Phnum * sizeof(typename ELFObjectFile::Elf_Phdr); // Align at cache line. NextAvailableAddress = alignTo(NextAvailableAddress, 64); @@ -1737,15 +1738,18 @@ void RewriteInstance::registerFragments() { // The first global symbol is identified by the symbol table sh_info value. // Used as local symbol search stopping point. - auto *ELF64LEFile = cast(InputFile); - const ELFFile &Obj = ELF64LEFile->getELFFile(); - auto *SymTab = llvm::find_if(cantFail(Obj.sections()), [](const auto &Sec) { - return Sec.sh_type == ELF::SHT_SYMTAB; - }); - assert(SymTab); - // Symtab sh_info contains the value one greater than the symbol table index - // of the last local symbol. - ELFSymbolRef LocalSymEnd = ELF64LEFile->toSymbolRef(SymTab, SymTab->sh_info); + auto getLocalSymEnd = [](auto *ELFFile) -> ELFSymbolRef { + const auto &Obj = ELFFile->getELFFile(); + auto *SymTab = llvm::find_if(cantFail(Obj.sections()), [](const auto &Sec) { + return Sec.sh_type == ELF::SHT_SYMTAB; + }); + assert(SymTab); + return ELFFile->toSymbolRef(SymTab, SymTab->sh_info); + }; + ELFSymbolRef LocalSymEnd = + isa(InputFile) + ? getLocalSymEnd(cast(InputFile)) + : getLocalSymEnd(cast(InputFile)); for (auto &Fragment : AmbiguousFragments) { const StringRef &ParentName = Fragment.first; @@ -2469,6 +2473,8 @@ int64_t getRelocationAddend(const ELFObjectFile *Obj, int64_t getRelocationAddend(const ELFObjectFileBase *Obj, const RelocationRef &Rel) { + if (auto *ELF32LE = dyn_cast(Obj)) + return getRelocationAddend(ELF32LE, Rel); return getRelocationAddend(cast(Obj), Rel); } @@ -2496,6 +2502,8 @@ uint32_t getRelocationSymbol(const ELFObjectFile *Obj, uint32_t getRelocationSymbol(const ELFObjectFileBase *Obj, const RelocationRef &Rel) { + if (auto *ELF32LE = dyn_cast(Obj)) + return getRelocationSymbol(ELF32LE, Rel); return getRelocationSymbol(cast(Obj), Rel); } } // anonymous namespace @@ -4559,9 +4567,11 @@ void RewriteInstance::updateSegmentInfo() { } } -void RewriteInstance::patchELFPHDRTable() { - auto ELF64LEFile = cast(InputFile); - const ELFFile &Obj = ELF64LEFile->getELFFile(); +template +void RewriteInstance::patchELFPHDRTable(ELFObjectFile *File) { + using PhdrTy = typename ELFT::Phdr; + + const ELFFile &Obj = File->getELFFile(); raw_fd_ostream &OS = Out->os(); Phnum = Obj.getHeader().e_phnum; @@ -4588,7 +4598,7 @@ void RewriteInstance::patchELFPHDRTable() { OS.seek(PHDRTableOffset); auto createPhdr = [](const SegmentInfo &SI) { - ELF64LEPhdrTy Phdr; + PhdrTy Phdr; Phdr.p_type = ELF::PT_LOAD; Phdr.p_offset = SI.FileOffset; Phdr.p_vaddr = SI.Address; @@ -4608,11 +4618,11 @@ void RewriteInstance::patchELFPHDRTable() { // Collect modified program headers, then insert new PT_LOAD segments // right after existing PT_LOAD segments to maintain ascending p_vaddr // order required by the ELF specification. - SmallVector Phdrs; + SmallVector Phdrs; bool SkippedGnuStack = false; - for (const ELF64LE::Phdr &Phdr : cantFail(Obj.program_headers())) { - ELF64LE::Phdr NewPhdr = Phdr; + for (const PhdrTy &Phdr : cantFail(Obj.program_headers())) { + PhdrTy NewPhdr = Phdr; switch (Phdr.p_type) { case ELF::PT_LOAD: { // Mark segment as executable if it contains BOLTReserved space. @@ -4626,8 +4636,8 @@ void RewriteInstance::patchELFPHDRTable() { NewPhdr.p_offset = PHDRTableOffset; NewPhdr.p_vaddr = PHDRTableAddress; NewPhdr.p_paddr = PHDRTableAddress; - NewPhdr.p_filesz = sizeof(NewPhdr) * Phnum; - NewPhdr.p_memsz = sizeof(NewPhdr) * Phnum; + NewPhdr.p_filesz = sizeof(PhdrTy) * Phnum; + NewPhdr.p_memsz = sizeof(PhdrTy) * Phnum; } break; case ELF::PT_GNU_EH_FRAME: { @@ -4663,16 +4673,15 @@ void RewriteInstance::patchELFPHDRTable() { // Insert new PT_LOAD segments right after the last existing PT_LOAD to // maintain ascending p_vaddr order. - auto LastPTLoad = llvm::find_if(reverse(Phdrs), [](const ELF64LE::Phdr &P) { - return P.p_type == ELF::PT_LOAD; - }); + auto LastPTLoad = llvm::find_if( + reverse(Phdrs), [](const PhdrTy &P) { return P.p_type == ELF::PT_LOAD; }); assert(LastPTLoad != Phdrs.rend() && "No existing PT_LOAD found"); auto InsertPos = LastPTLoad.base(); for (const SegmentInfo &SI : BC->NewSegments) InsertPos = std::next(Phdrs.insert(InsertPos, createPhdr(SI))); OS.write(reinterpret_cast(Phdrs.data()), - sizeof(ELF64LE::Phdr) * Phdrs.size()); + sizeof(PhdrTy) * Phdrs.size()); OS.seek(SavedPos); } @@ -4695,9 +4704,11 @@ uint64_t appendPadding(raw_pwrite_stream &OS, uint64_t Offset, } -void RewriteInstance::rewriteNoteSections() { - auto ELF64LEFile = cast(InputFile); - const ELFFile &Obj = ELF64LEFile->getELFFile(); +template +void RewriteInstance::rewriteNoteSections(ELFObjectFile *File) { + using ShdrTy = typename ELFT::Shdr; + + const ELFFile &Obj = File->getELFFile(); raw_fd_ostream &OS = Out->os(); uint64_t NextAvailableOffset = std::max( @@ -4705,13 +4716,13 @@ void RewriteInstance::rewriteNoteSections() { OS.seek(NextAvailableOffset); // Copy over non-allocatable section contents and update file offsets. - for (const ELF64LE::Shdr &Section : cantFail(Obj.sections())) { + for (const ShdrTy &Section : cantFail(Obj.sections())) { if (Section.sh_type == ELF::SHT_NULL) continue; if (Section.sh_flags & ELF::SHF_ALLOC) continue; - SectionRef SecRef = ELF64LEFile->toSectionRef(&Section); + SectionRef SecRef = File->toSectionRef(&Section); BinarySection *BSec = BC->getSectionForSectionRef(SecRef); assert(BSec && !BSec->isAllocatable() && "Matching non-allocatable BinarySection should exist."); diff --git a/bolt/test/Inputs/elf32-basic.yaml b/bolt/test/Inputs/elf32-basic.yaml new file mode 100644 index 000000000000..c1a55af025fa --- /dev/null +++ b/bolt/test/Inputs/elf32-basic.yaml @@ -0,0 +1,12 @@ +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_ARM +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [SHF_ALLOC, SHF_EXECINSTR] + Address: 0x10000 + Content: '00000000' diff --git a/bolt/test/elf32-basic.test b/bolt/test/elf32-basic.test new file mode 100644 index 000000000000..4df44d821564 --- /dev/null +++ b/bolt/test/elf32-basic.test @@ -0,0 +1,12 @@ +## Verify that BOLT accepts ELF32LE binaries and does not reject them with +## the old "Only 64-bit LE ELF binaries are supported" error. +## The binary uses EM_ARM which has no BOLT target support, so BOLT will fail +## with "Unrecognized machine in ELF file" -- that is expected. The point is +## that ELF32 parsing infrastructure works and does not reject the file +## outright. + +RUN: yaml2obj %p/Inputs/elf32-basic.yaml -o %t.exe +RUN: not llvm-bolt %t.exe -o /dev/null 2>&1 | FileCheck %s + +CHECK-NOT: Only 64-bit LE ELF binaries are supported +CHECK: BOLT-ERROR: Unrecognized machine in ELF file