[RFC] Standardizing undefined symbol relocation retention in ELF ld.bfd
Fangrui Song
maskray@sourceware.org
Wed Jul 2 04:20:19 GMT 2025
On Tue, Jul 1, 2025 at 2:11 PM Hakan Candar <hakancandar@protonmail.com> wrote:
>
> Hi all,
>
> Just to clarify - the key issue here is whether unresolved symbols are being retained as dynamic relocations at link time, enabling them to be resolved at runtime (e.g., via LD_PRELOAD or shared libraries). This impacts use cases like symbol interposition and modular runtime loading, and my goal is to make this behavior more consistent and controllable across backends.
>
> Best regards,
> Hakan Candar
>
>
> -------- Original Message --------
> On 01/07/2025 23:52, Hakan Candar <hakancandar@protonmail.com> wrote:
>
> > 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
Thanks for the table. Could you share the command used for testing?
I explored the behavior when writing
https://maskray.me/blog/2021-04-25-weak-symbol and recently revisited
it, confirming the inconsistency.
For LLD I recently added -z [no]dynamic-undefined-weak for all
supported targets.
* 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
Dynamic: -shared is specified, or there is at least one link-time shared object.
(The -no-pie (!ctx.arg.isPic in isStaticLinkTimeConstant) behavior
will likely change in the future.)
LLD treats undefined non-weak symbols the same as undefined weak
symbols. If we ever add -z dynamic-undefined, it will be an alias for
-z dynamic-undefined-weak.
More information about the Binutils
mailing list