[lld] [ELF] Merge exportDynamic into versionId (PR #71272)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 3 21:28:56 PDT 2023


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

For a Defined/Common symbol with a false `inDynamicList`, both
`versionId==VER_NDX_LOCAL` and `!exportDynamic` indicate that the symbol
should not be exported, which means that the two fields have overlapping
purposes. We can merge them together by reserving `versionId==-1` to
indicate a symbol that is not exported:

* -1 (initial): not exported, binding unchanged
* 0: VER_NDX_LOCAL, not exported, binding changed to STB_LOCAL
* 1: VER_NDX_GLOBAL, exported, binding unchanged
* others: verdef index, exported, binding unchanged

-1 and 0 are similar, but -1 does not change the binding to STB_LOCAL.

The saved bit can be use for another purpose, e.g. whether a symbol has
a DSO definition (#70130).

--version-script is almost never used for executables. If used, a minor behavior
change is that a version pattern that is not `local:` will export the matched
symbols.


>From 85ebe9be9cb8384486f3f11acbca57c68a0beaea Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Fri, 3 Nov 2023 14:48:13 -0700
Subject: [PATCH] [ELF] Merge exportDynamic into versionId

For a Defined/Common symbol with a false `inDynamicList`, both
`versionId==VER_NDX_LOCAL` and `!exportDynamic` indicate that the symbol
should not be exported, which means that the two fields have overlapping
purposes. We can merge them together by reserving `versionId==-1` to
indicate a symbol that is not exported:

* -1 (initial): not exported, binding unchanged
* 0: VER_NDX_LOCAL, not exported, binding changed to STB_LOCAL
* 1: VER_NDX_GLOBAL, exported, binding unchanged
* others: verdef index, exported, binding unchanged

-1 and 0 are similar, but -1 does not change the binding to STB_LOCAL.

The saved bit can be use for another purpose, e.g. whether a symbol has
a DSO definition (#70130).

--version-script is almost never used for executables. If used, a minor behavior
change is that a version pattern that is not `local:` will export the matched
symbols.
---
 lld/ELF/Config.h                     |  1 +
 lld/ELF/Driver.cpp                   |  3 ++
 lld/ELF/InputFiles.cpp               |  4 +--
 lld/ELF/LTO.cpp                      |  6 ++--
 lld/ELF/Relocations.cpp              |  4 +--
 lld/ELF/SymbolTable.cpp              |  2 +-
 lld/ELF/Symbols.cpp                  | 16 +++++------
 lld/ELF/Symbols.h                    | 42 +++++++++++++++++-----------
 lld/ELF/SyntheticSections.cpp        |  3 +-
 lld/test/ELF/version-script-symver.s |  4 +--
 10 files changed, 48 insertions(+), 37 deletions(-)

diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 56229334f9a44ae..06fef790d1e5439 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -225,6 +225,7 @@ struct Config {
   bool cref;
   llvm::SmallVector<std::pair<llvm::GlobPattern, uint64_t>, 0>
       deadRelocInNonAlloc;
+  uint16_t defaultVersionId;
   bool demangle = true;
   bool dependentLibraries;
   bool disableVerify;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 6290880c43d3b95..2e1214ae4087364 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1683,6 +1683,9 @@ static void readConfigs(opt::InputArgList &args) {
     }
   }
 
+  config->defaultVersionId =
+      config->exportDynamic ? ELF::VER_NDX_GLOBAL : nonExported;
+
   assert(config->versionDefinitions.empty());
   config->versionDefinitions.push_back(
       {"local", (uint16_t)VER_NDX_LOCAL, {}, {}});
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index a0d4be8ff9885b0..1ac94e179688fee 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1520,7 +1520,7 @@ template <class ELFT> void SharedFile::parse() {
       }
       Symbol *s = symtab.addSymbol(
           Undefined{this, name, sym.getBinding(), sym.st_other, sym.getType()});
-      s->exportDynamic = true;
+      s->versionId = VER_NDX_GLOBAL;
       if (s->isUndefined() && sym.getBinding() != STB_WEAK &&
           config->unresolvedSymbolsInShlib != UnresolvedPolicy::Ignore)
         requiredSymbols.push_back(s);
@@ -1698,7 +1698,7 @@ createBitcodeSymbol(Symbol *&sym, const std::vector<bool> &keptComdats,
   } else {
     Defined newSym(&f, StringRef(), binding, visibility, type, 0, 0, nullptr);
     if (objSym.canBeOmittedFromSymbolTable())
-      newSym.exportDynamic = false;
+      newSym.versionId = nonExported;
     sym->resolve(newSym);
   }
 }
diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index 504c12aac6c5696..e534f159e5e42be 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -245,9 +245,9 @@ void BitcodeCompiler::add(BitcodeFile &f) {
                             usedStartStop.count(objSym.getSectionName());
     // Identify symbols exported dynamically, and that therefore could be
     // referenced by a shared library not visible to the linker.
-    r.ExportDynamic =
-        sym->computeBinding() != STB_LOCAL &&
-        (config->exportDynamic || sym->exportDynamic || sym->inDynamicList);
+    r.ExportDynamic = sym->computeBinding() != STB_LOCAL &&
+                      (config->exportDynamic || sym->versionId != nonExported ||
+                       sym->inDynamicList);
     const auto *dr = dyn_cast<Defined>(sym);
     r.FinalDefinitionInLinkageUnit =
         (isExec || sym->visibility() != STV_DEFAULT) && dr &&
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 6765461805dadb0..cbf6c42cd2d9b8d 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -310,7 +310,7 @@ static void replaceWithDefined(Symbol &sym, SectionBase &sec, uint64_t value,
       .overwrite(sym);
 
   sym.verdefIndex = old.verdefIndex;
-  sym.exportDynamic = true;
+  sym.exportIfNonExported();
   sym.isUsedInRegularObj = true;
   // A copy relocated alias may need a GOT entry.
   sym.flags.store(old.flags.load(std::memory_order_relaxed) & NEEDS_GOT,
@@ -1068,7 +1068,7 @@ void RelocationScanner::processAux(RelExpr expr, RelType type, uint64_t offset,
   // direct relocation on through.
   if (LLVM_UNLIKELY(isIfunc) && config->zIfuncNoplt) {
     std::lock_guard<std::mutex> lock(relocMutex);
-    sym.exportDynamic = true;
+    sym.exportIfNonExported();
     mainPart->relaDyn->addSymbolReloc(type, *sec, offset, sym, addend, type);
     return;
   }
diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp
index fe7edd5b0eb49aa..1d8459c0db96857 100644
--- a/lld/ELF/SymbolTable.cpp
+++ b/lld/ELF/SymbolTable.cpp
@@ -93,7 +93,7 @@ Symbol *SymbolTable::insert(StringRef name) {
   sym->setName(name);
   sym->partition = 1;
   sym->verdefIndex = -1;
-  sym->versionId = VER_NDX_GLOBAL;
+  sym->versionId = nonExported;
   if (pos != StringRef::npos)
     sym->hasVersionSuffix = true;
   return sym;
diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp
index 671eb56f3404c95..0972a77fecfc32a 100644
--- a/lld/ELF/Symbols.cpp
+++ b/lld/ELF/Symbols.cpp
@@ -287,7 +287,7 @@ bool Symbol::includeInDynsym() const {
     // __pthread_initialize_minimal reference in csu/libc-start.c.
     return !(isUndefWeak() && config->noDynamicLinker);
 
-  return exportDynamic || inDynamicList;
+  return versionId != nonExported || inDynamicList;
 }
 
 // Print out a log message for --trace-symbol.
@@ -384,8 +384,8 @@ bool elf::computeIsPreemptible(const Symbol &sym) {
 // and that's the result of symbol resolution. However, symbols that
 // were not chosen still affect some symbol properties.
 void Symbol::mergeProperties(const Symbol &other) {
-  if (other.exportDynamic)
-    exportDynamic = true;
+  if (versionId == nonExported)
+    versionId = other.versionId;
 
   // DSO symbols do not affect visibility in the output.
   if (!other.isShared() && other.visibility() != STV_DEFAULT) {
@@ -575,8 +575,8 @@ void Symbol::checkDuplicate(const Defined &other) const {
 }
 
 void Symbol::resolve(const CommonSymbol &other) {
-  if (other.exportDynamic)
-    exportDynamic = true;
+  if (versionId == nonExported)
+    versionId = other.versionId;
   if (other.visibility() != STV_DEFAULT) {
     uint8_t v = visibility(), ov = other.visibility();
     setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
@@ -613,8 +613,8 @@ void Symbol::resolve(const CommonSymbol &other) {
 }
 
 void Symbol::resolve(const Defined &other) {
-  if (other.exportDynamic)
-    exportDynamic = true;
+  if (versionId == nonExported)
+    versionId = other.versionId;
   if (other.visibility() != STV_DEFAULT) {
     uint8_t v = visibility(), ov = other.visibility();
     setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
@@ -663,7 +663,7 @@ void Symbol::resolve(const LazyObject &other) {
 }
 
 void Symbol::resolve(const SharedSymbol &other) {
-  exportDynamic = true;
+  exportIfNonExported();
   if (isPlaceholder()) {
     other.overwrite(*this);
     return;
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index 4addb79d1257914..29eccb29fdd23da 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -67,6 +67,9 @@ struct SymbolAux {
 
 LLVM_LIBRARY_VISIBILITY extern SmallVector<SymbolAux, 0> symAux;
 
+// A versionId value similar to VER_NDX_LOCAL, but the binding is not changed.
+constexpr uint16_t nonExported = uint16_t(-1);
+
 // The base class for real symbol classes.
 class Symbol {
 public:
@@ -129,17 +132,6 @@ class Symbol {
   // which are referenced by relocations when -r or --emit-relocs is given.
   uint8_t used : 1;
 
-  // Used by a Defined symbol with protected or default visibility, to record
-  // whether it is required to be exported into .dynsym. This is set when any of
-  // the following conditions hold:
-  //
-  // - If there is an interposable symbol from a DSO. Note: We also do this for
-  //   STV_PROTECTED symbols which can't be interposed (to match BFD behavior).
-  // - If -shared or --export-dynamic is specified, any symbol in an object
-  //   file/bitcode sets this property, unless suppressed by LTO
-  //   canBeOmittedFromSymbolTable().
-  uint8_t exportDynamic : 1;
-
   // True if the symbol is in the --dynamic-list file. A Defined symbol with
   // protected or default visibility with this property is required to be
   // exported into .dynsym.
@@ -254,7 +246,7 @@ class Symbol {
   Symbol(Kind k, InputFile *file, StringRef name, uint8_t binding,
          uint8_t stOther, uint8_t type)
       : file(file), nameData(name.data()), nameSize(name.size()), type(type),
-        binding(binding), stOther(stOther), symbolKind(k), exportDynamic(false),
+        binding(binding), stOther(stOther), symbolKind(k),
         archSpecificBit(false) {}
 
   void overwrite(Symbol &sym, Kind k) const {
@@ -316,9 +308,25 @@ class Symbol {
   // This field is a index to the symbol's version definition.
   uint16_t verdefIndex;
 
-  // Version definition index.
-  uint16_t versionId;
+  // Used by a Defined symbol with protected or default visibility, to record
+  // the verdef index and whether the symbol is exported into .dynsym.
+  // * -1 (initial): not exported, binding unchanged
+  // * 0: VER_NDX_LOCAL, not exported, binding changed to STB_LOCAL
+  // * 1: VER_NDX_GLOBAL, exported, binding unchanged
+  // * others: verdef index, exported, binding unchanged
+  //
+  // -1 transits to 1 if any of the following conditions hold:
+  // * If there is an interposable symbol from a DSO. Note: We also do this for
+  //   STV_PROTECTED symbols which can't be interposed (to match BFD behavior).
+  // * If -shared or --export-dynamic is specified, any symbol in an object
+  //   file/bitcode sets this property, unless suppressed by LTO
+  //   canBeOmittedFromSymbolTable().
+  uint16_t versionId = nonExported;
 
+  void exportIfNonExported() {
+    if (versionId == nonExported)
+      versionId = llvm::ELF::VER_NDX_GLOBAL;
+  }
   void setFlags(uint16_t bits) {
     flags.fetch_or(bits, std::memory_order_relaxed);
   }
@@ -353,7 +361,7 @@ class Defined : public Symbol {
           uint8_t type, uint64_t value, uint64_t size, SectionBase *section)
       : Symbol(DefinedKind, file, name, binding, stOther, type), value(value),
         size(size), section(section) {
-    exportDynamic = config->exportDynamic;
+    versionId = config->defaultVersionId;
   }
   void overwrite(Symbol &sym) const {
     Symbol::overwrite(sym, DefinedKind);
@@ -398,7 +406,7 @@ class CommonSymbol : public Symbol {
                uint8_t stOther, uint8_t type, uint64_t alignment, uint64_t size)
       : Symbol(CommonKind, file, name, binding, stOther, type),
         alignment(alignment), size(size) {
-    exportDynamic = config->exportDynamic;
+    versionId = config->defaultVersionId;
   }
   void overwrite(Symbol &sym) const {
     Symbol::overwrite(sym, CommonKind);
@@ -442,7 +450,7 @@ class SharedSymbol : public Symbol {
                uint32_t alignment)
       : Symbol(SharedKind, &file, name, binding, stOther, type), value(value),
         size(size), alignment(alignment) {
-    exportDynamic = true;
+    versionId = llvm::ELF::VER_NDX_GLOBAL;
     dsoProtected = visibility() == llvm::ELF::STV_PROTECTED;
     // GNU ifunc is a mechanism to allow user-supplied functions to
     // resolve PLT slot values at load-time. This is contrary to the
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 0f7ebf9d7ba840b..235c0de07ab81a4 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -3128,7 +3128,8 @@ void VersionTableSection::writeTo(uint8_t *buf) {
     // For an unextracted lazy symbol (undefined weak), it must have been
     // converted to Undefined and have VER_NDX_GLOBAL version here.
     assert(!s.sym->isLazy());
-    write16(buf, s.sym->versionId);
+    write16(buf, s.sym->versionId == uint16_t(-1) ? VER_NDX_GLOBAL
+                                                  : s.sym->versionId);
     buf += 2;
   }
 }
diff --git a/lld/test/ELF/version-script-symver.s b/lld/test/ELF/version-script-symver.s
index db7c6f434ff4e57..798d9dbc5d7f144 100644
--- a/lld/test/ELF/version-script-symver.s
+++ b/lld/test/ELF/version-script-symver.s
@@ -42,9 +42,7 @@
 # RUN: ld.lld --version-script %t4.script -pie --export-dynamic %t.o -o %t4
 # RUN: llvm-readelf --dyn-syms %t4 | FileCheck --check-prefix=MIX2 %s
 # RUN: ld.lld --version-script %t4.script -pie %t.o -o %t4
-# RUN: llvm-readelf --dyn-syms %t4 | FileCheck --check-prefix=EXE %s
-
-# EXE: Symbol table '.dynsym' contains 1 entries:
+# RUN: llvm-readelf --dyn-syms %t4 | FileCheck --check-prefix=MIX2 %s
 
 # RUN: ld.lld --version-script %t4.script -shared %t.o %tref.o -o %t5.so
 # RUN: llvm-readelf -r %t5.so | FileCheck --check-prefix=RELOC %s



More information about the llvm-commits mailing list