Lld weak undefined symbols in vdso only

Hi all,

I’ve been playing with osdev and come across a change in behaviour from llvm-19 to llvm-20 that I’m not sure about.

It regards weak undefined symbols in elf on x86. There has been a change in elf/Writer.cpp line 288:

bool maybePreemptible = ctx.sharedFiles.size() || ctx.arg.shared;

Now my situation is likely very unique, I’m linking a pie and static libc that contains weak symbols that are resolved against a kernel provided vdso.

Hence ctx.sharedFiles.size() is zero and so I don’t get the relevant plt entries.

Am I doing something that just shouldn’t be supported ? or is a small tweak in lld warranted ?

(at present gnu ld and lld-19 work and generates the plt)

Cheers
-Peter

I am afraid your project is relying on a behavior that is not guaranteed. You’ll need to make maybePreemptible true to generate a PLT.

LLD had some inconsistency and I’ve refactored the code for version 20

The purpose of Symbol::includeInDynsym was somewhat ambiguous, as it was used both to determine if a symbol should be exported to .dynsym and to conservatively suppress transformations in other contexts like MarkLive and ICF. LLD 20 clarifies this by introducing Symbol::isExported specifically for indicating whether a defined symbol should be exported. All previous uses of Symbol::includeInDynsym have been updated to use Symbol::isExported instead. The old confusing Symbol::exportDynamic has been removed.

A special case within Symbol::includeInDynsym checked for isUndefWeak() && ctx.arg.noDynamicLinker. (This could be generalized to isUndefined() && ctx.arg.noDynamicLinker, as non-weak undefined symbols led to errors.) This condition ensures that undefined symbols are not included in .dynsym for statically linked ET_DYN executables (created with clang -static-pie).

This condition has been generalized in LLD 20 to (ctx.arg.shared || !ctx.sharedFiles.empty()) && (sym->isUndefined() || sym->isExported). This means undefined symbols are excluded from .dynsym in both ld.lld -pie a.o and ld.lld -pie --no-dynamic-linker a.o, but not ld.lld -pie a.o b.so. This change brings LLD’s behavior more in line with GNU ld.

Symbol::isPreemptible, indicating whether a symbol could be bound to another component, was calculated before relocation scanning and, in LLD 19, also during Identical Code Folding (ICF). In LLD 20, the ICF-related calculation has been moved to the symbol versioning parsing stage.

I can see that the changes are appropriate for the static-pie situation.

However, surely when you have a dynamic linker, even in the absence of shared libraries, preemption should be supported.

Looking around the tubes a little I see that there is a consensus that the gnu ld behaviour is somewhat inconsistent.

However, there are many cases when that behaviour (old llvm behaviour) is desired, mostly around using LD_PRELOAD to override weak symbols in pie executables.

I note specifically the discussions around the mold linker, which also doesn’t generate the plt. (eg. Weak symbols in executable are always resolved to 0 during linking · Issue #1401 · rui314/mold · GitHub)

The solution there is a linker flag -z dynamic-undefined-weak. This seems most reasonable and perhaps something lld would consider emulating.

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.
    This is probably what your project needs.
  • 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.

nice