[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