[llvm] Add symbol version support to llvm-ifs (PR #163030)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 8 00:14:12 PST 2026
https://github.com/ur4t updated https://github.com/llvm/llvm-project/pull/163030
>From 7636f89d71bca624976a3863d28d0cf6d1d2618d Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Sun, 12 Oct 2025 03:30:27 +0000
Subject: [PATCH 1/7] Add symbol version support to llvm-ifs
---
llvm/docs/CommandGuide/llvm-ifs.rst | 26 +-
llvm/include/llvm/InterfaceStub/IFSStub.h | 36 +-
llvm/lib/InterfaceStub/ELFObjHandler.cpp | 448 +++++++++++++++++++++-
llvm/lib/InterfaceStub/IFSHandler.cpp | 59 +++
llvm/lib/InterfaceStub/IFSStub.cpp | 10 +
llvm/tools/llvm-ifs/llvm-ifs.cpp | 19 +-
6 files changed, 577 insertions(+), 21 deletions(-)
diff --git a/llvm/docs/CommandGuide/llvm-ifs.rst b/llvm/docs/CommandGuide/llvm-ifs.rst
index e3582b365b61d..d310e62b14eb6 100644
--- a/llvm/docs/CommandGuide/llvm-ifs.rst
+++ b/llvm/docs/CommandGuide/llvm-ifs.rst
@@ -40,6 +40,12 @@ by the :program:`llvm-ifs`:
- { Name: sym2, Type: Func, Weak: false }
- { Name: sym3, Type: TLS }
- { Name: sym4, Type: Unknown, Warning: foo }
+ - { Name: sym5, Version: VER_1, Type: Func }
+ VersionDefinitions: /* Optional */
+ - { Name: libtest.so }
+ - { Name: VER_1 }
+ VersionRequirements: /* Optional */
+ - { File: libc.so.6, Names: [ GLIBC_2.14, GLIBC_2.4, GLIBC_2.2.5, GLIBC_2.3.4 ] }
...
* ``IFSVersion``: Version of the IFS file for reader compatibility.
@@ -67,6 +73,10 @@ by the :program:`llvm-ifs`:
+ ``Warning`` (optional): Warning text to output when this symbol is linked against.
+* ``VersionDefinitions`` (optional): A collection of symbol versions defined in this shared object file.
+
+* ``VersionRequirements`` (optional): A collection of symbol versions defined in external shared objects that this library depends on.
+
This YAML based text format contains everything that is needed to generate a
linkable ELF shared object as well as an Apple TAPI format file. The ordering
of symbols is sorted, so these files can be easily compared using diff tools.
@@ -81,12 +91,20 @@ A minimum ELF file that can be used by linker should have following sections pro
* ELF header.
+* Program headers. (optional)
+
* Section headers.
* Dynamic symbol table (``.dynsym`` section).
* Dynamic string table (``.dynstr`` section).
+* Version symbol table (``.gnu.version`` section). (optional)
+
+* Version definition table (``.gnu.version_d`` section). (optional)
+
+* Version requirement table (``.gnu.version_r`` section). (optional)
+
* Dynamic table (``.dynamic`` section).
+ ``DT_SYMTAB`` entry.
@@ -99,6 +117,12 @@ A minimum ELF file that can be used by linker should have following sections pro
+ ``DT_SONAME`` entry. (optional)
+ + ``DT_VERSYM`` entry. (optional)
+
+ + ``DT_VERDEF`` entry. (optional)
+
+ + ``DT_VERNEED`` entry. (optional)
+
* Section header string table (``.shstrtab`` section)
This ELF file may have compatibility issues with ELF analysis tools that rely on the program headers.
@@ -163,7 +187,7 @@ OPTIONS
triple in the output IFS file. If the value matches the target information
from the object file, this value will be used in the 'Target:' filed in the
generated IFS. If it conflicts with the input object file, an error will be
- reported and the program will stop.
+ reported and the program will stop.
.. option:: --hint-ifs-target
diff --git a/llvm/include/llvm/InterfaceStub/IFSStub.h b/llvm/include/llvm/InterfaceStub/IFSStub.h
index ddde889d46890..7321808dc1c04 100644
--- a/llvm/include/llvm/InterfaceStub/IFSStub.h
+++ b/llvm/include/llvm/InterfaceStub/IFSStub.h
@@ -52,8 +52,10 @@ enum class IFSBitWidthType {
struct IFSSymbol {
IFSSymbol() = default;
- explicit IFSSymbol(std::string SymbolName) : Name(std::move(SymbolName)) {}
+ explicit IFSSymbol(const std::string &Name, const std::string &Version)
+ : Name(Name), Version(Version) {}
std::string Name;
+ std::string Version;
std::optional<uint64_t> Size;
IFSSymbolType Type = IFSSymbolType::NoType;
bool Undefined = false;
@@ -62,6 +64,36 @@ struct IFSSymbol {
bool operator<(const IFSSymbol &RHS) const { return Name < RHS.Name; }
};
+struct IFSVerDef {
+ std::string Name;
+ std::vector<std::string> Parents;
+};
+
+inline bool operator==(const IFSVerDef &Lhs, const IFSVerDef &Rhs) {
+ if (Lhs.Name != Rhs.Name || Lhs.Parents != Rhs.Parents)
+ return false;
+ return true;
+}
+
+inline bool operator!=(const IFSVerDef &Lhs, const IFSVerDef &Rhs) {
+ return !(Lhs == Rhs);
+}
+
+struct IFSVerNeed {
+ std::string File;
+ std::vector<std::string> Names;
+};
+
+inline bool operator==(const IFSVerNeed &Lhs, const IFSVerNeed &Rhs) {
+ if (Lhs.File != Rhs.File || Lhs.Names != Rhs.Names)
+ return false;
+ return true;
+}
+
+inline bool operator!=(const IFSVerNeed &Lhs, const IFSVerNeed &Rhs) {
+ return !(Lhs == Rhs);
+}
+
struct IFSTarget {
std::optional<std::string> Triple;
std::optional<std::string> ObjectFormat;
@@ -94,6 +126,8 @@ struct IFSStub {
IFSTarget Target;
std::vector<std::string> NeededLibs;
std::vector<IFSSymbol> Symbols;
+ std::vector<IFSVerDef> VersionDefinitions;
+ std::vector<IFSVerNeed> VersionRequirements;
IFSStub() = default;
LLVM_ABI IFSStub(const IFSStub &Stub);
diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
index 9c81a8832c0f2..f35fb4a4346a9 100644
--- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp
+++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
@@ -39,6 +39,12 @@ struct DynamicEntries {
// Hash tables:
std::optional<uint64_t> ElfHash;
std::optional<uint64_t> GnuHash;
+ // Version tables:
+ std::optional<uint64_t> VerSym;
+ std::optional<uint64_t> VerDef;
+ std::optional<uint64_t> VerDefNum;
+ std::optional<uint64_t> VerNeed;
+ std::optional<uint64_t> VerNeedNum;
};
/// This initializes an ELF file header with information specific to a binary
@@ -124,6 +130,148 @@ template <class ELFT> class ELFSymbolTableBuilder {
llvm::SmallVector<Elf_Sym, 8> Symbols;
};
+template <class ELFT> class ELFVersionSymbolBuilder {
+public:
+ using Elf_Versym = typename ELFT::Versym;
+
+ ELFVersionSymbolBuilder() { Versions.push_back({}); }
+
+ void add(uint16_t Index) {
+ Elf_Versym VerSym;
+ VerSym.vs_index = Index;
+ Versions.push_back(VerSym);
+ }
+
+ size_t getSize() const { return Versions.size() * sizeof(Elf_Versym); }
+
+ void write(uint8_t *Buf) const {
+ memcpy(Buf, Versions.data(), Versions.size() * sizeof(Elf_Versym));
+ }
+
+private:
+ llvm::SmallVector<Elf_Versym, 8> Versions;
+};
+
+template <class ELFT> class ELFVersionDefinitionBuilder {
+public:
+ using Elf_Verdef = typename ELFT::Verdef;
+ using Elf_Verdaux = typename ELFT::Verdaux;
+
+ ELFVersionDefinitionBuilder() { VerDAux.push_back({}); }
+
+ void addDef(uint16_t Index, uint16_t Count, uint32_t Hash) {
+ Elf_Verdef VerDef;
+ VerDef.vd_version = VER_DEF_CURRENT;
+ VerDef.vd_flags = Index == 1 ? VER_FLG_BASE : 0;
+ VerDef.vd_ndx = Index;
+ VerDef.vd_cnt = Count;
+ VerDef.vd_hash = Hash;
+ VerDef.vd_aux = sizeof(Elf_Verdef);
+ VerDef.vd_next = sizeof(Elf_Verdef) + Count * sizeof(Elf_Verdaux);
+ this->VerDef.push_back(VerDef);
+ this->VerDAux.push_back({});
+ }
+
+ void addAux(uint16_t Vdndx, uint32_t Name) {
+ Elf_Verdaux VerDAux;
+ VerDAux.vda_name = Name;
+ VerDAux.vda_next = sizeof(Elf_Verdaux);
+ this->VerDAux[Vdndx].push_back(VerDAux);
+ }
+
+ void finalize() {
+ if (!VerDef.empty())
+ VerDef.back().vd_next = 0;
+ for (llvm::SmallVector<Elf_Verdaux, 8> &VerDAux : VerDAux)
+ if (!VerDAux.empty())
+ VerDAux.back().vda_next = 0;
+ }
+
+ size_t getSize() const {
+ size_t Count = 0;
+ for (const llvm::SmallVector<Elf_Verdaux, 8> &I : VerDAux)
+ Count += I.size();
+ return Count * sizeof(Elf_Verdaux) + VerDef.size() * sizeof(Elf_Verdef);
+ }
+
+ void write(uint8_t *Buf) const {
+ uint8_t *Ptr = Buf;
+ size_t Count = 0;
+ for (const Elf_Verdef &VerDef : VerDef) {
+ Count = sizeof(Elf_Verdef);
+ memcpy(Ptr, &VerDef, Count);
+ Ptr += Count;
+ Count = VerDef.vd_cnt * sizeof(Elf_Verdaux);
+ memcpy(Ptr, VerDAux[VerDef.vd_ndx].data(), Count);
+ Ptr += Count;
+ }
+ }
+
+private:
+ llvm::SmallVector<Elf_Verdef, 8> VerDef;
+ std::vector<llvm::SmallVector<Elf_Verdaux, 8>> VerDAux;
+};
+
+template <class ELFT> class ELFVersionRequirementBuilder {
+public:
+ using Elf_Verneed = typename ELFT::Verneed;
+ using Elf_Vernaux = typename ELFT::Vernaux;
+
+ void addFile(uint16_t Count, uint32_t File) {
+ Elf_Verneed VerNeed;
+ VerNeed.vn_version = VER_NEED_CURRENT;
+ VerNeed.vn_cnt = Count;
+ VerNeed.vn_file = File;
+ VerNeed.vn_aux = sizeof(Elf_Verneed);
+ VerNeed.vn_next = sizeof(Elf_Verneed) + Count * sizeof(Elf_Vernaux);
+ this->VerNeed.push_back(VerNeed);
+ this->VerNAux.push_back({});
+ }
+
+ void addAux(uint16_t Vnndx, uint32_t Hash, uint16_t Vdndx, uint32_t Name) {
+ Elf_Vernaux VerNAux;
+ VerNAux.vna_hash = Hash;
+ VerNAux.vna_flags = 0;
+ VerNAux.vna_other = Vdndx;
+ VerNAux.vna_name = Name;
+ VerNAux.vna_next = sizeof(Elf_Vernaux);
+ this->VerNAux[Vnndx].push_back(VerNAux);
+ }
+
+ void finalize() {
+ if (!VerNeed.empty())
+ VerNeed.back().vn_next = 0;
+ for (llvm::SmallVector<Elf_Vernaux, 8> &VerNAux : VerNAux)
+ if (!VerNAux.empty())
+ VerNAux.back().vna_next = 0;
+ }
+
+ size_t getSize() const {
+ size_t Count = 0;
+ for (const llvm::SmallVector<Elf_Vernaux, 8> &I : VerNAux)
+ Count += I.size();
+ return Count * sizeof(Elf_Vernaux) + VerNeed.size() * sizeof(Elf_Verneed);
+ }
+
+ void write(uint8_t *Buf) const {
+ uint8_t *Ptr = Buf;
+ size_t Count = 0;
+ size_t Vnndx = 0;
+ for (const Elf_Verneed &VerNeed : VerNeed) {
+ Count = sizeof(Elf_Verneed);
+ memcpy(Ptr, &VerNeed, Count);
+ Ptr += Count;
+ Count = VerNeed.vn_cnt * sizeof(Elf_Vernaux);
+ memcpy(Ptr, VerNAux[Vnndx].data(), Count);
+ Ptr += Count;
+ }
+ }
+
+private:
+ llvm::SmallVector<Elf_Verneed, 8> VerNeed;
+ std::vector<llvm::SmallVector<Elf_Vernaux, 8>> VerNAux;
+};
+
template <class ELFT> class ELFDynamicTableBuilder {
public:
using Elf_Dyn = typename ELFT::Dyn;
@@ -188,17 +336,43 @@ template <class ELFT> class ELFStubBuilder {
DynTab.Align = sizeof(Elf_Addr);
ShStrTab.Name = ".shstrtab";
ShStrTab.Align = 1;
+ VerSym.Name = ".gnu.version";
+ VerSym.Align = 2;
+ VerDef.Name = ".gnu.version_d";
+ VerDef.Align = sizeof(Elf_Addr);
+ VerNeed.Name = ".gnu.version_r";
+ VerNeed.Align = sizeof(Elf_Addr);
// Populate string tables.
for (const IFSSymbol &Sym : Stub.Symbols)
DynStr.Content.add(Sym.Name);
+ for (const IFSVerDef &VerDef : Stub.VersionDefinitions)
+ DynStr.Content.add(VerDef.Name);
+ for (const IFSVerNeed &VerNeed : Stub.VersionRequirements)
+ for (const std::string &VerNeedName : VerNeed.Names)
+ DynStr.Content.add(VerNeedName);
for (const std::string &Lib : Stub.NeededLibs)
DynStr.Content.add(Lib);
if (Stub.SoName)
DynStr.Content.add(*Stub.SoName);
- std::vector<OutputSection<ELFT> *> Sections = {&DynSym, &DynStr, &DynTab,
- &ShStrTab};
+ WriteVerSym = any_of(Stub.Symbols, [](const IFSSymbol &Sym) {
+ return !Sym.Version.empty();
+ });
+ WriteVerDef = !Stub.VersionDefinitions.empty();
+ WriteVerNeed = !Stub.VersionRequirements.empty();
+
+ std::vector<OutputSection<ELFT> *> Sections;
+ Sections.push_back(&DynSym);
+ Sections.push_back(&DynStr);
+ if (WriteVerSym)
+ Sections.push_back(&VerSym);
+ if (WriteVerDef)
+ Sections.push_back(&VerDef);
+ if (WriteVerNeed)
+ Sections.push_back(&VerNeed);
+ Sections.push_back(&DynTab);
+ Sections.push_back(&ShStrTab);
const OutputSection<ELFT> *LastSection = Sections.back();
// Now set the Index and put sections names into ".shstrtab".
uint64_t Index = 1;
@@ -224,18 +398,65 @@ template <class ELFT> class ELFStubBuilder {
}
DynSym.Size = DynSym.Content.getSize();
+ std::map<std::string, uint16_t> VerDefMap;
+ size_t Vdndx = 0;
+ size_t Vnndx = 0;
+
+ // Populate symbol version definition table.
+ for (const IFSVerDef &IFSVerDef : Stub.VersionDefinitions) {
+ Vdndx++;
+ VerDef.Content.addDef(Vdndx, IFSVerDef.Parents.size() + 1,
+ hashSysV(IFSVerDef.Name));
+ VerDef.Content.addAux(Vdndx, DynStr.Content.getOffset(IFSVerDef.Name));
+ for (const std::string &Parent : IFSVerDef.Parents) {
+ VerDef.Content.addAux(Vdndx, DynStr.Content.getOffset(Parent));
+ }
+ VerDefMap.insert({IFSVerDef.Name, Vdndx});
+ }
+ VerDef.Content.finalize();
+ VerDef.Size = VerDef.Content.getSize();
+
+ // Populate symbol version requirement table.
+ for (const IFSVerNeed &IFSVerNeed : Stub.VersionRequirements) {
+ VerNeed.Content.addFile(IFSVerNeed.Names.size(),
+ DynStr.Content.getOffset(IFSVerNeed.File));
+ for (const std::string &Name : IFSVerNeed.Names) {
+ Vdndx++;
+ VerNeed.Content.addAux(Vnndx, hashSysV(Name), Vdndx,
+ DynStr.Content.getOffset(Name));
+ VerDefMap.insert({Name, Vdndx});
+ }
+ Vnndx++;
+ }
+ VerNeed.Content.finalize();
+ VerNeed.Size = VerNeed.Content.getSize();
+
+ // Populate dynamic symbol version table.
+ for (const IFSSymbol &Sym : Stub.Symbols)
+ VerSym.Content.add(Sym.Version.empty() ? 1 : VerDefMap[Sym.Version]);
+ VerSym.Size = VerSym.Content.getSize();
+
// Poplulate dynamic table.
size_t DynSymIndex = DynTab.Content.addAddr(DT_SYMTAB, 0);
size_t DynStrIndex = DynTab.Content.addAddr(DT_STRTAB, 0);
+ size_t VerSymIndex;
+ size_t VerDefIndex;
+ size_t VerNeedIndex;
DynTab.Content.addValue(DT_STRSZ, DynSym.Size);
for (const std::string &Lib : Stub.NeededLibs)
DynTab.Content.addValue(DT_NEEDED, DynStr.Content.getOffset(Lib));
if (Stub.SoName)
DynTab.Content.addValue(DT_SONAME,
DynStr.Content.getOffset(*Stub.SoName));
+ if (WriteVerSym)
+ VerSymIndex = DynTab.Content.addAddr(DT_VERSYM, 0);
+ if (WriteVerDef)
+ VerDefIndex = DynTab.Content.addAddr(DT_VERDEF, 0);
+ if (WriteVerNeed)
+ VerNeedIndex = DynTab.Content.addAddr(DT_VERNEED, 0);
DynTab.Size = DynTab.Content.getSize();
// Calculate sections' addresses and offsets.
- uint64_t CurrentOffset = sizeof(Elf_Ehdr);
+ uint64_t CurrentOffset = sizeof(Elf_Ehdr) + ElfPhnum * sizeof(Elf_Phdr);
for (OutputSection<ELFT> *Sec : Sections) {
Sec->Offset = alignTo(CurrentOffset, Sec->Align);
Sec->Addr = Sec->Offset;
@@ -244,18 +465,50 @@ template <class ELFT> class ELFStubBuilder {
// Fill Addr back to dynamic table.
DynTab.Content.modifyAddr(DynSymIndex, DynSym.Addr);
DynTab.Content.modifyAddr(DynStrIndex, DynStr.Addr);
+ if (WriteVerSym)
+ DynTab.Content.modifyAddr(VerSymIndex, VerSym.Addr);
+ if (WriteVerDef)
+ DynTab.Content.modifyAddr(VerDefIndex, VerDef.Addr);
+ if (WriteVerNeed)
+ DynTab.Content.modifyAddr(VerNeedIndex, VerNeed.Addr);
// Write section headers of string tables.
fillSymTabShdr(DynSym, SHT_DYNSYM);
fillStrTabShdr(DynStr, SHF_ALLOC);
fillDynTabShdr(DynTab);
fillStrTabShdr(ShStrTab);
+ if (WriteVerSym)
+ fillVerSymShdr(VerSym);
+ if (WriteVerDef)
+ fillVerDefShdr(VerDef, Stub.VersionDefinitions.size());
+ if (WriteVerNeed)
+ fillVerNeedShdr(VerNeed, Stub.VersionRequirements.size());
// Finish initializing the ELF header.
initELFHeader<ELFT>(ElfHeader, static_cast<uint16_t>(*Stub.Target.Arch));
ElfHeader.e_shstrndx = ShStrTab.Index;
ElfHeader.e_shnum = LastSection->Index + 1;
+ ElfHeader.e_phnum = ElfPhnum;
+ ElfHeader.e_phoff = sizeof(Elf_Ehdr);
ElfHeader.e_shoff =
alignTo(LastSection->Offset + LastSection->Size, sizeof(Elf_Addr));
+
+ ElfProgramHeaders[0].p_type = PT_LOAD;
+ ElfProgramHeaders[0].p_offset = 0;
+ ElfProgramHeaders[0].p_vaddr = 0;
+ ElfProgramHeaders[0].p_paddr = 0;
+ ElfProgramHeaders[0].p_filesz = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[0].p_memsz = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[0].p_flags = PF_R;
+ ElfProgramHeaders[0].p_align = 0x1000;
+
+ ElfProgramHeaders[1].p_type = PT_DYNAMIC;
+ ElfProgramHeaders[1].p_offset = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_vaddr = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_paddr = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_filesz = DynTab.Shdr.sh_size;
+ ElfProgramHeaders[1].p_memsz = DynTab.Shdr.sh_size;
+ ElfProgramHeaders[1].p_flags = PF_R | PF_W;
+ ElfProgramHeaders[1].p_align = DynTab.Shdr.sh_addralign;
}
size_t getSize() const {
@@ -264,22 +517,45 @@ template <class ELFT> class ELFStubBuilder {
void write(uint8_t *Data) const {
write(Data, ElfHeader);
+ for (size_t I = 0; I < ElfPhnum; I++)
+ write(Data + ElfHeader.e_phoff + I * sizeof(Elf_Phdr),
+ ElfProgramHeaders[I]);
DynSym.Content.write(Data + DynSym.Shdr.sh_offset);
DynStr.Content.write(Data + DynStr.Shdr.sh_offset);
DynTab.Content.write(Data + DynTab.Shdr.sh_offset);
ShStrTab.Content.write(Data + ShStrTab.Shdr.sh_offset);
+ if (WriteVerSym)
+ VerSym.Content.write(Data + VerSym.Shdr.sh_offset);
+ if (WriteVerDef)
+ VerDef.Content.write(Data + VerDef.Shdr.sh_offset);
+ if (WriteVerNeed)
+ VerNeed.Content.write(Data + VerNeed.Shdr.sh_offset);
writeShdr(Data, DynSym);
writeShdr(Data, DynStr);
writeShdr(Data, DynTab);
writeShdr(Data, ShStrTab);
+ if (WriteVerSym)
+ writeShdr(Data, VerSym);
+ if (WriteVerDef)
+ writeShdr(Data, VerDef);
+ if (WriteVerNeed)
+ writeShdr(Data, VerNeed);
}
private:
Elf_Ehdr ElfHeader;
+ static constexpr auto ElfPhnum = 2;
+ Elf_Phdr ElfProgramHeaders[ElfPhnum];
ContentSection<ELFStringTableBuilder, ELFT> DynStr;
ContentSection<ELFStringTableBuilder, ELFT> ShStrTab;
ContentSection<ELFSymbolTableBuilder<ELFT>, ELFT> DynSym;
ContentSection<ELFDynamicTableBuilder<ELFT>, ELFT> DynTab;
+ ContentSection<ELFVersionSymbolBuilder<ELFT>, ELFT> VerSym;
+ ContentSection<ELFVersionDefinitionBuilder<ELFT>, ELFT> VerDef;
+ ContentSection<ELFVersionRequirementBuilder<ELFT>, ELFT> VerNeed;
+ bool WriteVerSym = false;
+ bool WriteVerDef = false;
+ bool WriteVerNeed = false;
template <class T> static void write(uint8_t *Data, const T &Value) {
*reinterpret_cast<T *>(Data) = Value;
@@ -298,6 +574,7 @@ template <class ELFT> class ELFStubBuilder {
StrTab.Shdr.sh_entsize = 0;
StrTab.Shdr.sh_link = 0;
}
+
void fillSymTabShdr(ContentSection<ELFSymbolTableBuilder<ELFT>, ELFT> &SymTab,
uint32_t ShType) const {
SymTab.Shdr.sh_type = ShType;
@@ -314,6 +591,7 @@ template <class ELFT> class ELFStubBuilder {
SymTab.Shdr.sh_entsize = sizeof(Elf_Sym);
SymTab.Shdr.sh_link = this->DynStr.Index;
}
+
void fillDynTabShdr(
ContentSection<ELFDynamicTableBuilder<ELFT>, ELFT> &DynTab) const {
DynTab.Shdr.sh_type = SHT_DYNAMIC;
@@ -327,6 +605,51 @@ template <class ELFT> class ELFStubBuilder {
DynTab.Shdr.sh_entsize = sizeof(Elf_Dyn);
DynTab.Shdr.sh_link = this->DynStr.Index;
}
+
+ void fillVerSymShdr(
+ ContentSection<ELFVersionSymbolBuilder<ELFT>, ELFT> &VerSym) const {
+ VerSym.Shdr.sh_type = SHT_GNU_versym;
+ VerSym.Shdr.sh_flags = SHF_ALLOC;
+ VerSym.Shdr.sh_addr = VerSym.Addr;
+ VerSym.Shdr.sh_offset = VerSym.Offset;
+ VerSym.Shdr.sh_info = 0;
+ VerSym.Shdr.sh_size = VerSym.Size;
+ VerSym.Shdr.sh_name = this->ShStrTab.Content.getOffset(VerSym.Name);
+ VerSym.Shdr.sh_addralign = VerSym.Align;
+ VerSym.Shdr.sh_entsize = sizeof(uint16_t);
+ VerSym.Shdr.sh_link = this->DynSym.Index;
+ }
+
+ void fillVerDefShdr(
+ ContentSection<ELFVersionDefinitionBuilder<ELFT>, ELFT> &VerDef,
+ size_t Size) const {
+ VerDef.Shdr.sh_type = SHT_GNU_verdef;
+ VerDef.Shdr.sh_flags = SHF_ALLOC;
+ VerDef.Shdr.sh_addr = VerDef.Addr;
+ VerDef.Shdr.sh_offset = VerDef.Offset;
+ VerDef.Shdr.sh_info = Size;
+ VerDef.Shdr.sh_size = VerDef.Size;
+ VerDef.Shdr.sh_name = this->ShStrTab.Content.getOffset(VerDef.Name);
+ VerDef.Shdr.sh_addralign = VerDef.Align;
+ VerDef.Shdr.sh_entsize = sizeof(Elf_Dyn);
+ VerDef.Shdr.sh_link = this->DynStr.Index;
+ }
+
+ void fillVerNeedShdr(
+ ContentSection<ELFVersionRequirementBuilder<ELFT>, ELFT> &VerNeed,
+ size_t Size) const {
+ VerNeed.Shdr.sh_type = SHT_GNU_verneed;
+ VerNeed.Shdr.sh_flags = SHF_ALLOC;
+ VerNeed.Shdr.sh_addr = VerNeed.Addr;
+ VerNeed.Shdr.sh_offset = VerNeed.Offset;
+ VerNeed.Shdr.sh_info = Size;
+ VerNeed.Shdr.sh_size = VerNeed.Size;
+ VerNeed.Shdr.sh_name = this->ShStrTab.Content.getOffset(VerNeed.Name);
+ VerNeed.Shdr.sh_addralign = VerNeed.Align;
+ VerNeed.Shdr.sh_entsize = sizeof(Elf_Dyn);
+ VerNeed.Shdr.sh_link = this->DynStr.Index;
+ }
+
uint64_t shdrOffset(const OutputSection<ELFT> &Sec) const {
return ElfHeader.e_shoff + Sec.Index * sizeof(Elf_Shdr);
}
@@ -371,6 +694,12 @@ template <class ELFT> class DynSym {
return getDynamicData(DynEnt.DynSymAddr, "dynamic symbol table");
}
+ const Elf_Shdr *getVerSym() { return findHdr(SHT_GNU_versym); }
+
+ const Elf_Shdr *getVerDef() { return findHdr(SHT_GNU_verdef); }
+
+ const Elf_Shdr *getVerNeed() { return findHdr(SHT_GNU_verneed); }
+
Expected<StringRef> getDynStr() {
if (DynSymHdr)
return ElfFile.getStringTableForSymtab(*DynSymHdr, Shdrs);
@@ -386,11 +715,11 @@ template <class ELFT> class DynSym {
DynSym(const ELFFile<ELFT> &ElfFile, const DynamicEntries &DynEnt,
Elf_Shdr_Range Shdrs)
: ElfFile(ElfFile), DynEnt(DynEnt), Shdrs(Shdrs),
- DynSymHdr(findDynSymHdr()) {}
+ DynSymHdr(findHdr(SHT_DYNSYM)) {}
- const Elf_Shdr *findDynSymHdr() {
+ const Elf_Shdr *findHdr(uint32_t Type) {
for (const Elf_Shdr &Sec : Shdrs)
- if (Sec.sh_type == SHT_DYNSYM) {
+ if (Sec.sh_type == Type) {
// If multiple .dynsym are present, use the first one.
// This behavior aligns with llvm::object::ELFFile::getDynSymtabSize()
return &Sec;
@@ -478,6 +807,22 @@ static Error populateDynamic(DynamicEntries &Dyn,
break;
case DT_GNU_HASH:
Dyn.GnuHash = Entry.d_un.d_ptr;
+ break;
+ case DT_VERSYM:
+ Dyn.VerSym = Entry.d_un.d_ptr;
+ break;
+ case DT_VERDEF:
+ Dyn.VerDef = Entry.d_un.d_ptr;
+ break;
+ case DT_VERDEFNUM:
+ Dyn.VerDefNum = Entry.d_un.d_val;
+ break;
+ case DT_VERNEED:
+ Dyn.VerNeed = Entry.d_un.d_ptr;
+ break;
+ case DT_VERNEEDNUM:
+ Dyn.VerNeedNum = Entry.d_un.d_val;
+ break;
}
}
@@ -515,11 +860,12 @@ static Error populateDynamic(DynamicEntries &Dyn,
/// information from a binary ELFT::Sym.
///
/// @param SymName The desired name of the IFSSymbol.
+/// @param SymVer The desired version of the IFSSymbol.
/// @param RawSym ELFT::Sym to extract symbol information from.
template <class ELFT>
-static IFSSymbol createELFSym(StringRef SymName,
+static IFSSymbol createELFSym(StringRef SymName, StringRef SymVer,
const typename ELFT::Sym &RawSym) {
- IFSSymbol TargetSym{std::string(SymName)};
+ IFSSymbol TargetSym{std::string(SymName), std::string(SymVer)};
uint8_t Binding = RawSym.getBinding();
if (Binding == STB_WEAK)
TargetSym.Weak = true;
@@ -541,14 +887,22 @@ static IFSSymbol createELFSym(StringRef SymName,
/// from an ELF binary.
///
/// @param TargetStub IFSStub to add symbols to.
+/// @param ElfFile Elf File object.
/// @param DynSym Range of dynamic symbols to add to TargetStub.
+/// @param VerSymPtr Pointer to symbol version table in Elf File.
+/// @param VersionMap Versions of dynamic symbols.
/// @param DynStr StringRef to the dynamic string table.
template <class ELFT>
-static Error populateSymbols(IFSStub &TargetStub,
- const typename ELFT::SymRange DynSym,
- StringRef DynStr) {
+static Error
+populateSymbols(IFSStub &TargetStub, const ELFFile<ELFT> &ElfFile,
+ const typename ELFT::SymRange DynSym,
+ const typename ELFT::Shdr *VerSymPtr,
+ const SmallVector<std::optional<VersionEntry>> &VersionMap,
+ StringRef DynStr) {
// Skips the first symbol since it's the NULL symbol.
+ size_t I = 0;
for (auto RawSym : DynSym.drop_front(1)) {
+ I++;
// If a symbol does not have global or weak binding, ignore it.
uint8_t Binding = RawSym.getBinding();
if (!(Binding == STB_GLOBAL || Binding == STB_WEAK))
@@ -562,7 +916,22 @@ static Error populateSymbols(IFSStub &TargetStub,
Expected<StringRef> SymName = terminatedSubstr(DynStr, RawSym.st_name);
if (!SymName)
return SymName.takeError();
- IFSSymbol Sym = createELFSym<ELFT>(*SymName, RawSym);
+ std::string VersionName;
+ if (VerSymPtr) {
+ Expected<const typename ELFT::Versym *> VerEntryOrErr =
+ ElfFile.template getEntry<typename ELFT::Versym>(*VerSymPtr, I);
+ if (!VerEntryOrErr)
+ return appendToError(VerEntryOrErr.takeError(),
+ "when reading symbol versions");
+ size_t VersionIndex = (*VerEntryOrErr)->vs_index & VERSYM_VERSION;
+ if (VersionIndex > VersionMap.size() || !VersionMap[VersionIndex]) {
+ return createError("SHT_GNU_versym section refers to a version index " +
+ Twine(VersionIndex) + " which is missing");
+ }
+ if (VersionIndex != VER_NDX_LOCAL && VersionIndex != VER_NDX_GLOBAL)
+ VersionName = VersionMap[VersionIndex]->Name.c_str();
+ }
+ IFSSymbol Sym = createELFSym<ELFT>(*SymName, VersionName, RawSym);
TargetStub.Symbols.push_back(std::move(Sym));
// TODO: Populate symbol warning.
}
@@ -577,6 +946,7 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
using Elf_Dyn_Range = typename ELFT::DynRange;
using Elf_Sym_Range = typename ELFT::SymRange;
using Elf_Sym = typename ELFT::Sym;
+ using Elf_Shdr = typename ELFT::Shdr;
std::unique_ptr<IFSStub> DestStub = std::make_unique<IFSStub>();
const ELFFile<ELFT> &ElfFile = ElfObj.getELFFile();
// Fetch .dynamic table.
@@ -627,11 +997,50 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
DestStub->NeededLibs.push_back(std::string(*LibNameOrErr));
}
+ const Elf_Shdr *VerDefPtr = EDynSym->getVerDef();
+ if (VerDefPtr) {
+ Expected<std::vector<VerDef>> VerDefOrError =
+ ElfFile.getVersionDefinitions(*VerDefPtr);
+ if (!VerDefOrError)
+ return appendToError(VerDefOrError.takeError(),
+ "when reading symbol version definitions");
+ for (const VerDef &VerDef : *VerDefOrError) {
+ std::vector<std::string> Parents;
+ for (const VerdAux &VerDAux : VerDef.AuxV) {
+ Parents.push_back(VerDAux.Name);
+ }
+ DestStub->VersionDefinitions.push_back({VerDef.Name, std::move(Parents)});
+ }
+ }
+
+ const Elf_Shdr *VerNeedPtr = EDynSym->getVerNeed();
+ if (VerNeedPtr) {
+ Expected<std::vector<VerNeed>> VerNeedOrError =
+ ElfFile.getVersionDependencies(*VerNeedPtr);
+ if (!VerNeedOrError)
+ return appendToError(VerNeedOrError.takeError(),
+ "when reading symbol version needs");
+ for (const VerNeed &VerNeed : *VerNeedOrError) {
+ std::string File = VerNeed.File;
+ IFSVerNeed IFSVerNeed;
+ IFSVerNeed.File = VerNeed.File;
+ for (const VernAux &VerNAux : VerNeed.AuxV) {
+ IFSVerNeed.Names.push_back(VerNAux.Name.c_str());
+ }
+ DestStub->VersionRequirements.push_back(IFSVerNeed);
+ }
+ }
+
// Populate Symbols from .dynsym table and dynamic string table.
Expected<uint64_t> SymCount = ElfFile.getDynSymtabSize();
if (!SymCount)
return SymCount.takeError();
if (*SymCount > 0) {
+ Expected<SmallVector<std::optional<VersionEntry>>> VersionsOrError =
+ ElfFile.loadVersionMap(VerNeedPtr, VerDefPtr);
+ if (!VersionsOrError)
+ return appendToError(VersionsOrError.takeError(),
+ "when reading dynamic symbol versions");
// Get pointer to in-memory location of .dynsym section.
Expected<const uint8_t *> DynSymPtr = EDynSym->getDynSym();
if (!DynSymPtr)
@@ -639,7 +1048,9 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
"when locating .dynsym section contents");
Elf_Sym_Range DynSyms = ArrayRef<Elf_Sym>(
reinterpret_cast<const Elf_Sym *>(*DynSymPtr), *SymCount);
- Error SymReadError = populateSymbols<ELFT>(*DestStub, DynSyms, DynStr);
+ Error SymReadError =
+ populateSymbols<ELFT>(*DestStub, ElfFile, DynSyms, EDynSym->getVerSym(),
+ *VersionsOrError, DynStr);
if (SymReadError)
return appendToError(std::move(SymReadError),
"when reading dynamic symbols");
@@ -695,13 +1106,16 @@ Expected<std::unique_ptr<IFSStub>> readELFFile(MemoryBufferRef Buf) {
}
Binary *Bin = BinOrErr->get();
- if (auto Obj = dyn_cast<ELFObjectFile<ELF32LE>>(Bin)) {
+ if (auto *Obj = dyn_cast<ELFObjectFile<ELF32LE>>(Bin)) {
return buildStub(*Obj);
- } else if (auto Obj = dyn_cast<ELFObjectFile<ELF64LE>>(Bin)) {
+ }
+ if (auto *Obj = dyn_cast<ELFObjectFile<ELF64LE>>(Bin)) {
return buildStub(*Obj);
- } else if (auto Obj = dyn_cast<ELFObjectFile<ELF32BE>>(Bin)) {
+ }
+ if (auto *Obj = dyn_cast<ELFObjectFile<ELF32BE>>(Bin)) {
return buildStub(*Obj);
- } else if (auto Obj = dyn_cast<ELFObjectFile<ELF64BE>>(Bin)) {
+ }
+ if (auto *Obj = dyn_cast<ELFObjectFile<ELF64BE>>(Bin)) {
return buildStub(*Obj);
}
return createStringError(errc::not_supported, "unsupported binary format");
diff --git a/llvm/lib/InterfaceStub/IFSHandler.cpp b/llvm/lib/InterfaceStub/IFSHandler.cpp
index 39dc46601b2a0..fa4b5506be4c3 100644
--- a/llvm/lib/InterfaceStub/IFSHandler.cpp
+++ b/llvm/lib/InterfaceStub/IFSHandler.cpp
@@ -24,10 +24,26 @@ using namespace llvm;
using namespace llvm::ifs;
LLVM_YAML_IS_SEQUENCE_VECTOR(IFSSymbol)
+LLVM_YAML_IS_SEQUENCE_VECTOR(IFSVerDef)
+LLVM_YAML_IS_SEQUENCE_VECTOR(IFSVerNeed)
+LLVM_YAML_STRONG_TYPEDEF(std::string, FlowString)
+LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FlowString)
namespace llvm {
namespace yaml {
+template <> struct ScalarTraits<FlowString> {
+ static void output(const FlowString &Value, void *, llvm::raw_ostream &Out) {
+ Out << Value;
+ }
+
+ static StringRef input(StringRef Scalar, void *, FlowString &Value) {
+ return Scalar;
+ }
+
+ static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+};
+
/// YAML traits for ELFSymbolType.
template <> struct ScalarEnumerationTraits<IFSSymbolType> {
static void enumeration(IO &IO, IFSSymbolType &SymbolType) {
@@ -116,6 +132,7 @@ template <> struct MappingTraits<IFSTarget> {
template <> struct MappingTraits<IFSSymbol> {
static void mapping(IO &IO, IFSSymbol &Symbol) {
IO.mapRequired("Name", Symbol.Name);
+ IO.mapOptional("Version", Symbol.Version, "");
IO.mapRequired("Type", Symbol.Type);
// The need for symbol size depends on the symbol type.
if (Symbol.Type == IFSSymbolType::NoType) {
@@ -135,6 +152,44 @@ template <> struct MappingTraits<IFSSymbol> {
static const bool flow = true; // NOLINT(readability-identifier-naming)
};
+/// YAML traits for ELFVersionDefinition.
+template <> struct MappingTraits<IFSVerDef> {
+ static void mapping(IO &IO, IFSVerDef &VerDef) {
+ IO.mapRequired("Name", VerDef.Name);
+ if (IO.outputting()) {
+ std::vector<FlowString> Parents;
+ for (const std::string &Parent : VerDef.Parents) {
+ Parents.push_back(Parent);
+ }
+ IO.mapOptional("Parents", Parents);
+ } else {
+ IO.mapOptional("Parents", VerDef.Parents);
+ }
+ }
+
+ // Compacts symbol information into a single line.
+ static const bool flow = true; // NOLINT(readability-identifier-naming)
+};
+
+/// YAML traits for ELFVersionRequirements.
+template <> struct MappingTraits<IFSVerNeed> {
+ static void mapping(IO &IO, IFSVerNeed &VerNeed) {
+ IO.mapRequired("File", VerNeed.File);
+ if (IO.outputting()) {
+ std::vector<FlowString> Names;
+ for (const std::string &Parent : VerNeed.Names) {
+ Names.push_back(Parent);
+ }
+ IO.mapOptional("Names", Names);
+ } else {
+ IO.mapOptional("Names", VerNeed.Names);
+ }
+ }
+
+ // Compacts symbol information into a single line.
+ static const bool flow = true; // NOLINT(readability-identifier-naming)
+};
+
/// YAML traits for ELFStub objects.
template <> struct MappingTraits<IFSStub> {
static void mapping(IO &IO, IFSStub &Stub) {
@@ -145,6 +200,8 @@ template <> struct MappingTraits<IFSStub> {
IO.mapOptional("Target", Stub.Target);
IO.mapOptional("NeededLibs", Stub.NeededLibs);
IO.mapRequired("Symbols", Stub.Symbols);
+ IO.mapOptional("VersionDefinitions", Stub.VersionDefinitions);
+ IO.mapOptional("VersionRequirements", Stub.VersionRequirements);
}
};
@@ -158,6 +215,8 @@ template <> struct MappingTraits<IFSStubTriple> {
IO.mapOptional("Target", Stub.Target.Triple);
IO.mapOptional("NeededLibs", Stub.NeededLibs);
IO.mapRequired("Symbols", Stub.Symbols);
+ IO.mapOptional("VersionDefinitions", Stub.VersionDefinitions);
+ IO.mapOptional("VersionRequirements", Stub.VersionRequirements);
}
};
} // end namespace yaml
diff --git a/llvm/lib/InterfaceStub/IFSStub.cpp b/llvm/lib/InterfaceStub/IFSStub.cpp
index cde15ccfdba06..d797a9d55593b 100644
--- a/llvm/lib/InterfaceStub/IFSStub.cpp
+++ b/llvm/lib/InterfaceStub/IFSStub.cpp
@@ -19,6 +19,8 @@ IFSStub::IFSStub(IFSStub const &Stub) {
SoName = Stub.SoName;
NeededLibs = Stub.NeededLibs;
Symbols = Stub.Symbols;
+ VersionDefinitions = Stub.VersionDefinitions;
+ VersionRequirements = Stub.VersionRequirements;
}
IFSStub::IFSStub(IFSStub &&Stub) {
@@ -27,6 +29,8 @@ IFSStub::IFSStub(IFSStub &&Stub) {
SoName = std::move(Stub.SoName);
NeededLibs = std::move(Stub.NeededLibs);
Symbols = std::move(Stub.Symbols);
+ VersionDefinitions = std::move(Stub.VersionDefinitions);
+ VersionRequirements = std::move(Stub.VersionRequirements);
}
IFSStubTriple::IFSStubTriple(IFSStubTriple const &Stub) : IFSStub() {
@@ -35,6 +39,8 @@ IFSStubTriple::IFSStubTriple(IFSStubTriple const &Stub) : IFSStub() {
SoName = Stub.SoName;
NeededLibs = Stub.NeededLibs;
Symbols = Stub.Symbols;
+ VersionDefinitions = Stub.VersionDefinitions;
+ VersionRequirements = Stub.VersionRequirements;
}
IFSStubTriple::IFSStubTriple(IFSStub const &Stub) {
@@ -43,6 +49,8 @@ IFSStubTriple::IFSStubTriple(IFSStub const &Stub) {
SoName = Stub.SoName;
NeededLibs = Stub.NeededLibs;
Symbols = Stub.Symbols;
+ VersionDefinitions = Stub.VersionDefinitions;
+ VersionRequirements = Stub.VersionRequirements;
}
IFSStubTriple::IFSStubTriple(IFSStubTriple &&Stub) {
@@ -51,6 +59,8 @@ IFSStubTriple::IFSStubTriple(IFSStubTriple &&Stub) {
SoName = std::move(Stub.SoName);
NeededLibs = std::move(Stub.NeededLibs);
Symbols = std::move(Stub.Symbols);
+ VersionDefinitions = std::move(Stub.VersionDefinitions);
+ VersionRequirements = std::move(Stub.VersionRequirements);
}
bool IFSTarget::empty() {
diff --git a/llvm/tools/llvm-ifs/llvm-ifs.cpp b/llvm/tools/llvm-ifs/llvm-ifs.cpp
index 1244bfb788ae5..1b90e5c11255e 100644
--- a/llvm/tools/llvm-ifs/llvm-ifs.cpp
+++ b/llvm/tools/llvm-ifs/llvm-ifs.cpp
@@ -392,7 +392,7 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
// Attempt to merge input.
IFSStub Stub;
- std::map<std::string, IFSSymbol> SymbolMap;
+ std::map<std::tuple<std::string, std::string>, IFSSymbol> SymbolMap;
std::string PreviousInputFilePath;
for (const std::string &InputFilePath : Config.InputFilePaths) {
Expected<std::unique_ptr<IFSStub>> StubOrErr =
@@ -406,6 +406,8 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
Stub.Target = TargetStub->Target;
Stub.SoName = TargetStub->SoName;
Stub.NeededLibs = TargetStub->NeededLibs;
+ Stub.VersionDefinitions = TargetStub->VersionDefinitions;
+ Stub.VersionRequirements = TargetStub->VersionRequirements;
} else {
if (Stub.IfsVersion != TargetStub->IfsVersion) {
if (Stub.IfsVersion.getMajor() != IfsVersionCurrent.getMajor()) {
@@ -439,10 +441,23 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
<< InputFilePath << "\n";
return -1;
}
+ if (Stub.VersionDefinitions != TargetStub->VersionDefinitions) {
+ WithColor::error() << "Interface Stub: VersionDefinitions Mismatch."
+ << "\nFilenames: " << PreviousInputFilePath << " "
+ << InputFilePath << "\n";
+ return -1;
+ }
+ if (Stub.VersionRequirements != TargetStub->VersionRequirements) {
+ WithColor::error() << "Interface Stub: VersionRequirements Mismatch."
+ << "\nFilenames: " << PreviousInputFilePath << " "
+ << InputFilePath << "\n";
+ return -1;
+ }
}
for (auto Symbol : TargetStub->Symbols) {
- auto [SI, Inserted] = SymbolMap.try_emplace(Symbol.Name, Symbol);
+ auto [SI, Inserted] =
+ SymbolMap.try_emplace({Symbol.Name, Symbol.Version}, Symbol);
if (Inserted)
continue;
>From 27dbf68cb908e7c9c43be83f24c12bb1e778a88d Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Sun, 12 Oct 2025 03:30:36 +0000
Subject: [PATCH 2/7] Fix IFS tests
---
.../llvm-ifs/binary-read-syms-gnu-hash.test | 7 +++++--
.../llvm-ifs/binary-read-syms-sysv-hash.test | 7 +++++--
.../tools/llvm-ifs/strip-undefined-symbols.test | 3 +++
llvm/unittests/InterfaceStub/ELFYAMLTest.cpp | 16 ++++++++++++----
4 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/llvm/test/tools/llvm-ifs/binary-read-syms-gnu-hash.test b/llvm/test/tools/llvm-ifs/binary-read-syms-gnu-hash.test
index 4fa86dcb0418c..4d98e796af9f1 100644
--- a/llvm/test/tools/llvm-ifs/binary-read-syms-gnu-hash.test
+++ b/llvm/test/tools/llvm-ifs/binary-read-syms-gnu-hash.test
@@ -14,9 +14,12 @@
# CHECK-NEXT: - { Name: _ITM_deregisterTMCloneTable, Type: NoType, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: _ITM_registerTMCloneTable, Type: NoType, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: _Z11rotateArrayPii, Type: Func }
-# CHECK-NEXT: - { Name: __cxa_finalize, Type: Func, Undefined: true, Weak: true }
+# CHECK-NEXT: - { Name: __cxa_finalize, Version: GLIBC_2.2.5, Type: Func, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: __gmon_start__, Type: NoType, Undefined: true, Weak: true }
-# CHECK-NEXT: - { Name: __tls_get_addr, Type: Func, Undefined: true }
+# CHECK-NEXT: - { Name: __tls_get_addr, Version: GLIBC_2.3, Type: Func, Undefined: true }
# CHECK-NEXT: - { Name: _fini, Type: Func }
# CHECK-NEXT: - { Name: _init, Type: Func }
+# CHECK-NEXT: VersionRequirements:
+# CHECK-NEXT: - { File: libc.so.6, Names: [ GLIBC_2.2.5 ] }
+# CHECK-NEXT: - { File: ld-linux-x86-64.so.2, Names: [ GLIBC_2.3 ] }
# CHECK-NEXT: ...
diff --git a/llvm/test/tools/llvm-ifs/binary-read-syms-sysv-hash.test b/llvm/test/tools/llvm-ifs/binary-read-syms-sysv-hash.test
index 94f1f60a1a751..1763595309f6e 100644
--- a/llvm/test/tools/llvm-ifs/binary-read-syms-sysv-hash.test
+++ b/llvm/test/tools/llvm-ifs/binary-read-syms-sysv-hash.test
@@ -14,9 +14,12 @@
# CHECK-NEXT: - { Name: _ITM_deregisterTMCloneTable, Type: NoType, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: _ITM_registerTMCloneTable, Type: NoType, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: _Z11rotateArrayPii, Type: Func }
-# CHECK-NEXT: - { Name: __cxa_finalize, Type: Func, Undefined: true, Weak: true }
+# CHECK-NEXT: - { Name: __cxa_finalize, Version: GLIBC_2.2.5, Type: Func, Undefined: true, Weak: true }
# CHECK-NEXT: - { Name: __gmon_start__, Type: NoType, Undefined: true, Weak: true }
-# CHECK-NEXT: - { Name: __tls_get_addr, Type: Func, Undefined: true }
+# CHECK-NEXT: - { Name: __tls_get_addr, Version: GLIBC_2.3, Type: Func, Undefined: true }
# CHECK-NEXT: - { Name: _fini, Type: Func }
# CHECK-NEXT: - { Name: _init, Type: Func }
+# CHECK-NEXT: VersionRequirements:
+# CHECK-NEXT: - { File: libc.so.6, Names: [ GLIBC_2.2.5 ] }
+# CHECK-NEXT: - { File: ld-linux-x86-64.so.2, Names: [ GLIBC_2.3 ] }
# CHECK-NEXT: ...
diff --git a/llvm/test/tools/llvm-ifs/strip-undefined-symbols.test b/llvm/test/tools/llvm-ifs/strip-undefined-symbols.test
index 010a1b91b9fce..0e138bd12f0b3 100644
--- a/llvm/test/tools/llvm-ifs/strip-undefined-symbols.test
+++ b/llvm/test/tools/llvm-ifs/strip-undefined-symbols.test
@@ -14,4 +14,7 @@
# CHECK-NEXT: - { Name: _Z11rotateArrayPii, Type: Func }
# CHECK-NEXT: - { Name: _fini, Type: Func }
# CHECK-NEXT: - { Name: _init, Type: Func }
+# CHECK-NEXT: VersionRequirements:
+# CHECK-NEXT: - { File: libc.so.6, Names: [ GLIBC_2.2.5 ] }
+# CHECK-NEXT: - { File: ld-linux-x86-64.so.2, Names: [ GLIBC_2.3 ] }
# CHECK-NEXT: ...
diff --git a/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp b/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp
index 39aec132db861..f89abf6e6a617 100644
--- a/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp
+++ b/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp
@@ -192,6 +192,7 @@ TEST(ElfYamlTextAPI, YAMLWritesTBESymbols) {
" - { Name: foo, Type: NoType, Size: 99, Warning: Does nothing }\n"
" - { Name: nor, Type: Func, Undefined: true }\n"
" - { Name: not, Type: Unknown, Size: 12345678901234 }\n"
+ " - { Name: ver, Version: VER, Type: Func }\n"
"...\n";
IFSStub Stub;
Stub.IfsVersion = VersionTuple(1, 0);
@@ -200,36 +201,43 @@ TEST(ElfYamlTextAPI, YAMLWritesTBESymbols) {
Stub.Target.Endianness = IFSEndiannessType::Little;
Stub.Target.ObjectFormat = "ELF";
- IFSSymbol SymBar("bar");
+ IFSSymbol SymBar("bar", "");
SymBar.Size = 128u;
SymBar.Type = IFSSymbolType::Func;
SymBar.Undefined = false;
SymBar.Weak = true;
- IFSSymbol SymFoo("foo");
+ IFSSymbol SymFoo("foo", "");
SymFoo.Size = 99u;
SymFoo.Type = IFSSymbolType::NoType;
SymFoo.Undefined = false;
SymFoo.Weak = false;
SymFoo.Warning = "Does nothing";
- IFSSymbol SymNor("nor");
+ IFSSymbol SymNor("nor", "");
SymNor.Size = 1234u;
SymNor.Type = IFSSymbolType::Func;
SymNor.Undefined = true;
SymNor.Weak = false;
- IFSSymbol SymNot("not");
+ IFSSymbol SymNot("not", "");
SymNot.Size = 12345678901234u;
SymNot.Type = IFSSymbolType::Unknown;
SymNot.Undefined = false;
SymNot.Weak = false;
+ IFSSymbol SymVer("ver", "VER");
+ SymVer.Size = 128u;
+ SymVer.Type = IFSSymbolType::Func;
+ SymVer.Undefined = false;
+ SymVer.Weak = false;
+
// Symbol order is preserved instead of being sorted.
Stub.Symbols.push_back(SymBar);
Stub.Symbols.push_back(SymFoo);
Stub.Symbols.push_back(SymNor);
Stub.Symbols.push_back(SymNot);
+ Stub.Symbols.push_back(SymVer);
// Ensure move constructor works as expected.
IFSStub Moved = std::move(Stub);
>From c31c95a3902c98184db071d8f7b27e3125878f94 Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Wed, 7 Jan 2026 04:11:36 +0000
Subject: [PATCH 3/7] Compare via std::tie
---
llvm/include/llvm/InterfaceStub/IFSStub.h | 39 +++++++++++------------
1 file changed, 18 insertions(+), 21 deletions(-)
diff --git a/llvm/include/llvm/InterfaceStub/IFSStub.h b/llvm/include/llvm/InterfaceStub/IFSStub.h
index 7321808dc1c04..87b8c13a4fcab 100644
--- a/llvm/include/llvm/InterfaceStub/IFSStub.h
+++ b/llvm/include/llvm/InterfaceStub/IFSStub.h
@@ -61,7 +61,9 @@ struct IFSSymbol {
bool Undefined = false;
bool Weak = false;
std::optional<std::string> Warning;
- bool operator<(const IFSSymbol &RHS) const { return Name < RHS.Name; }
+ bool operator<(const IFSSymbol &RHS) const {
+ return std::tie(Name, Version) < std::tie(RHS.Name, RHS.Version);
+ }
};
struct IFSVerDef {
@@ -69,14 +71,12 @@ struct IFSVerDef {
std::vector<std::string> Parents;
};
-inline bool operator==(const IFSVerDef &Lhs, const IFSVerDef &Rhs) {
- if (Lhs.Name != Rhs.Name || Lhs.Parents != Rhs.Parents)
- return false;
- return true;
+inline bool operator==(const IFSVerDef &LHS, const IFSVerDef &RHS) {
+ return std::tie(LHS.Name, LHS.Parents) == std::tie(RHS.Name, RHS.Parents);
}
-inline bool operator!=(const IFSVerDef &Lhs, const IFSVerDef &Rhs) {
- return !(Lhs == Rhs);
+inline bool operator!=(const IFSVerDef &LHS, const IFSVerDef &RHS) {
+ return !(LHS == RHS);
}
struct IFSVerNeed {
@@ -84,14 +84,12 @@ struct IFSVerNeed {
std::vector<std::string> Names;
};
-inline bool operator==(const IFSVerNeed &Lhs, const IFSVerNeed &Rhs) {
- if (Lhs.File != Rhs.File || Lhs.Names != Rhs.Names)
- return false;
- return true;
+inline bool operator==(const IFSVerNeed &LHS, const IFSVerNeed &RHS) {
+ return std::tie(LHS.File, LHS.Names) == std::tie(RHS.File, RHS.Names);
}
-inline bool operator!=(const IFSVerNeed &Lhs, const IFSVerNeed &Rhs) {
- return !(Lhs == Rhs);
+inline bool operator!=(const IFSVerNeed &LHS, const IFSVerNeed &RHS) {
+ return !(LHS == RHS);
}
struct IFSTarget {
@@ -105,16 +103,15 @@ struct IFSTarget {
LLVM_ABI bool empty();
};
-inline bool operator==(const IFSTarget &Lhs, const IFSTarget &Rhs) {
- if (Lhs.Arch != Rhs.Arch || Lhs.BitWidth != Rhs.BitWidth ||
- Lhs.Endianness != Rhs.Endianness ||
- Lhs.ObjectFormat != Rhs.ObjectFormat || Lhs.Triple != Rhs.Triple)
- return false;
- return true;
+inline bool operator==(const IFSTarget &LHS, const IFSTarget &RHS) {
+ return std::tie(LHS.Arch, LHS.BitWidth, LHS.Endianness, LHS.ObjectFormat,
+ LHS.Triple) == std::tie(RHS.Arch, RHS.BitWidth,
+ RHS.Endianness, RHS.ObjectFormat,
+ RHS.Triple);
}
-inline bool operator!=(const IFSTarget &Lhs, const IFSTarget &Rhs) {
- return !(Lhs == Rhs);
+inline bool operator!=(const IFSTarget &LHS, const IFSTarget &RHS) {
+ return !(LHS == RHS);
}
// A cumulative representation of InterFace stubs.
>From d7969a0db49bc18885d647a9b338977c1095cd89 Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Wed, 7 Jan 2026 04:14:13 +0000
Subject: [PATCH 4/7] Modernize push_back via copy
---
llvm/lib/InterfaceStub/IFSHandler.cpp | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/InterfaceStub/IFSHandler.cpp b/llvm/lib/InterfaceStub/IFSHandler.cpp
index fa4b5506be4c3..baf01693096a0 100644
--- a/llvm/lib/InterfaceStub/IFSHandler.cpp
+++ b/llvm/lib/InterfaceStub/IFSHandler.cpp
@@ -158,9 +158,7 @@ template <> struct MappingTraits<IFSVerDef> {
IO.mapRequired("Name", VerDef.Name);
if (IO.outputting()) {
std::vector<FlowString> Parents;
- for (const std::string &Parent : VerDef.Parents) {
- Parents.push_back(Parent);
- }
+ copy(VerDef.Parents, std::back_inserter(Parents));
IO.mapOptional("Parents", Parents);
} else {
IO.mapOptional("Parents", VerDef.Parents);
@@ -177,9 +175,7 @@ template <> struct MappingTraits<IFSVerNeed> {
IO.mapRequired("File", VerNeed.File);
if (IO.outputting()) {
std::vector<FlowString> Names;
- for (const std::string &Parent : VerNeed.Names) {
- Names.push_back(Parent);
- }
+ copy(VerNeed.Names, std::back_inserter(Names));
IO.mapOptional("Names", Names);
} else {
IO.mapOptional("Names", VerNeed.Names);
>From fdcf05e0806d1200e00188c027374100d44f65f9 Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Wed, 7 Jan 2026 04:16:11 +0000
Subject: [PATCH 5/7] Modernize push_back via transform
---
llvm/lib/InterfaceStub/ELFObjHandler.cpp | 33 ++++++++++++------------
1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
index f35fb4a4346a9..b961b0d04682b 100644
--- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp
+++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
@@ -1004,13 +1004,13 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
if (!VerDefOrError)
return appendToError(VerDefOrError.takeError(),
"when reading symbol version definitions");
- for (const VerDef &VerDef : *VerDefOrError) {
- std::vector<std::string> Parents;
- for (const VerdAux &VerDAux : VerDef.AuxV) {
- Parents.push_back(VerDAux.Name);
- }
- DestStub->VersionDefinitions.push_back({VerDef.Name, std::move(Parents)});
- }
+ transform(*VerDefOrError, std::back_inserter(DestStub->VersionDefinitions),
+ [](const VerDef &VerDef) {
+ std::vector<std::string> Parents;
+ transform(VerDef.AuxV, std::back_inserter(Parents),
+ [](const VerdAux &VerDAux) { return VerDAux.Name; });
+ return IFSVerDef{VerDef.Name, std::move(Parents)};
+ });
}
const Elf_Shdr *VerNeedPtr = EDynSym->getVerNeed();
@@ -1020,15 +1020,16 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
if (!VerNeedOrError)
return appendToError(VerNeedOrError.takeError(),
"when reading symbol version needs");
- for (const VerNeed &VerNeed : *VerNeedOrError) {
- std::string File = VerNeed.File;
- IFSVerNeed IFSVerNeed;
- IFSVerNeed.File = VerNeed.File;
- for (const VernAux &VerNAux : VerNeed.AuxV) {
- IFSVerNeed.Names.push_back(VerNAux.Name.c_str());
- }
- DestStub->VersionRequirements.push_back(IFSVerNeed);
- }
+ transform(*VerNeedOrError,
+ std::back_inserter(DestStub->VersionRequirements),
+ [](const VerNeed &VerNeed) {
+ std::vector<std::string> Names;
+ transform(VerNeed.AuxV, std::back_inserter(Names),
+ [](const VernAux &VerNAux) {
+ return VerNAux.Name.c_str();
+ });
+ return IFSVerNeed{VerNeed.File, std::move(Names)};
+ });
}
// Populate Symbols from .dynsym table and dynamic string table.
>From 0aaf9e88caac9a5fee58d31f5bfa1f3aff24d8ab Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Thu, 8 Jan 2026 06:55:30 +0000
Subject: [PATCH 6/7] Add switch controling whether to emit program headers
---
llvm/docs/CommandGuide/llvm-ifs.rst | 4 +
.../llvm/InterfaceStub/ELFObjHandler.h | 2 +
llvm/lib/InterfaceStub/ELFObjHandler.cpp | 111 ++++++++++++------
llvm/tools/llvm-ifs/Opts.td | 1 +
llvm/tools/llvm-ifs/llvm-ifs.cpp | 13 +-
5 files changed, 90 insertions(+), 41 deletions(-)
diff --git a/llvm/docs/CommandGuide/llvm-ifs.rst b/llvm/docs/CommandGuide/llvm-ifs.rst
index d310e62b14eb6..e285a362e8368 100644
--- a/llvm/docs/CommandGuide/llvm-ifs.rst
+++ b/llvm/docs/CommandGuide/llvm-ifs.rst
@@ -213,6 +213,10 @@ OPTIONS
This flag strips the ``Target`` field from the IFS file so it can be overridden
later.
+.. option:: --output-elf-with-program-headers
+
+ When this flag is set, llvm-ifs will generate ELF file with program headers.
+
.. option:: --write-if-changed
When this flag is set, llvm-ifs will only write the output file if it does not
diff --git a/llvm/include/llvm/InterfaceStub/ELFObjHandler.h b/llvm/include/llvm/InterfaceStub/ELFObjHandler.h
index 0de0b32804801..dcdd821d918cf 100644
--- a/llvm/include/llvm/InterfaceStub/ELFObjHandler.h
+++ b/llvm/include/llvm/InterfaceStub/ELFObjHandler.h
@@ -33,9 +33,11 @@ LLVM_ABI Expected<std::unique_ptr<IFSStub>> readELFFile(MemoryBufferRef Buf);
///
/// @param FilePath File path for writing the ELF binary.
/// @param Stub Source ELFStub to generate a binary ELF stub from.
+/// @param EmitProgramHeaders Whether or not to emit ELF program headers.
/// @param WriteIfChanged Whether or not to preserve timestamp if
/// the output stays the same.
LLVM_ABI Error writeBinaryStub(StringRef FilePath, const IFSStub &Stub,
+ bool EmitProgramHeaders = false,
bool WriteIfChanged = false);
} // end namespace ifs
diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
index b961b0d04682b..7cc2133beb18d 100644
--- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp
+++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
@@ -327,7 +327,8 @@ template <class ELFT> class ELFStubBuilder {
ELFStubBuilder(const ELFStubBuilder &) = delete;
ELFStubBuilder(ELFStubBuilder &&) = default;
- explicit ELFStubBuilder(const IFSStub &Stub) {
+ explicit ELFStubBuilder(const IFSStub &Stub, bool EmitProgramHeaders)
+ : EmitProgramHeaders(EmitProgramHeaders) {
DynSym.Name = ".dynsym";
DynSym.Align = sizeof(Elf_Addr);
DynStr.Name = ".dynstr";
@@ -456,7 +457,9 @@ template <class ELFT> class ELFStubBuilder {
VerNeedIndex = DynTab.Content.addAddr(DT_VERNEED, 0);
DynTab.Size = DynTab.Content.getSize();
// Calculate sections' addresses and offsets.
- uint64_t CurrentOffset = sizeof(Elf_Ehdr) + ElfPhnum * sizeof(Elf_Phdr);
+ uint64_t CurrentOffset = sizeof(Elf_Ehdr);
+ if (EmitProgramHeaders)
+ CurrentOffset += ElfPhnum * sizeof(Elf_Phdr);
for (OutputSection<ELFT> *Sec : Sections) {
Sec->Offset = alignTo(CurrentOffset, Sec->Align);
Sec->Addr = Sec->Offset;
@@ -487,28 +490,31 @@ template <class ELFT> class ELFStubBuilder {
initELFHeader<ELFT>(ElfHeader, static_cast<uint16_t>(*Stub.Target.Arch));
ElfHeader.e_shstrndx = ShStrTab.Index;
ElfHeader.e_shnum = LastSection->Index + 1;
- ElfHeader.e_phnum = ElfPhnum;
- ElfHeader.e_phoff = sizeof(Elf_Ehdr);
ElfHeader.e_shoff =
alignTo(LastSection->Offset + LastSection->Size, sizeof(Elf_Addr));
- ElfProgramHeaders[0].p_type = PT_LOAD;
- ElfProgramHeaders[0].p_offset = 0;
- ElfProgramHeaders[0].p_vaddr = 0;
- ElfProgramHeaders[0].p_paddr = 0;
- ElfProgramHeaders[0].p_filesz = DynTab.Shdr.sh_offset;
- ElfProgramHeaders[0].p_memsz = DynTab.Shdr.sh_offset;
- ElfProgramHeaders[0].p_flags = PF_R;
- ElfProgramHeaders[0].p_align = 0x1000;
-
- ElfProgramHeaders[1].p_type = PT_DYNAMIC;
- ElfProgramHeaders[1].p_offset = DynTab.Shdr.sh_offset;
- ElfProgramHeaders[1].p_vaddr = DynTab.Shdr.sh_offset;
- ElfProgramHeaders[1].p_paddr = DynTab.Shdr.sh_offset;
- ElfProgramHeaders[1].p_filesz = DynTab.Shdr.sh_size;
- ElfProgramHeaders[1].p_memsz = DynTab.Shdr.sh_size;
- ElfProgramHeaders[1].p_flags = PF_R | PF_W;
- ElfProgramHeaders[1].p_align = DynTab.Shdr.sh_addralign;
+ if (EmitProgramHeaders) {
+ ElfHeader.e_phnum = ElfPhnum;
+ ElfHeader.e_phoff = sizeof(Elf_Ehdr);
+
+ ElfProgramHeaders[0].p_type = PT_LOAD;
+ ElfProgramHeaders[0].p_offset = 0;
+ ElfProgramHeaders[0].p_vaddr = 0;
+ ElfProgramHeaders[0].p_paddr = 0;
+ ElfProgramHeaders[0].p_filesz = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[0].p_memsz = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[0].p_flags = PF_R;
+ ElfProgramHeaders[0].p_align = 0x1000;
+
+ ElfProgramHeaders[1].p_type = PT_DYNAMIC;
+ ElfProgramHeaders[1].p_offset = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_vaddr = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_paddr = DynTab.Shdr.sh_offset;
+ ElfProgramHeaders[1].p_filesz = DynTab.Shdr.sh_size;
+ ElfProgramHeaders[1].p_memsz = DynTab.Shdr.sh_size;
+ ElfProgramHeaders[1].p_flags = PF_R | PF_W;
+ ElfProgramHeaders[1].p_align = DynTab.Shdr.sh_addralign;
+ }
}
size_t getSize() const {
@@ -517,9 +523,10 @@ template <class ELFT> class ELFStubBuilder {
void write(uint8_t *Data) const {
write(Data, ElfHeader);
- for (size_t I = 0; I < ElfPhnum; I++)
- write(Data + ElfHeader.e_phoff + I * sizeof(Elf_Phdr),
- ElfProgramHeaders[I]);
+ if (EmitProgramHeaders)
+ for (size_t I = 0; I < ElfPhnum; I++)
+ write(Data + ElfHeader.e_phoff + I * sizeof(Elf_Phdr),
+ ElfProgramHeaders[I]);
DynSym.Content.write(Data + DynSym.Shdr.sh_offset);
DynStr.Content.write(Data + DynStr.Shdr.sh_offset);
DynTab.Content.write(Data + DynTab.Shdr.sh_offset);
@@ -543,8 +550,8 @@ template <class ELFT> class ELFStubBuilder {
}
private:
- Elf_Ehdr ElfHeader;
static constexpr auto ElfPhnum = 2;
+ Elf_Ehdr ElfHeader;
Elf_Phdr ElfProgramHeaders[ElfPhnum];
ContentSection<ELFStringTableBuilder, ELFT> DynStr;
ContentSection<ELFStringTableBuilder, ELFT> ShStrTab;
@@ -556,6 +563,7 @@ template <class ELFT> class ELFStubBuilder {
bool WriteVerSym = false;
bool WriteVerDef = false;
bool WriteVerNeed = false;
+ bool EmitProgramHeaders = false;
template <class T> static void write(uint8_t *Data, const T &Value) {
*reinterpret_cast<T *>(Data) = Value;
@@ -1065,10 +1073,14 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
///
/// @param FilePath File path for writing the ELF binary.
/// @param Stub Source InterFace Stub to generate a binary ELF stub from.
+/// @param EmitProgramHeaders Whether or not to emit ELF program headers.
+/// @param WriteIfChanged Whether or not to preserve timestamp if
+/// the output stays the same.
template <class ELFT>
static Error writeELFBinaryToFile(StringRef FilePath, const IFSStub &Stub,
+ bool EmitProgramHeaders,
bool WriteIfChanged) {
- ELFStubBuilder<ELFT> Builder{Stub};
+ ELFStubBuilder<ELFT> Builder{Stub, EmitProgramHeaders};
// Write Stub to memory first.
std::vector<uint8_t> Buf(Builder.getSize());
Builder.write(Buf.data());
@@ -1125,22 +1137,47 @@ Expected<std::unique_ptr<IFSStub>> readELFFile(MemoryBufferRef Buf) {
// This function wraps the ELFT writeELFBinaryToFile() so writeBinaryStub()
// can be called without having to use ELFType templates directly.
Error writeBinaryStub(StringRef FilePath, const IFSStub &Stub,
- bool WriteIfChanged) {
+ bool EmitProgramHeaders, bool WriteIfChanged) {
assert(Stub.Target.Arch);
assert(Stub.Target.BitWidth);
assert(Stub.Target.Endianness);
- if (Stub.Target.BitWidth == IFSBitWidthType::IFS32) {
- if (Stub.Target.Endianness == IFSEndiannessType::Little) {
- return writeELFBinaryToFile<ELF32LE>(FilePath, Stub, WriteIfChanged);
- } else {
- return writeELFBinaryToFile<ELF32BE>(FilePath, Stub, WriteIfChanged);
+ Error (*WriteELF)(StringRef, const IFSStub &, bool, bool) = nullptr;
+ switch (*Stub.Target.BitWidth) {
+ case IFSBitWidthType::IFS32: {
+ switch (*Stub.Target.Endianness) {
+ case IFSEndiannessType::Little: {
+ WriteELF = writeELFBinaryToFile<ELF32LE>;
+ break;
}
- } else {
- if (Stub.Target.Endianness == IFSEndiannessType::Little) {
- return writeELFBinaryToFile<ELF64LE>(FilePath, Stub, WriteIfChanged);
- } else {
- return writeELFBinaryToFile<ELF64BE>(FilePath, Stub, WriteIfChanged);
+ case IFSEndiannessType::Big: {
+ WriteELF = writeELFBinaryToFile<ELF32BE>;
+ break;
+ }
+ case IFSEndiannessType::Unknown:
+ break;
}
+ break;
+ }
+ case IFSBitWidthType::IFS64: {
+ switch (*Stub.Target.Endianness) {
+ case IFSEndiannessType::Little: {
+ WriteELF = writeELFBinaryToFile<ELF64LE>;
+ break;
+ }
+ case IFSEndiannessType::Big: {
+ WriteELF = writeELFBinaryToFile<ELF64BE>;
+ break;
+ }
+ case IFSEndiannessType::Unknown:
+ break;
+ }
+ break;
+ }
+ case IFSBitWidthType::Unknown:
+ break;
+ }
+ if (WriteELF) {
+ return WriteELF(FilePath, Stub, EmitProgramHeaders, WriteIfChanged);
}
llvm_unreachable("invalid binary output target");
}
diff --git a/llvm/tools/llvm-ifs/Opts.td b/llvm/tools/llvm-ifs/Opts.td
index 7288cf0123a72..99a5858a0a959 100644
--- a/llvm/tools/llvm-ifs/Opts.td
+++ b/llvm/tools/llvm-ifs/Opts.td
@@ -20,6 +20,7 @@ defm input_format : Eq<"input-format", "Specify the input file format">;
defm output : Eq<"output", "Output file **DEPRECATED**">;
def : Separate<["-"], "o">, HelpText<"Alias for --output">, Alias<output_EQ>;
defm output_elf : Eq<"output-elf", "Output path for ELF file">;
+def output_elf_with_program_headers : FF<"output-elf-with-program-headers", "Output ELF file with program headers">;
defm output_format : Eq<"output-format", "Specify the output file format **DEPRECATED**">;
defm output_ifs : Eq<"output-ifs", "Output path for IFS file">;
defm output_tbd : Eq<"output-tbd", "Output path for TBD file">;
diff --git a/llvm/tools/llvm-ifs/llvm-ifs.cpp b/llvm/tools/llvm-ifs/llvm-ifs.cpp
index 1b90e5c11255e..545b5c93f9a29 100644
--- a/llvm/tools/llvm-ifs/llvm-ifs.cpp
+++ b/llvm/tools/llvm-ifs/llvm-ifs.cpp
@@ -109,6 +109,7 @@ struct DriverConfig {
std::optional<std::string> OutputIfs;
std::optional<std::string> OutputTbd;
+ bool OutputElfWithProgramHeaders = false;
bool WriteIfChanged = false;
};
@@ -376,6 +377,8 @@ static DriverConfig parseArgs(int argc, char *const *argv) {
Config.OutputIfs = A->getValue();
if (const opt::Arg *A = Args.getLastArg(OPT_output_tbd_EQ))
Config.OutputTbd = A->getValue();
+ Config.OutputElfWithProgramHeaders =
+ Args.hasArg(OPT_output_elf_with_program_headers);
Config.WriteIfChanged = Args.hasArg(OPT_write_if_changed);
return Config;
}
@@ -583,8 +586,9 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
Error TargetError = validateIFSTarget(Stub, true);
if (TargetError)
fatalError(std::move(TargetError));
- Error BinaryWriteError =
- writeBinaryStub(*Config.Output, Stub, Config.WriteIfChanged);
+ Error BinaryWriteError = writeBinaryStub(
+ *Config.Output, Stub, Config.OutputElfWithProgramHeaders,
+ Config.WriteIfChanged);
if (BinaryWriteError)
fatalError(std::move(BinaryWriteError));
break;
@@ -596,8 +600,9 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
Error TargetError = validateIFSTarget(Stub, true);
if (TargetError)
fatalError(std::move(TargetError));
- Error BinaryWriteError =
- writeBinaryStub(*Config.OutputElf, Stub, Config.WriteIfChanged);
+ Error BinaryWriteError = writeBinaryStub(
+ *Config.OutputElf, Stub, Config.OutputElfWithProgramHeaders,
+ Config.WriteIfChanged);
if (BinaryWriteError)
fatalError(std::move(BinaryWriteError));
}
>From 89a0cfa7a026baa77a9a7e2f5d665f573b6dae59 Mon Sep 17 00:00:00 2001
From: ur4t <ur4t at protonmail.com>
Date: Thu, 8 Jan 2026 07:50:40 +0000
Subject: [PATCH 7/7] Add IFS tests
For versioned symbols and program headers
---
.../output-elf-with-program-headers.ifs | 15 ++++++++
.../tools/llvm-ifs/version-symbol-func.ifs | 35 +++++++++++++++++
.../tools/llvm-ifs/version-symbol-need.ifs | 38 +++++++++++++++++++
.../tools/llvm-ifs/version-symbol-object.ifs | 32 ++++++++++++++++
4 files changed, 120 insertions(+)
create mode 100644 llvm/test/tools/llvm-ifs/output-elf-with-program-headers.ifs
create mode 100644 llvm/test/tools/llvm-ifs/version-symbol-func.ifs
create mode 100644 llvm/test/tools/llvm-ifs/version-symbol-need.ifs
create mode 100644 llvm/test/tools/llvm-ifs/version-symbol-object.ifs
diff --git a/llvm/test/tools/llvm-ifs/output-elf-with-program-headers.ifs b/llvm/test/tools/llvm-ifs/output-elf-with-program-headers.ifs
new file mode 100644
index 0000000000000..a0f11d3f0e87c
--- /dev/null
+++ b/llvm/test/tools/llvm-ifs/output-elf-with-program-headers.ifs
@@ -0,0 +1,15 @@
+# RUN: llvm-ifs --input-format=IFS --output-elf - --output-elf-with-program-headers %s | \
+# RUN: llvm-readelf --all - | FileCheck %s --check-prefixes=CHECK-ELF
+
+# CHECK-ELF: Program Headers:
+# CHECK-ELF: Section to Segment mapping:
+# CHECK-ELF: Segment Sections...
+# CHECK-ELF: 00 .dynsym .dynstr
+# CHECK-ELF: 01 .dynamic
+# CHECK-ELF: None .shstrtab
+
+--- !ifs-v1
+IfsVersion: 3.0
+Target: x86_64-unknown-linux-gnu
+Symbols:
+...
diff --git a/llvm/test/tools/llvm-ifs/version-symbol-func.ifs b/llvm/test/tools/llvm-ifs/version-symbol-func.ifs
new file mode 100644
index 0000000000000..ed74e64a2e3e8
--- /dev/null
+++ b/llvm/test/tools/llvm-ifs/version-symbol-func.ifs
@@ -0,0 +1,35 @@
+# RUN: llvm-ifs --input-format=IFS --output-ifs - %s | \
+# RUN: FileCheck %s --check-prefixes=CHECK-IFS
+
+# RUN: llvm-ifs --input-format=IFS --output-elf - %s | \
+# RUN: llvm-readelf --all - | FileCheck %s --check-prefixes=CHECK-ELF
+
+# CHECK-IFS: --- !ifs-v1
+# CHECK-IFS-NEXT: IfsVersion: 3.0
+# CHECK-IFS-NEXT: SoName: libfoo.so
+# CHECK-IFS-NEXT: Target: x86_64-unknown-linux-gnu
+# CHECK-IFS-NEXT: Symbols:
+# CHECK-IFS-NEXT: - { Name: VER, Version: VER, Type: Object, Size: 0 }
+# CHECK-IFS-NEXT: - { Name: func, Version: VER, Type: Func }
+# CHECK-IFS-NEXT: VersionDefinitions:
+# CHECK-IFS-NEXT: - { Name: libfoo.so }
+# CHECK-IFS-NEXT: - { Name: VER }
+# CHECK-IFS-NEXT: ...
+
+# CHECK-ELF: OBJECT GLOBAL DEFAULT 1 VER@@VER
+# CHECK-ELF: FUNC GLOBAL DEFAULT 1 func@@VER
+
+# CHECK-ELF: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: libfoo.so
+# CHECK-ELF: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: VER
+
+--- !ifs-v1
+IfsVersion: 3.0
+SoName: libfoo.so
+Target: x86_64-unknown-linux-gnu
+Symbols:
+ - { Name: VER, Version: VER, Type: Object, Size: 0 }
+ - { Name: func, Version: VER, Type: Func }
+VersionDefinitions:
+ - { Name: libfoo.so }
+ - { Name: VER }
+...
diff --git a/llvm/test/tools/llvm-ifs/version-symbol-need.ifs b/llvm/test/tools/llvm-ifs/version-symbol-need.ifs
new file mode 100644
index 0000000000000..881e6d1f1ca12
--- /dev/null
+++ b/llvm/test/tools/llvm-ifs/version-symbol-need.ifs
@@ -0,0 +1,38 @@
+# RUN: llvm-ifs --input-format=IFS --output-ifs - %s | \
+# RUN: FileCheck %s --check-prefixes=CHECK-IFS
+
+# RUN: llvm-ifs --input-format=IFS --output-elf - %s | \
+# RUN: llvm-readelf --all - | FileCheck %s --check-prefixes=CHECK-ELF
+
+# CHECK-IFS: --- !ifs-v1
+# CHECK-IFS-NEXT: IfsVersion: 3.0
+# CHECK-IFS-NEXT: SoName: libfoo.so
+# CHECK-IFS-NEXT: Target: x86_64-unknown-linux-gnu
+# CHECK-IFS-NEXT: NeededLibs:
+# CHECK-IFS-NEXT: - libbar.so
+# CHECK-IFS-NEXT: Symbols:
+# CHECK-IFS-NEXT: - { Name: func, Version: VER, Type: Func }
+# CHECK-IFS-NEXT: VersionDefinitions:
+# CHECK-IFS-NEXT: - { Name: libfoo.so }
+# CHECK-IFS-NEXT: VersionRequirements:
+# CHECK-IFS-NEXT: - { File: libbar.so, Names: [ VER ] }
+# CHECK-IFS-NEXT: ...
+
+# CHECK-ELF: FUNC GLOBAL DEFAULT 1 func at VER
+
+# CHECK-ELF: Version: 1 File: libbar.so Cnt: 1
+# CHECK-ELF: Name: VER Flags: none Version: 2
+
+--- !ifs-v1
+IfsVersion: 3.0
+SoName: libfoo.so
+Target: x86_64-unknown-linux-gnu
+NeededLibs:
+ - libbar.so
+Symbols:
+ - { Name: func, Version: VER, Type: Func }
+VersionDefinitions:
+ - { Name: libfoo.so }
+VersionRequirements:
+ - { File: libbar.so, Names: [ VER ] }
+...
diff --git a/llvm/test/tools/llvm-ifs/version-symbol-object.ifs b/llvm/test/tools/llvm-ifs/version-symbol-object.ifs
new file mode 100644
index 0000000000000..c36c2ba6119a6
--- /dev/null
+++ b/llvm/test/tools/llvm-ifs/version-symbol-object.ifs
@@ -0,0 +1,32 @@
+# RUN: llvm-ifs --input-format=IFS --output-ifs - %s | \
+# RUN: FileCheck %s --check-prefixes=CHECK-IFS
+
+# RUN: llvm-ifs --input-format=IFS --output-elf - %s | \
+# RUN: llvm-readelf --all - | FileCheck %s --check-prefixes=CHECK-ELF
+
+# CHECK-IFS: --- !ifs-v1
+# CHECK-IFS-NEXT: IfsVersion: 3.0
+# CHECK-IFS-NEXT: SoName: libfoo.so
+# CHECK-IFS-NEXT: Target: x86_64-unknown-linux-gnu
+# CHECK-IFS-NEXT: Symbols:
+# CHECK-IFS-NEXT: - { Name: VER, Version: VER, Type: Object, Size: 0 }
+# CHECK-IFS-NEXT: VersionDefinitions:
+# CHECK-IFS-NEXT: - { Name: libfoo.so }
+# CHECK-IFS-NEXT: - { Name: VER }
+# CHECK-IFS-NEXT: ...
+
+# CHECK-ELF: OBJECT GLOBAL DEFAULT 1 VER@@VER
+
+# CHECK-ELF: Rev: 1 Flags: BASE Index: 1 Cnt: 1 Name: libfoo.so
+# CHECK-ELF: Rev: 1 Flags: none Index: 2 Cnt: 1 Name: VER
+
+--- !ifs-v1
+IfsVersion: 3.0
+SoName: libfoo.so
+Target: x86_64-unknown-linux-gnu
+Symbols:
+ - { Name: VER, Version: VER, Type: Object, Size: 0 }
+VersionDefinitions:
+ - { Name: libfoo.so }
+ - { Name: VER }
+...
More information about the llvm-commits
mailing list