[lld] d96a93f - ELF: Use index 0 for unversioned undefined symbols (#168189)
via llvm-commits
llvm-commits at lists.llvm.org
Sat Nov 22 12:54:55 PST 2025
Author: Fangrui Song
Date: 2025-11-22T20:54:50Z
New Revision: d96a93ff00ac02bc523d36dd2e687d597a068ae1
URL: https://github.com/llvm/llvm-project/commit/d96a93ff00ac02bc523d36dd2e687d597a068ae1
DIFF: https://github.com/llvm/llvm-project/commit/d96a93ff00ac02bc523d36dd2e687d597a068ae1.diff
LOG: ELF: Use index 0 for unversioned undefined symbols (#168189)
The GNU documentation is ambiguous about the version index for
unversioned undefined symbols. The current specification at
https://sourceware.org/gnu-gabi/program-loading-and-dynamic-linking.txt
defines VER_NDX_LOCAL (0) as "The symbol is private, and is not
available outside this object."
However, this naming is misleading for undefined symbols. As suggested
in
discussions, VER_NDX_LOCAL should conceptually be VER_NDX_NONE and apply
to unversioned undefined symbols as well.
GNU ld has used index 0 for unversioned undefined symbols both before
version 2.35 (see https://sourceware.org/PR26002) and in the upcoming
2.46 release (see https://sourceware.org/PR33577). This change aligns
with GNU ld's behavior by switching from index 1 to index 0.
While here, add a test to dso-undef-extract-lazy.s that undefined
symbols of index 0 in DSO are treated as unversioned symbols.
Added:
Modified:
lld/ELF/InputFiles.cpp
lld/ELF/Symbols.h
lld/ELF/SyntheticSections.cpp
lld/test/ELF/linkerscript/version-script.s
lld/test/ELF/version-script-extern-undefined.s
llvm/include/llvm/BinaryFormat/ELF.h
Removed:
################################################################################
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index a5921feb18299..240a6d0cd4b2b 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1676,8 +1676,9 @@ template <class ELFT> void SharedFile::parse() {
const uint16_t ver = versyms[i], idx = ver & ~VERSYM_HIDDEN;
if (sym.isUndefined()) {
- // For unversioned undefined symbols, VER_NDX_GLOBAL makes more sense but
- // as of binutils 2.34, GNU ld produces VER_NDX_LOCAL.
+ // Index 0 (VER_NDX_LOCAL) is used for unversioned undefined symbols.
+ // GNU ld versions between 2.35 and 2.45 also generate VER_NDX_GLOBAL
+ // for this case (https://sourceware.org/PR33577).
if (ver != VER_NDX_LOCAL && ver != VER_NDX_GLOBAL) {
if (idx >= verneeds.size()) {
ErrAlways(ctx) << "corrupt input file: version need index " << idx
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index c117e3b716c1b..a7d61f48ed3d5 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -313,6 +313,8 @@ class Symbol {
// represents the Verdef index within the input DSO, which will be converted
// to a Verneed index in the output. Otherwise, this represents the Verdef
// index (VER_NDX_LOCAL, VER_NDX_GLOBAL, or a named version).
+ // VER_NDX_LOCAL indicates a defined symbol that has been localized by a
+ // version script's local: directive or --exclude-libs.
uint16_t versionId;
LLVM_PREFERRED_TYPE(bool)
uint8_t versionScriptAssigned : 1;
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 19b08152ae081..ea9b87952cd84 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -3784,9 +3784,10 @@ void VersionTableSection::writeTo(uint8_t *buf) {
buf += 2;
for (const SymbolTableEntry &s : getPartition(ctx).dynSymTab->getSymbols()) {
// For an unextracted lazy symbol (undefined weak), it must have been
- // converted to Undefined and have VER_NDX_GLOBAL version here.
+ // converted to Undefined.
assert(!s.sym->isLazy());
- write16(ctx, buf, s.sym->versionId);
+ // Undefined symbols should use index 0 when unversioned.
+ write16(ctx, buf, s.sym->isUndefined() ? 0 : s.sym->versionId);
buf += 2;
}
}
diff --git a/lld/test/ELF/linkerscript/version-script.s b/lld/test/ELF/linkerscript/version-script.s
index 52382eeb1245c..6b97fede00c37 100644
--- a/lld/test/ELF/linkerscript/version-script.s
+++ b/lld/test/ELF/linkerscript/version-script.s
@@ -17,7 +17,7 @@
# CHECK-NEXT: Name:
# CHECK-NEXT: }
# CHECK-NEXT: Symbol {
-# CHECK-NEXT: Version: 1
+# CHECK-NEXT: Version: 0
# CHECK-NEXT: Name: und
# CHECK-NEXT: }
# CHECK-NEXT: Symbol {
diff --git a/lld/test/ELF/version-script-extern-undefined.s b/lld/test/ELF/version-script-extern-undefined.s
index 38114229e0ce3..010b4d5d6b63d 100644
--- a/lld/test/ELF/version-script-extern-undefined.s
+++ b/lld/test/ELF/version-script-extern-undefined.s
@@ -11,7 +11,7 @@
# CHECK-NEXT: Name:
# CHECK-NEXT: }
# CHECK-NEXT: Symbol {
-# CHECK-NEXT: Version: 1
+# CHECK-NEXT: Version: 0
# CHECK-NEXT: Name: _Z3abbi
# CHECK-NEXT: }
# CHECK-NEXT: ]
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 39e9611c7190e..bfcf5dab47722 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1710,8 +1710,8 @@ enum { VER_FLG_BASE = 0x1, VER_FLG_WEAK = 0x2, VER_FLG_INFO = 0x4 };
// Special constants for the version table. (SHT_GNU_versym/.gnu.version)
enum {
- VER_NDX_LOCAL = 0, // Unversioned local symbol
- VER_NDX_GLOBAL = 1, // Unversioned global symbol
+ VER_NDX_LOCAL = 0, // Unversioned undefined or localized defined symbol
+ VER_NDX_GLOBAL = 1, // Unversioned non-local defined symbol
VERSYM_VERSION = 0x7fff, // Version Index mask
VERSYM_HIDDEN = 0x8000 // Hidden bit (non-default version)
};
More information about the llvm-commits
mailing list