[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
2fcaa00d1e).

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
This commit is contained in:
Fangrui Song
2025-06-12 19:50:41 -07:00
committed by GitHub
parent 7232c07eb9
commit 07dad4ecba
10 changed files with 59 additions and 14 deletions

View File

@@ -368,6 +368,7 @@ struct Config {
bool writeAddends;
bool zCombreloc;
bool zCopyreloc;
bool zDynamicUndefined;
bool zForceBti;
bool zForceIbt;
bool zGlobal;

View File

@@ -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");

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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':