[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