[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.
This commit is contained in:
Brian Cain 2026-04-03 15:16:31 -05:00 committed by GitHub
parent 8a8434f22a
commit 98ced6cfd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 108 additions and 71 deletions

View File

@ -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 <typename ELFT> TYPE FUNC(object::ELFObjectFile<ELFT> *Obj); \
TYPE FUNC() { \
if (auto *ELF32LE = dyn_cast<object::ELF32LEObjectFile>(InputFile)) \
return FUNC(ELF32LE); \
if (auto *ELF64LE = dyn_cast<object::ELF64LEObjectFile>(InputFile)) \
return FUNC(ELF64LE); \
if (auto *ELF32BE = dyn_cast<object::ELF32BEObjectFile>(InputFile)) \
return FUNC(ELF32BE); \
auto *ELF64BE = cast<object::ELF64BEObjectFile>(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 <typename ELFT> TYPE FUNC(object::ELFObjectFile<ELFT> *Obj); \
TYPE FUNC() { \
if (auto *ELF32LE = dyn_cast<object::ELF32LEObjectFile>(InputFile)) \
return FUNC(ELF32LE); \
if (auto *ELF64LE = dyn_cast<object::ELF64LEObjectFile>(InputFile)) \
return FUNC(ELF64LE); \
if (auto *ELF32BE = dyn_cast<object::ELF32BEObjectFile>(InputFile)) \
return FUNC(ELF32BE); \
auto *ELF64BE = cast<object::ELF64BEObjectFile>(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,

View File

@ -374,8 +374,6 @@ MCPlusBuilder *createMCPlusBuilder(const Triple::ArchType Arch,
} // namespace bolt
} // namespace llvm
using ELF64LEPhdrTy = ELF64LEFile::Elf_Phdr;
namespace {
bool refersToReorderedSection(ErrorOr<BinarySection &> 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<ELF64LEObjectFile>(InputFile);
if (!ELF64LEFile) {
if (!isa<ELF64LEObjectFile>(InputFile) &&
!isa<ELF32LEObjectFile>(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<ELF64LE> &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 <typename ELFT>
void RewriteInstance::markGnuRelroSections(ELFObjectFile<ELFT> *ELFObjFile) {
using ELFShdrTy = typename ELFObjectFile<ELFT>::Elf_Shdr;
auto ELF64LEFile = cast<ELF64LEObjectFile>(InputFile);
const ELFFile<ELFT> &Obj = ELF64LEFile->getELFFile();
const ELFFile<ELFT> &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 <typename ELFT>
Error RewriteInstance::discoverStorage(ELFObjectFile<ELFT> *ELFObjFile) {
NamedRegionTimer T("discoverStorage", "discover storage", TimerGroupName,
TimerGroupDesc, opts::TimeRewrite);
auto ELF64LEFile = cast<ELF64LEObjectFile>(InputFile);
const ELFFile<ELF64LE> &Obj = ELF64LEFile->getELFFile();
const ELFFile<ELFT> &Obj = ELFObjFile->getELFFile();
BC->StartFunctionAddress = Obj.getHeader().e_entry;
NextAvailableAddress = 0;
uint64_t NextAvailableOffset = 0;
Expected<ELF64LE::PhdrRange> 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<uint64_t>(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<uint64_t>(Phdr.p_vaddr) + Phdr.p_memsz);
NextAvailableOffset =
std::max(NextAvailableOffset,
static_cast<uint64_t>(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<ELFT>::Elf_Phdr);
NextAvailableOffset +=
Phnum * sizeof(typename ELFObjectFile<ELFT>::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<ELF64LEObjectFile>(InputFile);
const ELFFile<ELF64LE> &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<ELF32LEObjectFile>(InputFile)
? getLocalSymEnd(cast<ELF32LEObjectFile>(InputFile))
: getLocalSymEnd(cast<ELF64LEObjectFile>(InputFile));
for (auto &Fragment : AmbiguousFragments) {
const StringRef &ParentName = Fragment.first;
@ -2469,6 +2473,8 @@ int64_t getRelocationAddend(const ELFObjectFile<ELFT> *Obj,
int64_t getRelocationAddend(const ELFObjectFileBase *Obj,
const RelocationRef &Rel) {
if (auto *ELF32LE = dyn_cast<ELF32LEObjectFile>(Obj))
return getRelocationAddend(ELF32LE, Rel);
return getRelocationAddend(cast<ELF64LEObjectFile>(Obj), Rel);
}
@ -2496,6 +2502,8 @@ uint32_t getRelocationSymbol(const ELFObjectFile<ELFT> *Obj,
uint32_t getRelocationSymbol(const ELFObjectFileBase *Obj,
const RelocationRef &Rel) {
if (auto *ELF32LE = dyn_cast<ELF32LEObjectFile>(Obj))
return getRelocationSymbol(ELF32LE, Rel);
return getRelocationSymbol(cast<ELF64LEObjectFile>(Obj), Rel);
}
} // anonymous namespace
@ -4559,9 +4567,11 @@ void RewriteInstance::updateSegmentInfo() {
}
}
void RewriteInstance::patchELFPHDRTable() {
auto ELF64LEFile = cast<ELF64LEObjectFile>(InputFile);
const ELFFile<ELF64LE> &Obj = ELF64LEFile->getELFFile();
template <typename ELFT>
void RewriteInstance::patchELFPHDRTable(ELFObjectFile<ELFT> *File) {
using PhdrTy = typename ELFT::Phdr;
const ELFFile<ELFT> &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<ELF64LEPhdrTy, 16> Phdrs;
SmallVector<PhdrTy, 16> 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<const char *>(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<ELF64LEObjectFile>(InputFile);
const ELFFile<ELF64LE> &Obj = ELF64LEFile->getELFFile();
template <typename ELFT>
void RewriteInstance::rewriteNoteSections(ELFObjectFile<ELFT> *File) {
using ShdrTy = typename ELFT::Shdr;
const ELFFile<ELFT> &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.");

View File

@ -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'

View File

@ -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