David Park

lld and GNU ld can pull different archive members

· linker, toolchain

I assumed two linkers fed the same archives in the same order would produce the same binary. Debugging a build divergence, I found out they don’t — ld.lld (LLVM’s linker) pulled an object out of one archive, ld.bfd (the GNU linker, what most people mean by “GNU ld”) pulled a same-named object out of a different one. Same sources, same link line.

Both archives defined the same symbol with different bodies — that’s the underlying bug. It only stayed hidden because each linker happened to pick a different one.

  • GNU ld searches each archive once, where it sits on the link line, and only pulls members for symbols that are already undefined at that point. A reference that shows up later never sends it back to an earlier archive (short of --start-group / --end-group).
  • lld remembers every archive symbol it has seen, so a later reference can pull a member out of an archive that came earlier.
  • So with main.o liba.a use.o libb.a, where use.o calls which() and both archives define it, GNU ld (and gold) take which from libb.a, and lld takes it from liba.a. Neither linker is broken; they just follow different rules for archives.

Haven’t confirmed that’s what bit me, but it’s the best-known way the two disagree. To see which member each linker took, pass -Wl,-y,which (--trace-symbol) to both links. lld also has --why-extract=-, and GNU ld’s map file (-Wl,-Map=out.map) lists each pulled member under “Archive member included to satisfy reference by file (symbol)”. lld’s --warn-backrefs doesn’t help here: it warns when GNU ld would fail to link, and stays quiet when GNU ld would just pick another member.

For now: don’t ship same-name symbols with different bodies across archives and expect the linker to disambiguate them. Which one wins is an implementation detail, and switching linkers (or versions) can flip it silently.