[RFC] Standardizing undefined symbol relocation retention in ELF ld.bfd
Hakan Candar
hakancandar@protonmail.com
Tue Jul 1 20:52:44 GMT 2025
Hi all,
I've been working with ELF dynamic symbol resolution in ld.bfd and came
across some surprising inconsistencies when it comes to how undefined
symbols - both weak and strong - are handled across different backends
and versions.
Currently, the behavior of undefined symbol retention varies not only
across architectures, but even across different binutils versions of the
same architecture. This leads to situations where the same source and
linker flags behave differently depending on target and toolchain
version, making it difficult for users to write portable and predictable
link lines.
Here's a summary of the behavior I observed:
(Strong symbols were tested with --unresolved-symbols=ignore-all)
Target | Version | Keeps Weak | Keeps Strong | Accepts -z dynamic-undefined-weak
-----------+---------+------------+--------------+----------------------------------
x86-64 | 2.41+ | Yes | No* | Yes
aarch64 | 2.41 | Yes | No | No (warns)
aarch64 | 2.43 | Yes | Yes | No (warns)
aarch64 | 2.45 | Yes | No | No (warns)
riscv64 | 2.41 | Broken** | Broken** | No (warns)
riscv64 | 2.44 | Yes | Yes | No (warns)
ppc64le | 2.41+ | Yes | Yes | Yes
m68k | 2.40 | No*** | No*** | No (warns)
*: Unresolved strong symbol can be retained with --export-dynamic, but not -fPIE -pie.
**: The linker retained unresolved symbol relocations as dynamic relocations, correctly
emitted .got/.got.plt entries and the @plt thunk; however, the function call
erroneously jumped to address 0 instead of the expected func@plt stub.
***: Unresolved strong symbol can be retained with -fPIE -pie, but with --export-dynamic.
To the best of my knowledge, the behavior described above reflects the
state of each backend as tested or inspected in the relevant binutils
versions. However, due to the evolving nature of these implementations,
some edge cases or version-specific nuances may differ slightly. I'm
happy to be corrected on any of these observations if needed.
The flag `-z dynamic-undefined-weak` is supported only by a few
backends (e.g., x86-64, ppc64), while others warn or ignore it. Even
without the flag, some backends retain weak and/or strong undefined
symbols by default, leading to divergence in behavior.
This inconsistency makes it difficult to reason about dynamic linking,
runtime symbol resolution (e.g., via LD_PRELOAD), or symbol stubbing
scenarios, especially across targets.
**Proposal**
I would like to propose centralizing the undefined symbol retention
policy for ELF targets inside `elflink.c`, and treating flags like
`-z dynamic-undefined-weak` (and a potential new flag `-z
dynamic-undefined` for strong symbols) in a uniform, target-agnostic
way.
This would involve:
- Moving the decision logic for retaining undefined symbols to shared
code (e.g., elflink.c)
- Respecting per-backend defaults via a new backend flag
- Honoring user-specified flags to override the default (without
warnings)
- Adding test coverage for undefined weak/strong symbol handling on
multiple targets
- Avoiding behavior regressions by preserving current behavior unless
the user opts in
This should not impact existing behavior unless flags are explicitly
used. The goal is to make ld.bfd more predictable and consistent across
targets, especially for projects relying on undefined symbol behavior
for runtime linking or modular design.
I’d be happy to write and send an initial patch series implementing this
logic along with backend defaults, and a basic test suite to cover key cases.
I’m also open to feedback on flag naming, scope, or alternative approaches
if preferred.
Please let me know if this direction makes sense, or if there are
concerns that I should be aware of before submitting patches.
Best regards,
Hakan Candar
More information about the Binutils
mailing list