[lld] 07dad4e - [ELF] Implement -z dynamic-undefined-weak
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 12 19:50:44 PDT 2025
Author: Fangrui Song
Date: 2025-06-12T19:50:41-07:00
New Revision: 07dad4ecba43bcd92453a0cd4c351025126db683
URL: https://github.com/llvm/llvm-project/commit/07dad4ecba43bcd92453a0cd4c351025126db683
DIFF: https://github.com/llvm/llvm-project/commit/07dad4ecba43bcd92453a0cd4c351025126db683.diff
LOG: [ELF] Implement -z dynamic-undefined-weak
The behavior of an undefined weak reference is implementation defined.
For static -no-pie linking, dynamic relocations are generally avoided (except
IRELATIVE). -shared linking generally emits dynamic relocations.
Dynamic -no-pie linking and -pie allow flexibility. Changes adjust the
behavior for better consistency and simpler internal representation,
e.g. https://reviews.llvm.org/D63003 https://reviews.llvm.org/D105164
(generalized to undefined non-weak in
2fcaa00d1e2317a90c9071b735eb0e758b5dd58b).
GNU ld introduced -z [no]dynamic-undefined-weak option to fine-tune the
behavior. (The option is not very effective with -no-pie, e.g. on
x86-64, `ld.bfd a.o s.so -z dynamic-undefined-weak` generates
R_X86_64_NONE relocations instead of GLOB_DAT/JUMP_SLOT)
This patch implements -z [no]dynamic-undefined-weak option.
The effects are summarized as follows:
* Static -no-pie: no-op
* Dynamic -no-pie: nodynamic-undefined-weak suppresses GLOB_DAT/JUMP_SLOT
* Static -pie: dynamic-undefined-weak generates ABS/GLOB_DAT/JUMP_SLOT.
https://discourse.llvm.org/t/lld-weak-undefined-symbols-in-vdso-only/86749
* Dynamic -pie: nodynamic-undefined-weak suppresses ABS/GLOB_DAT/JUMP_SLOT
The -pie behavior likely stays stable while -no-pie (`!ctx.arg.isPic` in
`isStaticLinkTimeConstant`) behavior will likely change in the future.
The current default value of ctx.arg.zDynamicUndefined is selected to
prevent behavior changes.
Pull Request: https://github.com/llvm/llvm-project/pull/143831
Added:
Modified:
lld/ELF/Config.h
lld/ELF/Driver.cpp
lld/ELF/Symbols.cpp
lld/ELF/Writer.cpp
lld/docs/ReleaseNotes.rst
lld/docs/ld.lld.1
lld/test/ELF/driver.test
lld/test/ELF/weak-undef-got-plt.s
lld/test/ELF/weak-undef-hidden.s
lld/test/ELF/weak-undef-rw.s
Removed:
################################################################################
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 0a52dfe6901bd..3a9001d2cc8b8 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -368,6 +368,7 @@ struct Config {
bool writeAddends;
bool zCombreloc;
bool zCopyreloc;
+ bool zDynamicUndefined;
bool zForceBti;
bool zForceIbt;
bool zGlobal;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 12dac82c614a7..87b19cf543d9f 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -591,6 +591,7 @@ static void checkZOptions(Ctx &ctx, opt::InputArgList &args) {
args::getZOptionValue(args, OPT_z, "max-page-size", 0);
args::getZOptionValue(args, OPT_z, "common-page-size", 0);
getZFlag(args, "rel", "rela", false);
+ getZFlag(args, "dynamic-undefined-weak", "nodynamic-undefined-weak", false);
for (auto *arg : args.filtered(OPT_z))
if (!arg->isClaimed())
Warn(ctx) << "unknown -z value: " << StringRef(arg->getValue());
@@ -3058,6 +3059,13 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
ctx.hasDynsym = !ctx.sharedFiles.empty() || ctx.arg.isPic;
ctx.arg.exportDynamic &= ctx.hasDynsym;
+ // Preemptibility of undefined symbols when ctx.hasDynsym is true. Default is
+ // true for dynamic linking.
+ ctx.arg.zDynamicUndefined =
+ getZFlag(args, "dynamic-undefined-weak", "nodynamic-undefined-weak",
+ ctx.sharedFiles.size() || ctx.arg.shared) &&
+ ctx.hasDynsym;
+
// If an entry symbol is in a static archive, pull out that file now.
if (Symbol *sym = ctx.symtab->find(ctx.arg.entry))
handleUndefined(ctx, sym, "--entry");
diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp
index c461dfed0d741..de839795c50d7 100644
--- a/lld/ELF/Symbols.cpp
+++ b/lld/ELF/Symbols.cpp
@@ -333,10 +333,13 @@ bool elf::computeIsPreemptible(Ctx &ctx, const Symbol &sym) {
if (sym.visibility() != STV_DEFAULT)
return false;
- // At this point copy relocations have not been created yet, so any
- // symbol that is not defined locally is preemptible.
+ // At this point copy relocations have not been created yet.
+ // Shared symbols are preemptible. Undefined symbols are preemptible
+ // when zDynamicUndefined (default in dynamic linking). Weakness is not
+ // checked, though undefined non-weak would typically trigger relocation
+ // errors unless options like -z undefs are used.
if (!sym.isDefined())
- return true;
+ return !sym.isUndefined() || ctx.arg.zDynamicUndefined;
if (!ctx.arg.shared)
return false;
@@ -360,7 +363,6 @@ void elf::parseVersionAndComputeIsPreemptible(Ctx &ctx) {
// can contain versions in the form of <name>@<version>.
// Let them parse and update their names to exclude version suffix.
// In addition, compute isExported and isPreemptible.
- bool maybePreemptible = ctx.sharedFiles.size() || ctx.arg.shared;
for (Symbol *sym : ctx.symtab->getSymbols()) {
if (sym->hasVersionSuffix)
sym->parseSymbolVersion(ctx);
@@ -369,11 +371,11 @@ void elf::parseVersionAndComputeIsPreemptible(Ctx &ctx) {
continue;
}
if (!sym->isDefined() && !sym->isCommon()) {
- sym->isPreemptible = maybePreemptible && computeIsPreemptible(ctx, *sym);
+ sym->isPreemptible = computeIsPreemptible(ctx, *sym);
} else if (ctx.arg.exportDynamic &&
(sym->isUsedInRegularObj || !sym->ltoCanOmit)) {
sym->isExported = true;
- sym->isPreemptible = maybePreemptible && computeIsPreemptible(ctx, *sym);
+ sym->isPreemptible = computeIsPreemptible(ctx, *sym);
}
}
}
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 3d9888f576f05..15909daf51ab6 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -285,7 +285,6 @@ static void demoteDefined(Defined &sym, DenseMap<SectionBase *, size_t> &map) {
static void demoteSymbolsAndComputeIsPreemptible(Ctx &ctx) {
llvm::TimeTraceScope timeScope("Demote symbols");
DenseMap<InputFile *, DenseMap<SectionBase *, size_t>> sectionIndexMap;
- bool maybePreemptible = ctx.sharedFiles.size() || ctx.arg.shared;
for (Symbol *sym : ctx.symtab->getSymbols()) {
if (auto *d = dyn_cast<Defined>(sym)) {
if (d->section && !d->section->isLive())
@@ -301,9 +300,8 @@ static void demoteSymbolsAndComputeIsPreemptible(Ctx &ctx) {
}
}
- if (maybePreemptible)
- sym->isPreemptible = (sym->isUndefined() || sym->isExported) &&
- computeIsPreemptible(ctx, *sym);
+ sym->isPreemptible = (sym->isUndefined() || sym->isExported) &&
+ computeIsPreemptible(ctx, *sym);
}
}
diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst
index 5c180fd8fbeeb..064ed0828c31f 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -25,6 +25,10 @@ Non-comprehensive list of changes in this release
ELF Improvements
----------------
+* Added ``-z dynamic-undefined-weak`` to make undefined weak symbols dynamic
+ when the dynamic symbol table is present.
+ (`#143831 <https://github.com/llvm/llvm-project/pull/143831>`_)
+
* For AArch64, added support for ``-zgcs-report-dynamic``, enabling checks for
GNU GCS Attribute Flags in Dynamic Objects when GCS is enabled. Inherits value
from ``-zgcs-report`` (capped at ``warning`` level) unless user-defined,
diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1
index 57aa2be5907b5..cfacdb081a807 100644
--- a/lld/docs/ld.lld.1
+++ b/lld/docs/ld.lld.1
@@ -793,6 +793,14 @@ Specify how to report the missing GNU_PROPERTY_X86_FEATURE_1_IBT or GNU_PROPERTY
.Cm none
is the default, linker will not report the missing property otherwise will be reported as a warning or an error.
.Pp
+.It Cm dynamic-undefined-weak
+Make undefined weak symbols dynamic when the dynamic symbol table is present, if they are referenced from
+relocatable object files and not forced local by symbol visibility or versioning. Do not make them dynamic when
+.Cm nodynamic-undefined-weak
+is specified.
+.Cm dynamic-undefined-weak
+is the default when building a shared object, or when an input shared object is present.
+.Pp
.It Cm pauth-report Ns = Ns Ar [none|warning|error]
Specify how to report the missing GNU_PROPERTY_AARCH64_FEATURE_PAUTH property.
.Cm none
diff --git a/lld/test/ELF/driver.test b/lld/test/ELF/driver.test
index 45d73607c8ac6..6d5761212cc38 100644
--- a/lld/test/ELF/driver.test
+++ b/lld/test/ELF/driver.test
@@ -47,7 +47,8 @@
# ERR9: error: cannot open output file utput=/no/such/file
# RUN: ld.lld %t -z foo -o /dev/null 2>&1 | FileCheck -check-prefix=ERR10 %s --implicit-check-not=warning:
-# RUN: ld.lld %t -z foo -z rel -z rela -z max-page-size=1 -z common-page-size=1 -o /dev/null --version 2>&1 | \
+# RUN: ld.lld %t -z foo -z rel -z rela -z max-page-size=1 -z common-page-size=1 -z dynamic-undefined-weak \
+# RUN: -z nodynamic-undefined-weak -o /dev/null --version 2>&1 | \
# RUN: FileCheck -check-prefix=ERR10 %s --implicit-check-not=warning:
# ERR10: warning: unknown -z value: foo
diff --git a/lld/test/ELF/weak-undef-got-plt.s b/lld/test/ELF/weak-undef-got-plt.s
index 0ee3da2cd3b40..48a7914e5b987 100644
--- a/lld/test/ELF/weak-undef-got-plt.s
+++ b/lld/test/ELF/weak-undef-got-plt.s
@@ -6,11 +6,17 @@
# RUN: ld.lld a.o -o a
# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=NORELOC
+# RUN: ld.lld a.o -o a -z dynamic-undefined-weak
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=NORELOC
# RUN: ld.lld a.o s.so -o as
# RUN: llvm-objdump -dR as | FileCheck %s
+# RUN: ld.lld a.o s.so -o as -z nodynamic-undefined-weak
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=NORELOC
# RUN: ld.lld -pie a.o s.so -o as.pie
# RUN: llvm-objdump -dR as.pie | FileCheck %s
+# RUN: ld.lld -pie a.o s.so -o as.pie -z nodynamic-undefined-weak
+# RUN: llvm-readelf -r as.pie | FileCheck --check-prefix=NORELOC %s
# RUN: ld.lld -shared a.o -o a.so
# RUN: llvm-objdump -dR a.so | FileCheck %s
diff --git a/lld/test/ELF/weak-undef-hidden.s b/lld/test/ELF/weak-undef-hidden.s
index 2baad5738c36f..ad2ba29ec27ab 100644
--- a/lld/test/ELF/weak-undef-hidden.s
+++ b/lld/test/ELF/weak-undef-hidden.s
@@ -5,6 +5,10 @@
// RUN: ld.lld %t.o -o %t -pie
// RUN: llvm-readobj -r -S --section-data %t | FileCheck %s
+/// -z dynamic-undefined-weak does not affect hidden undefined symbols.
+// RUN: ld.lld %t.o -o %t.so -shared -z dynamic-undefined-weak
+// RUN: llvm-readobj -r -S --section-data %t.so | FileCheck %s
+
/// This is usually guarded with a comparison. Don't report an error.
call g
diff --git a/lld/test/ELF/weak-undef-rw.s b/lld/test/ELF/weak-undef-rw.s
index 497228a3cf905..8d777669b7e16 100644
--- a/lld/test/ELF/weak-undef-rw.s
+++ b/lld/test/ELF/weak-undef-rw.s
@@ -18,9 +18,22 @@
## gABI leaves the behavior of weak undefined references implementation defined.
## We choose to resolve them statically for static linking and produce dynamic relocations
## for dynamic linking (-shared or at least one input DSO).
-##
-## Note: Some ports of GNU ld support -z nodynamic-undefined-weak that we don't
-## implement.
+
+## -z dynamic-undefined-weak is ignored if .dynsym is absent (-no-pie without DSO)
+# RUN: ld.lld a.o -o a.d -z dynamic-undefined-weak 2>&1 | count 0
+# RUN: llvm-readelf -r --hex-dump=.data a.d | FileCheck %s --check-prefix=STATIC
+
+## Currently no effect for S+A relocations.
+# RUN: ld.lld a.o s.so -o as.d -z dynamic-undefined-weak
+# RUN: llvm-readelf -r --hex-dump=.data as.d | FileCheck %s --check-prefix=STATIC
+
+## -z dynamic-undefined-weak forces dynamic relocations if .dynsym is present.
+# RUN: ld.lld a.o -o a.pie.d -pie -z dynamic-undefined-weak
+# RUN: llvm-readelf -r a.pie.d | FileCheck %s --check-prefix=DYN
+
+## -z nodynamic-undefined-weak suppresses dynamic relocations.
+# RUN: ld.lld a.o -o a.so.n -shared -z dynamic-undefined-weak -z nodynamic-undefined-weak
+# RUN: llvm-readelf -r --hex-dump=.data a.so.n | FileCheck %s --check-prefix=STATIC
# STATIC: no relocations
# STATIC: Hex dump of section '.data':
More information about the llvm-commits
mailing list