[llvm] Add symbol version support to llvm-ifs (PR #163030)

via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 04:58:34 PDT 2026


https://github.com/ur4t updated https://github.com/llvm/llvm-project/pull/163030

>From 658bc2ab6cd7a45eb8832e27082a3a8a84ae80c2 Mon Sep 17 00:00:00 2001
From: ur4t <46435411+ur4t at users.noreply.github.com>
Date: Thu, 25 Jun 2026 03:08:46 +0000
Subject: [PATCH 1/2] Add ELF symbol version to IFS

---
 llvm/docs/CommandGuide/llvm-ifs.md           |   9 +
 llvm/include/llvm/InterfaceStub/IFSStub.h    |  27 +-
 llvm/lib/InterfaceStub/ELFObjHandler.cpp     | 403 +++++++++++++++----
 llvm/lib/InterfaceStub/IFSHandler.cpp        |   2 +
 llvm/test/tools/llvm-ifs/versioned.ifs       |  32 ++
 llvm/tools/llvm-ifs/llvm-ifs.cpp             |   9 +-
 llvm/unittests/InterfaceStub/ELFYAMLTest.cpp |   9 +
 7 files changed, 402 insertions(+), 89 deletions(-)
 create mode 100644 llvm/test/tools/llvm-ifs/versioned.ifs

diff --git a/llvm/docs/CommandGuide/llvm-ifs.md b/llvm/docs/CommandGuide/llvm-ifs.md
index 9e2617d52e1754..6294fbf988551a 100644
--- a/llvm/docs/CommandGuide/llvm-ifs.md
+++ b/llvm/docs/CommandGuide/llvm-ifs.md
@@ -35,6 +35,7 @@ Symbols:
   - { Name: sym2, Type: Func, Weak: false }
   - { Name: sym3, Type: TLS }
   - { Name: sym4, Type: Unknown, Warning: foo }
+  - { Name: sym5, Version: VER_1, Type: Func }
 ...
 ```
 
@@ -52,8 +53,10 @@ Symbols:
 - `Symbols`: A collection of all data needed to link objects for each symbol, sorted by name in ascending order.
 
   - `Name`: Symbol name.
+  - `Version` (optional): Symbol version.
   - `Type`: Whether the symbol is an object, function, no-type, thread local storage, or unknown. Symbol types not explicitly supported are mapped as unknown to improve signal-to-noise ratio.
   - `Size`: The size of the symbol in question, doesn't apply to functions, and is optional for NoType symbols.
+  - `Default`: Whether or not the symbol is a default version symbol in this shared object file.
   - `Undefined`: Whether or not the symbol is defined in this shared object file.
   - `Weak`: Whether or not the symbol should be treated as weak.
   - `Warning` (optional): Warning text to output when this symbol is linked against.
@@ -76,6 +79,10 @@ A minimum ELF file that can be used by linker should have following sections pro
 
 - Dynamic string table (`.dynstr` section).
 
+- Version symbol table (``.gnu.version`` section). (optional)
+
+- Version definition table (``.gnu.version_d`` section). (optional)
+
 - Dynamic table (`.dynamic` section).
 
   - `DT_SYMTAB` entry.
@@ -83,6 +90,8 @@ A minimum ELF file that can be used by linker should have following sections pro
   - `DT_STRSZ` entry.
   - `DT_NEEDED` entries. (optional)
   - `DT_SONAME` entry. (optional)
+  - `DT_VERSYM` entry. (optional)
+  - `DT_VERDEF` entry. (optional)
 
 - Section header string table (`.shstrtab` section)
 
diff --git a/llvm/include/llvm/InterfaceStub/IFSStub.h b/llvm/include/llvm/InterfaceStub/IFSStub.h
index ddde889d468906..840a574191afd7 100644
--- a/llvm/include/llvm/InterfaceStub/IFSStub.h
+++ b/llvm/include/llvm/InterfaceStub/IFSStub.h
@@ -52,14 +52,18 @@ enum class IFSBitWidthType {
 
 struct IFSSymbol {
   IFSSymbol() = default;
-  explicit IFSSymbol(std::string SymbolName) : Name(std::move(SymbolName)) {}
+  explicit IFSSymbol(const std::string &Name) : Name(Name) {}
   std::string Name;
+  std::string Version;
   std::optional<uint64_t> Size;
   IFSSymbolType Type = IFSSymbolType::NoType;
+  bool Default = false;
   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 &Other) const {
+    return std::tie(Name, Version) < std::tie(Other.Name, Other.Version);
+  }
 };
 
 struct IFSTarget {
@@ -70,21 +74,16 @@ struct IFSTarget {
   std::optional<IFSEndiannessType> Endianness;
   std::optional<IFSBitWidthType> BitWidth;
 
+  bool operator==(const IFSTarget &Other) const {
+    return std::tie(Arch, BitWidth, Endianness, ObjectFormat, Triple) ==
+           std::tie(Other.Arch, Other.BitWidth, Other.Endianness,
+                    Other.ObjectFormat, Other.Triple);
+  }
+  bool operator!=(const IFSTarget &Other) const { return !(*this == Other); }
+
   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 !(Lhs == Rhs);
-}
-
 // A cumulative representation of InterFace stubs.
 // Both textual and binary stubs will read into and write from this object.
 struct IFSStub {
diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
index 9c81a8832c0f2a..1affc037dd1ee4 100644
--- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp
+++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
@@ -39,6 +39,10 @@ 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;
 };
 
 /// This initializes an ELF file header with information specific to a binary
@@ -116,12 +120,90 @@ template <class ELFT> class ELFSymbolTableBuilder {
 
   size_t getSize() const { return Symbols.size() * sizeof(Elf_Sym); }
 
+  void write(uint8_t *Buf) const { memcpy(Buf, Symbols.data(), getSize()); }
+
+private:
+  llvm::SmallVector<Elf_Sym, 8> Symbols;
+};
+
+template <class ELFT> class ELFVersionSymbolBuilder {
+public:
+  using Elf_Versym = typename ELFT::Versym;
+
+  ELFVersionSymbolBuilder() { VerSyms.push_back({}); }
+
+  void add(uint16_t Index) {
+    Elf_Versym VerSym;
+    VerSym.vs_index = Index;
+    VerSyms.push_back(VerSym);
+  }
+
+  size_t getSize() const { return VerSyms.size() * sizeof(Elf_Versym); }
+
+  void write(uint8_t *Buf) const { memcpy(Buf, VerSyms.data(), getSize()); }
+
+private:
+  llvm::SmallVector<Elf_Versym, 8> VerSyms;
+};
+
+template <class ELFT> class ELFVersionDefinitionBuilder {
+public:
+  using Elf_Verdef = typename ELFT::Verdef;
+  using Elf_Verdaux = typename ELFT::Verdaux;
+
+  ELFVersionDefinitionBuilder() { VerDAuxes.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);
+    VerDefs.push_back(VerDef);
+    VerDAuxes.push_back({});
+  }
+
+  void addAux(uint16_t Vdndx, uint32_t Name) {
+    Elf_Verdaux VerDAux;
+    VerDAux.vda_name = Name;
+    VerDAux.vda_next = sizeof(Elf_Verdaux);
+    VerDAuxes[Vdndx].push_back(VerDAux);
+  }
+
+  void finalize() {
+    if (!VerDefs.empty())
+      VerDefs.back().vd_next = 0;
+    for (llvm::SmallVector<Elf_Verdaux, 8> &VerDAux : VerDAuxes)
+      if (!VerDAux.empty())
+        VerDAux.back().vda_next = 0;
+  }
+
+  size_t getSize() const {
+    size_t Count = 0;
+    for (const llvm::SmallVector<Elf_Verdaux, 8> &VerDAux : VerDAuxes)
+      Count += VerDAux.size();
+    return Count * sizeof(Elf_Verdaux) + VerDefs.size() * sizeof(Elf_Verdef);
+  }
+
   void write(uint8_t *Buf) const {
-    memcpy(Buf, Symbols.data(), sizeof(Elf_Sym) * Symbols.size());
+    uint8_t *Ptr = Buf;
+    size_t Count = 0;
+    for (const Elf_Verdef &VerDef : VerDefs) {
+      Count = sizeof(Elf_Verdef);
+      memcpy(Ptr, &VerDef, Count);
+      Ptr += Count;
+      Count = VerDef.vd_cnt * sizeof(Elf_Verdaux);
+      memcpy(Ptr, VerDAuxes[VerDef.vd_ndx].data(), Count);
+      Ptr += Count;
+    }
   }
 
 private:
-  llvm::SmallVector<Elf_Sym, 8> Symbols;
+  llvm::SmallVector<Elf_Verdef, 8> VerDefs;
+  llvm::SmallVector<llvm::SmallVector<Elf_Verdaux, 8>, 8> VerDAuxes;
 };
 
 template <class ELFT> class ELFDynamicTableBuilder {
@@ -177,9 +259,10 @@ template <class ELFT> class ELFStubBuilder {
   using Elf_Dyn = typename ELFT::Dyn;
 
   ELFStubBuilder(const ELFStubBuilder &) = delete;
-  ELFStubBuilder(ELFStubBuilder &&) = default;
+  ELFStubBuilder(ELFStubBuilder &&) = delete;
+  ELFStubBuilder() = default;
 
-  explicit ELFStubBuilder(const IFSStub &Stub) {
+  Error populate(const IFSStub &Stub) {
     DynSym.Name = ".dynsym";
     DynSym.Align = sizeof(Elf_Addr);
     DynStr.Name = ".dynstr";
@@ -188,17 +271,33 @@ 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);
 
     // Populate string tables.
-    for (const IFSSymbol &Sym : Stub.Symbols)
+    for (const IFSSymbol &Sym : Stub.Symbols) {
       DynStr.Content.add(Sym.Name);
+      if (!Sym.Version.empty()) {
+        DynStr.Content.add(Sym.Version);
+        HasVersion = true;
+      }
+    }
     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};
+    std::vector<OutputSection<ELFT> *> Sections;
+    Sections.push_back(&DynSym);
+    Sections.push_back(&DynStr);
+    if (HasVersion) {
+      Sections.push_back(&VerSym);
+      Sections.push_back(&VerDef);
+    }
+    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,15 +323,52 @@ template <class ELFT> class ELFStubBuilder {
     }
     DynSym.Size = DynSym.Content.getSize();
 
+    std::map<std::string, size_t> VerDefMap;
+    size_t Vdndx = 0;
+
+    {
+      // VER_NDX_LOCAL
+      Vdndx++;
+      // VER_NDX_GLOBAL
+      const std::string Name = Stub.SoName.value_or("");
+      VerDef.Content.addDef(Vdndx, 1, hashSysV(Name));
+      VerDef.Content.addAux(Vdndx, DynStr.Content.getOffset(Name));
+      Vdndx++;
+    }
+
+    // Populate dynamic symbol version table and version definition table.
+    for (const IFSSymbol &Sym : Stub.Symbols)
+      if (Sym.Version.empty())
+        VerSym.Content.add(VER_NDX_GLOBAL);
+      else {
+        const auto &[VI, Inserted] = VerDefMap.insert({Sym.Version, Vdndx});
+        const auto &[Name, Ndx] = *VI;
+        if (Inserted) {
+          VerDef.Content.addDef(Vdndx, 1, hashSysV(Name));
+          VerDef.Content.addAux(Vdndx, DynStr.Content.getOffset(Name));
+          Vdndx++;
+        }
+        VerSym.Content.add(Sym.Default ? Ndx : (Ndx | VERSYM_HIDDEN));
+      }
+    VerDef.Content.finalize();
+    VerDef.Size = VerDef.Content.getSize();
+    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;
     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 (HasVersion) {
+      VerSymIndex = DynTab.Content.addAddr(DT_VERSYM, 0);
+      VerDefIndex = DynTab.Content.addAddr(DT_VERDEF, 0);
+    }
     DynTab.Size = DynTab.Content.getSize();
     // Calculate sections' addresses and offsets.
     uint64_t CurrentOffset = sizeof(Elf_Ehdr);
@@ -244,11 +380,19 @@ template <class ELFT> class ELFStubBuilder {
     // Fill Addr back to dynamic table.
     DynTab.Content.modifyAddr(DynSymIndex, DynSym.Addr);
     DynTab.Content.modifyAddr(DynStrIndex, DynStr.Addr);
+    if (HasVersion) {
+      DynTab.Content.modifyAddr(VerSymIndex, VerSym.Addr);
+      DynTab.Content.modifyAddr(VerDefIndex, VerDef.Addr);
+    }
     // Write section headers of string tables.
     fillSymTabShdr(DynSym, SHT_DYNSYM);
     fillStrTabShdr(DynStr, SHF_ALLOC);
     fillDynTabShdr(DynTab);
     fillStrTabShdr(ShStrTab);
+    if (HasVersion) {
+      fillVerSymShdr(VerSym);
+      fillVerDefShdr(VerDef, Vdndx);
+    }
 
     // Finish initializing the ELF header.
     initELFHeader<ELFT>(ElfHeader, static_cast<uint16_t>(*Stub.Target.Arch));
@@ -256,6 +400,8 @@ template <class ELFT> class ELFStubBuilder {
     ElfHeader.e_shnum = LastSection->Index + 1;
     ElfHeader.e_shoff =
         alignTo(LastSection->Offset + LastSection->Size, sizeof(Elf_Addr));
+
+    return Error::success();
   }
 
   size_t getSize() const {
@@ -268,10 +414,18 @@ template <class ELFT> class ELFStubBuilder {
     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 (HasVersion)
+      VerSym.Content.write(Data + VerSym.Shdr.sh_offset);
+    if (HasVersion)
+      VerDef.Content.write(Data + VerDef.Shdr.sh_offset);
     writeShdr(Data, DynSym);
     writeShdr(Data, DynStr);
     writeShdr(Data, DynTab);
     writeShdr(Data, ShStrTab);
+    if (HasVersion)
+      writeShdr(Data, VerSym);
+    if (HasVersion)
+      writeShdr(Data, VerDef);
   }
 
 private:
@@ -280,6 +434,9 @@ template <class ELFT> class ELFStubBuilder {
   ContentSection<ELFStringTableBuilder, ELFT> ShStrTab;
   ContentSection<ELFSymbolTableBuilder<ELFT>, ELFT> DynSym;
   ContentSection<ELFDynamicTableBuilder<ELFT>, ELFT> DynTab;
+  ContentSection<ELFVersionSymbolBuilder<ELFT>, ELFT> VerSym;
+  ContentSection<ELFVersionDefinitionBuilder<ELFT>, ELFT> VerDef;
+  bool HasVersion = false;
 
   template <class T> static void write(uint8_t *Data, const T &Value) {
     *reinterpret_cast<T *>(Data) = Value;
@@ -298,6 +455,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 +472,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 +486,36 @@ 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;
+  }
+
   uint64_t shdrOffset(const OutputSection<ELFT> &Sec) const {
     return ElfHeader.e_shoff + Sec.Index * sizeof(Elf_Shdr);
   }
@@ -371,6 +560,10 @@ 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); }
+
   Expected<StringRef> getDynStr() {
     if (DynSymHdr)
       return ElfFile.getStringTableForSymtab(*DynSymHdr, Shdrs);
@@ -386,11 +579,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) const {
     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 +671,16 @@ 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;
     }
   }
 
@@ -515,17 +718,22 @@ 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 Default Whether the IFSSymbol is a default version symbol.
 /// @param RawSym ELFT::Sym to extract symbol information from.
 template <class ELFT>
-static IFSSymbol createELFSym(StringRef SymName,
+static IFSSymbol createELFSym(StringRef SymName, StringRef SymVer, bool Default,
                               const typename ELFT::Sym &RawSym) {
-  IFSSymbol TargetSym{std::string(SymName)};
+  IFSSymbol TargetSym{SymName.str()};
   uint8_t Binding = RawSym.getBinding();
   if (Binding == STB_WEAK)
     TargetSym.Weak = true;
   else
     TargetSym.Weak = false;
 
+  TargetSym.Version = SymVer;
+
+  TargetSym.Default = Default;
   TargetSym.Undefined = RawSym.isUndefined();
   TargetSym.Type = convertELFSymbolTypeToIFS(RawSym.st_info);
 
@@ -537,38 +745,6 @@ static IFSSymbol createELFSym(StringRef SymName,
   return TargetSym;
 }
 
-/// This function populates an IFSStub with symbols using information read
-/// from an ELF binary.
-///
-/// @param TargetStub IFSStub to add symbols to.
-/// @param DynSym Range of dynamic symbols to add to TargetStub.
-/// @param DynStr StringRef to the dynamic string table.
-template <class ELFT>
-static Error populateSymbols(IFSStub &TargetStub,
-                             const typename ELFT::SymRange DynSym,
-                             StringRef DynStr) {
-  // Skips the first symbol since it's the NULL symbol.
-  for (auto RawSym : DynSym.drop_front(1)) {
-    // If a symbol does not have global or weak binding, ignore it.
-    uint8_t Binding = RawSym.getBinding();
-    if (!(Binding == STB_GLOBAL || Binding == STB_WEAK))
-      continue;
-    // If a symbol doesn't have default or protected visibility, ignore it.
-    uint8_t Visibility = RawSym.getVisibility();
-    if (!(Visibility == STV_DEFAULT || Visibility == STV_PROTECTED))
-      continue;
-    // Create an IFSSymbol and populate it with information from the symbol
-    // table entry.
-    Expected<StringRef> SymName = terminatedSubstr(DynStr, RawSym.st_name);
-    if (!SymName)
-      return SymName.takeError();
-    IFSSymbol Sym = createELFSym<ELFT>(*SymName, RawSym);
-    TargetStub.Symbols.push_back(std::move(Sym));
-    // TODO: Populate symbol warning.
-  }
-  return Error::success();
-}
-
 /// Returns a new IFSStub with all members populated from an ELFObjectFile.
 /// @param ElfObj Source ELFObjectFile.
 template <class ELFT>
@@ -577,6 +753,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,22 +804,76 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
     DestStub->NeededLibs.push_back(std::string(*LibNameOrErr));
   }
 
+  // [VER_NDX_LOCAL, VER_NDX_GLOBAL]
+  std::vector<std::string> Versions{{}, {}};
+
+  if (const Elf_Shdr *VerDefPtr = EDynSym->getVerDef()) {
+    Expected<std::vector<VerDef>> VerDefOrError =
+        ElfFile.getVersionDefinitions(*VerDefPtr);
+    if (!VerDefOrError)
+      return appendToError(VerDefOrError.takeError(),
+                           "when reading dynamic symbol version definitions");
+    for (const VerDef &VerDef : *VerDefOrError) {
+      size_t N = VerDef.Ndx & VERSYM_VERSION;
+      if (N >= Versions.size())
+        Versions.resize(N + 1);
+      Versions[N] = VerDef.Name.c_str();
+    }
+  }
+
   // Populate Symbols from .dynsym table and dynamic string table.
   Expected<uint64_t> SymCount = ElfFile.getDynSymtabSize();
   if (!SymCount)
     return SymCount.takeError();
-  if (*SymCount > 0) {
-    // Get pointer to in-memory location of .dynsym section.
-    Expected<const uint8_t *> DynSymPtr = EDynSym->getDynSym();
-    if (!DynSymPtr)
-      return appendToError(DynSymPtr.takeError(),
-                           "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);
-    if (SymReadError)
-      return appendToError(std::move(SymReadError),
-                           "when reading dynamic symbols");
+  if (*SymCount == 0)
+    return std::move(DestStub);
+
+  // Get pointer to in-memory location of .dynsym section.
+  Expected<const uint8_t *> DynSymPtr = EDynSym->getDynSym();
+  if (!DynSymPtr)
+    return appendToError(DynSymPtr.takeError(),
+                         "when locating .dynsym section contents");
+  Elf_Sym_Range DynSyms = ArrayRef<Elf_Sym>(
+      reinterpret_cast<const Elf_Sym *>(*DynSymPtr), *SymCount);
+
+  size_t SymbolIndex = 0;
+  // Skips the first symbol since it's the NULL symbol.
+  for (const Elf_Sym &DynSym : DynSyms.drop_front(1)) {
+    SymbolIndex++;
+    // If a symbol does not have global or weak binding, ignore it.
+    uint8_t Binding = DynSym.getBinding();
+    if (!(Binding == STB_GLOBAL || Binding == STB_WEAK))
+      continue;
+    // If a symbol doesn't have default or protected visibility, ignore it.
+    uint8_t Visibility = DynSym.getVisibility();
+    if (!(Visibility == STV_DEFAULT || Visibility == STV_PROTECTED))
+      continue;
+    Expected<StringRef> SymName = terminatedSubstr(DynStr, DynSym.st_name);
+    if (!SymName)
+      return appendToError(SymName.takeError(), "when reading dynamic symbols");
+
+    bool Default = false;
+    std::string Version;
+    if (const Elf_Shdr *VerSymPtr = EDynSym->getVerSym()) {
+      using Elf_VerSym = typename ELFT::Versym;
+      Expected<const Elf_VerSym *> VerEntryOrErr =
+          ElfFile.template getEntry<Elf_VerSym>(*VerSymPtr, SymbolIndex);
+      if (!VerEntryOrErr)
+        return appendToError(VerEntryOrErr.takeError(),
+                             "when reading symbol versions");
+      uint16_t VSIndex = (*VerEntryOrErr)->vs_index;
+      size_t VersionIndex = VSIndex & VERSYM_VERSION;
+      if (VersionIndex > VER_NDX_GLOBAL && VersionIndex < Versions.size()) {
+        Default = !(VSIndex & VERSYM_HIDDEN);
+        Version = Versions[VersionIndex];
+        if (Version.empty())
+          return createError(
+              "SHT_GNU_versym section refers to a version index " +
+              Twine(VersionIndex) + " which is missing");
+      }
+    }
+    DestStub->Symbols.push_back(
+        createELFSym<ELFT>(*SymName, Version, Default, DynSym));
   }
 
   return std::move(DestStub);
@@ -656,7 +887,9 @@ buildStub(const ELFObjectFile<ELFT> &ElfObj) {
 template <class ELFT>
 static Error writeELFBinaryToFile(StringRef FilePath, const IFSStub &Stub,
                                   bool WriteIfChanged) {
-  ELFStubBuilder<ELFT> Builder{Stub};
+  ELFStubBuilder<ELFT> Builder;
+  if (Error Err = Builder.populate(Stub))
+    return Err;
   // Write Stub to memory first.
   std::vector<uint8_t> Buf(Builder.getSize());
   Builder.write(Buf.data());
@@ -695,13 +928,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");
@@ -714,18 +950,43 @@ Error writeBinaryStub(StringRef FilePath, const IFSStub &Stub,
   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) = 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, WriteIfChanged);
   }
   llvm_unreachable("invalid binary output target");
 }
diff --git a/llvm/lib/InterfaceStub/IFSHandler.cpp b/llvm/lib/InterfaceStub/IFSHandler.cpp
index 39dc46601b2a02..9d284abb324e65 100644
--- a/llvm/lib/InterfaceStub/IFSHandler.cpp
+++ b/llvm/lib/InterfaceStub/IFSHandler.cpp
@@ -116,6 +116,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) {
@@ -126,6 +127,7 @@ template <> struct MappingTraits<IFSSymbol> {
     } else if (Symbol.Type != IFSSymbolType::Func) {
       IO.mapOptional("Size", Symbol.Size);
     }
+    IO.mapOptional("Default", Symbol.Default, false);
     IO.mapOptional("Undefined", Symbol.Undefined, false);
     IO.mapOptional("Weak", Symbol.Weak, false);
     IO.mapOptional("Warning", Symbol.Warning);
diff --git a/llvm/test/tools/llvm-ifs/versioned.ifs b/llvm/test/tools/llvm-ifs/versioned.ifs
new file mode 100644
index 00000000000000..6c9aebb815ed49
--- /dev/null
+++ b/llvm/test/tools/llvm-ifs/versioned.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, Default: true }
+# CHECK-IFS-NEXT:   - { Name: def, Version: VER, Type: Func, Default: true }
+# CHECK-IFS-NEXT:   - { Name: func, Version: VER, Type: Func }
+# CHECK-IFS-NEXT: ...
+
+# CHECK-ELF: OBJECT  GLOBAL DEFAULT  1 VER@@VER
+# CHECK-ELF: FUNC    GLOBAL DEFAULT  1 def@@VER
+# CHECK-ELF: FUNC    GLOBAL DEFAULT  1 func at 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, Default: true }
+  - { Name: func, Version: VER, Type: Func }
+  - { Name: def, Version: VER, Type: Func, Default: true }
+...
diff --git a/llvm/tools/llvm-ifs/llvm-ifs.cpp b/llvm/tools/llvm-ifs/llvm-ifs.cpp
index 575a1117363fe5..2cadd5fe554cee 100644
--- a/llvm/tools/llvm-ifs/llvm-ifs.cpp
+++ b/llvm/tools/llvm-ifs/llvm-ifs.cpp
@@ -379,7 +379,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 =
@@ -429,7 +429,8 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
     }
 
     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;
 
@@ -470,8 +471,8 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
       return -1;
     }
 
-  for (auto &Entry : SymbolMap)
-    Stub.Symbols.push_back(Entry.second);
+  for (const auto &[Name, Symbol] : SymbolMap)
+    Stub.Symbols.push_back(Symbol);
 
   // Change SoName before emitting stubs.
   if (Config.SoName)
diff --git a/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp b/llvm/unittests/InterfaceStub/ELFYAMLTest.cpp
index 39aec132db8617..efdb859ae7123a 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);
@@ -225,11 +226,19 @@ TEST(ElfYamlTextAPI, YAMLWritesTBESymbols) {
   SymNot.Undefined = false;
   SymNot.Weak = false;
 
+  IFSSymbol SymVer("ver");
+  SymVer.Version = "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 2821dc90c709d5a064a175b00a43837dba3e934e Mon Sep 17 00:00:00 2001
From: ur4t <46435411+ur4t at users.noreply.github.com>
Date: Sun, 4 Oct 2026 11:26:40 +0000
Subject: [PATCH 2/2] Fix VerDefNum and Default

---
 llvm/lib/InterfaceStub/ELFObjHandler.cpp | 10 ++++++----
 llvm/tools/llvm-ifs/llvm-ifs.cpp         |  8 ++++++++
 2 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/InterfaceStub/ELFObjHandler.cpp b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
index 1affc037dd1ee4..48b289e7c52b31 100644
--- a/llvm/lib/InterfaceStub/ELFObjHandler.cpp
+++ b/llvm/lib/InterfaceStub/ELFObjHandler.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Support/FileOutputBuffer.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include <map>
 #include <optional>
 
 using llvm::object::ELFObjectFile;
@@ -368,6 +369,7 @@ template <class ELFT> class ELFStubBuilder {
     if (HasVersion) {
       VerSymIndex = DynTab.Content.addAddr(DT_VERSYM, 0);
       VerDefIndex = DynTab.Content.addAddr(DT_VERDEF, 0);
+      DynTab.Content.addValue(DT_VERDEFNUM, Vdndx - 1);
     }
     DynTab.Size = DynTab.Content.getSize();
     // Calculate sections' addresses and offsets.
@@ -391,7 +393,7 @@ template <class ELFT> class ELFStubBuilder {
     fillStrTabShdr(ShStrTab);
     if (HasVersion) {
       fillVerSymShdr(VerSym);
-      fillVerDefShdr(VerDef, Vdndx);
+      fillVerDefShdr(VerDef, Vdndx - 1);
     }
 
     // Finish initializing the ELF header.
@@ -503,16 +505,16 @@ template <class ELFT> class ELFStubBuilder {
 
   void fillVerDefShdr(
       ContentSection<ELFVersionDefinitionBuilder<ELFT>, ELFT> &VerDef,
-      size_t Size) const {
+      size_t VerDefNum) 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_info = VerDefNum;
     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_entsize = 0;
     VerDef.Shdr.sh_link = this->DynStr.Index;
   }
 
diff --git a/llvm/tools/llvm-ifs/llvm-ifs.cpp b/llvm/tools/llvm-ifs/llvm-ifs.cpp
index 2cadd5fe554cee..24099a0841c419 100644
--- a/llvm/tools/llvm-ifs/llvm-ifs.cpp
+++ b/llvm/tools/llvm-ifs/llvm-ifs.cpp
@@ -453,6 +453,14 @@ int llvm_ifs_main(int argc, char **argv, const llvm::ToolContext &) {
 
         return -1;
       }
+      if (Symbol.Default != SI->second.Default) {
+        WithColor::error() << "Interface Stub: Default Mismatch for "
+                           << Symbol.Name << ".\nFilename: " << InputFilePath
+                           << "\nDefault Values: " << SI->second.Default << " "
+                           << Symbol.Default << "\n";
+
+        return -1;
+      }
       if (Symbol.Weak != SI->second.Weak) {
         Symbol.Weak = false;
         continue;



More information about the llvm-commits mailing list