[lld] [ELF] Set vna_flags to VER_FLG_WEAK if all references are weak (PR #176673)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Jan 18 14:32:38 PST 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/176673

When all undefined references to a version are weak, set vna_flags to
VER_FLG_WEAK in the .gnu.version_r section. This enables glibc ld.so to
report a warning instead of an error when the required version is not
found at runtime, supporting optional dependencies.

Per https://sourceware.org/bugzilla/show_bug.cgi?id=24718#c20 ,
glibc rtld since 2.30 (BZ #24741) tolerates missing versioned symbols
when the runtime shared object defines the required version. With this
vna_flags VER_FLG_WEAK change, rtld can also tolerate a completely
missing version, printing a message like:

```
% LD_PRELOAD=c2.so ./a
./a: /tmp/t/v2/c2.so: weak version `v1' not found (required by /tmp/t/v2/b.so)
a
```

instead of a fatal error.

In addition, add a comment about `vn.vernaxus.size() == 1`.


>From d1217e1ceb184f0bb08b59cb2d99b01969f4b8f5 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 18 Jan 2026 14:32:27 -0800
Subject: [PATCH] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20initia?=
 =?UTF-8?q?l=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.5-bogner
---
 lld/ELF/InputFiles.h                  | 14 +++--
 lld/ELF/SyntheticSections.cpp         | 36 +++++++-----
 lld/ELF/SyntheticSections.h           |  2 +-
 lld/test/ELF/verneed-as-needed-weak.s | 18 ------
 lld/test/ELF/verneed-weak.s           | 80 +++++++++++++++++++++++++++
 5 files changed, 112 insertions(+), 38 deletions(-)
 delete mode 100644 lld/test/ELF/verneed-as-needed-weak.s
 create mode 100644 lld/test/ELF/verneed-weak.s

diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index ba844ad18f637..4672657cad71a 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -341,10 +341,16 @@ class SharedFile : public ELFFileBase {
   // This is actually a vector of Elf_Verdef pointers.
   SmallVector<const void *, 0> verdefs;
 
-  // If the output file needs Elf_Verneed data structures for this file, this is
-  // a vector of Elf_Vernaux version identifiers that map onto the entries in
-  // Verdefs, otherwise it is empty.
-  SmallVector<uint32_t, 0> vernauxs;
+  // Parallel to verdefs. If a version definition is referenced by a relocatable
+  // file, the entry records the assigned Vernaux index in the output file and
+  // whether all references are weak.
+  struct VerneedInfo {
+    uint16_t id = 0;
+    // True if all references to this version are weak. Used to set
+    // VER_FLG_WEAK.
+    bool weak = true;
+  };
+  SmallVector<VerneedInfo, 0> verneedInfo;
 
   SmallVector<StringRef, 0> dtNeeded;
   StringRef soName;
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 1c3ae0ac48e9c..8c24d5b864b3a 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1575,7 +1575,7 @@ DynamicSection<ELFT>::computeContents() {
     addInSec(DT_VERNEED, *part.verNeed);
     unsigned needNum = 0;
     for (SharedFile *f : ctx.sharedFiles)
-      if (!f->vernauxs.empty())
+      if (!f->verneedInfo.empty())
         ++needNum;
     addInt(DT_VERNEEDNUM, needNum);
   }
@@ -3791,17 +3791,18 @@ void elf::addVerneed(Ctx &ctx, Symbol &ss) {
   if (ss.versionId == VER_NDX_GLOBAL)
     return;
 
-  if (file.vernauxs.empty())
-    file.vernauxs.resize(file.verdefs.size());
+  if (file.verneedInfo.empty())
+    file.verneedInfo.resize(file.verdefs.size());
 
   // Select a version identifier for the vernaux data structure, if we haven't
   // already allocated one. The verdef identifiers cover the range
   // [1..getVerDefNum(ctx)]; this causes the vernaux identifiers to start from
   // getVerDefNum(ctx)+1.
-  if (file.vernauxs[ss.versionId] == 0)
-    file.vernauxs[ss.versionId] = ++ctx.vernauxNum + getVerDefNum(ctx);
+  if (file.verneedInfo[ss.versionId].id == 0)
+    file.verneedInfo[ss.versionId].id = ++ctx.vernauxNum + getVerDefNum(ctx);
+  file.verneedInfo[ss.versionId].weak &= ss.isWeak();
 
-  ss.versionId = file.vernauxs[ss.versionId];
+  ss.versionId = file.verneedInfo[ss.versionId].id;
 }
 
 template <class ELFT>
@@ -3811,29 +3812,34 @@ VersionNeedSection<ELFT>::VersionNeedSection(Ctx &ctx)
 
 template <class ELFT> void VersionNeedSection<ELFT>::finalizeContents() {
   for (SharedFile *f : ctx.sharedFiles) {
-    if (f->vernauxs.empty())
+    if (f->verneedInfo.empty())
       continue;
     verneeds.emplace_back();
     Verneed &vn = verneeds.back();
     vn.nameStrTab = getPartition(ctx).dynStrTab->addString(f->soName);
     bool isLibc = ctx.arg.relrGlibc && f->soName.starts_with("libc.so.");
     bool isGlibc2 = false;
-    for (unsigned i = 0; i != f->vernauxs.size(); ++i) {
-      if (f->vernauxs[i] == 0)
+    for (unsigned i = 0; i != f->verneedInfo.size(); ++i) {
+      if (f->verneedInfo[i].id == 0)
         continue;
+      // Each Verdef has one or more Verdaux entries. The first Verdaux gives
+      // the version name; subsequent entries (if any) are parent versions
+      // (e.g., v2 {} v1;). We only use the first one, as parent versions have
+      // not rtld behavior difference in practice.
       auto *verdef =
           reinterpret_cast<const typename ELFT::Verdef *>(f->verdefs[i]);
       StringRef ver(f->getStringTable().data() + verdef->getAux()->vda_name);
       if (isLibc && ver.starts_with("GLIBC_2."))
         isGlibc2 = true;
-      vn.vernauxs.push_back({verdef->vd_hash, f->vernauxs[i],
+      vn.vernauxs.push_back({verdef->vd_hash, f->verneedInfo[i],
                              getPartition(ctx).dynStrTab->addString(ver)});
     }
     if (isGlibc2) {
       const char *ver = "GLIBC_ABI_DT_RELR";
-      vn.vernauxs.push_back({hashSysV(ver),
-                             ++ctx.vernauxNum + getVerDefNum(ctx),
-                             getPartition(ctx).dynStrTab->addString(ver)});
+      vn.vernauxs.push_back(
+          {hashSysV(ver),
+           {uint16_t(++ctx.vernauxNum + getVerDefNum(ctx)), false},
+           getPartition(ctx).dynStrTab->addString(ver)});
     }
   }
 
@@ -3860,8 +3866,8 @@ template <class ELFT> void VersionNeedSection<ELFT>::writeTo(uint8_t *buf) {
     // Create the Elf_Vernauxs for this Elf_Verneed.
     for (auto &vna : vn.vernauxs) {
       vernaux->vna_hash = vna.hash;
-      vernaux->vna_flags = 0;
-      vernaux->vna_other = vna.verneedIndex;
+      vernaux->vna_flags = vna.verneedInfo.weak ? VER_FLG_WEAK : 0;
+      vernaux->vna_other = vna.verneedInfo.id;
       vernaux->vna_name = vna.nameStrTab;
       vernaux->vna_next = sizeof(Elf_Vernaux);
       ++vernaux;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index f9862b07b1ed7..2ce8d132c93fe 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -1057,7 +1057,7 @@ class VersionNeedSection final : public SyntheticSection {
 
   struct Vernaux {
     uint64_t hash;
-    uint32_t verneedIndex;
+    SharedFile::VerneedInfo verneedInfo;
     uint64_t nameStrTab;
   };
 
diff --git a/lld/test/ELF/verneed-as-needed-weak.s b/lld/test/ELF/verneed-as-needed-weak.s
deleted file mode 100644
index 215ec8e218cc3..0000000000000
--- a/lld/test/ELF/verneed-as-needed-weak.s
+++ /dev/null
@@ -1,18 +0,0 @@
-# REQUIRES: x86
-# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %S/Inputs/verneed1.s -o %t1.o
-# RUN: echo "v1 {}; v2 {}; v3 { local: *; };" > %t.script
-# RUN: ld.lld -shared %t1.o --version-script %t.script -o %t.so
-
-# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o
-# RUN: ld.lld %t.o --as-needed %t.so -o %t
-# RUN: llvm-readobj -V %t | FileCheck %s
-
-# CHECK:       VersionRequirements [
-# CHECK-NEXT:  ]
-
-.weak f1
-
-.globl _start
-_start:
-.data
-.quad f1
diff --git a/lld/test/ELF/verneed-weak.s b/lld/test/ELF/verneed-weak.s
new file mode 100644
index 0000000000000..1383ef76347af
--- /dev/null
+++ b/lld/test/ELF/verneed-weak.s
@@ -0,0 +1,80 @@
+# REQUIRES: x86
+## Test that vna_flags is set to VER_FLG_WEAK if all references to a version
+## are weak. This allows rtld to continue with a warning instead of erroring
+## when the required version is not found.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 ref.s -o ref.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 refw.s -o refw.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 refw-gh.s -o refw-gh.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 def.s -o def.o
+# RUN: ld.lld -shared --soname=def.so --version-script=ver def.o -o def.so
+
+## All references are weak; vna_flags should be VER_FLG_WEAK.
+# RUN: ld.lld -shared refw-gh.o def.so -o weakref.so
+# RUN: llvm-readelf -V weakref.so | FileCheck %s --check-prefix=WEAK1
+
+# WEAK1:      Version needs section '.gnu.version_r' contains 1 entries:
+# WEAK1-NEXT:  Addr:
+# WEAK1-NEXT:   0x0000: Version: 1  File: def.so  Cnt: 2
+# WEAK1-NEXT:   0x0010:   Name: v1  Flags: WEAK  Version: 3
+# WEAK1-NEXT:   0x0020:   Name: v2  Flags: WEAK  Version: 2
+
+## All references are weak (from two object files); vna_flags should be VER_FLG_WEAK.
+# RUN: ld.lld -shared refw.o refw-gh.o def.so -o weakref2.so
+# RUN: llvm-readelf -V weakref2.so | FileCheck %s --check-prefix=WEAK2
+
+# WEAK2:      Version needs section '.gnu.version_r' contains 1 entries:
+# WEAK2-NEXT:  Addr:
+# WEAK2-NEXT:   0x0000: Version: 1  File: def.so  Cnt: 2
+# WEAK2-NEXT:   0x0010:   Name: v1  Flags: WEAK  Version: 2
+# WEAK2-NEXT:   0x0020:   Name: v2  Flags: WEAK  Version: 3
+
+## v1 has mixed references (none); v2 has weak references (WEAK).
+# RUN: ld.lld -shared ref.o refw-gh.o def.so -o mixedref.so
+# RUN: llvm-readelf -V mixedref.so | FileCheck %s --check-prefix=MIXED
+
+# MIXED:      Version needs section '.gnu.version_r' contains 1 entries:
+# MIXED-NEXT:  Addr:
+# MIXED-NEXT:   0x0000: Version: 1  File: def.so  Cnt: 2
+# MIXED-NEXT:   0x0010:   Name: v1  Flags: none  Version: 2
+# MIXED-NEXT:   0x0020:   Name: v2  Flags: WEAK  Version: 3
+
+## All references are non-weak; vna_flags should be 0 (none).
+# RUN: ld.lld -shared ref.o def.so -o strongref.so
+# RUN: llvm-readelf -V strongref.so | FileCheck %s --check-prefix=STRONG
+
+# STRONG:      Version needs section '.gnu.version_r' contains 1 entries:
+# STRONG-NEXT:  Addr:
+# STRONG-NEXT:   0x0000: Version: 1  File: def.so  Cnt: 1
+# STRONG-NEXT:   0x0010:   Name: v1  Flags: none  Version: 2
+# STRONG-EMPTY:
+
+## --as-needed: weak reference doesn't pull in def.so, so no version needs.
+# RUN: ld.lld -shared refw.o --as-needed def.so -o asneeded.so
+# RUN: llvm-readelf -V asneeded.so | count 0
+
+#--- ver
+v1 { f; g; };
+v2 { h; };
+
+#--- ref.s
+.globl f
+call f
+
+#--- refw.s
+.weak f
+call f
+
+#--- refw-gh.s
+.weak g, h
+.symver g, g@@@v1
+call g
+call h
+
+#--- def.s
+.globl f, g, h
+f:
+g:
+h:
+  ret



More information about the llvm-commits mailing list