-
Couldn't load subscription status.
- Fork 15k
Description
Description
When linking with lld and passing both -shared and --dynamic-linker=/lib64/ld-linux-x86-64.so.2, the resulting ELF shared object does not contain a PT_INTERP program header.
This differs from GNU ld (bfd), which does emit a PT_INTERP in the same situation.
This causes executables built as cdylib+-shared+--dynamic-linker to segfault when run directly, because the runtime loader is never invoked.
If run explicitly with the loader (/lib64/ld-linux-x86-64.so.2 ./libmylib.so), the binary executes correctly.
Steps to Reproduce
-
Minimal C file:
int main() { return 42; }
-
Link with lld:
clang -fuse-ld=lld -shared test.c \ /usr/lib/x86_64-linux-gnu/Scrt1.o \ -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2 \ -o test.so
-
Inspect program headers:
readelf -l test.so | grep INTERP→ no
INTERPsegment is present. -
Run:
./test.so
→ segfault (jump to 0).
-
Run with loader explicitly:
/lib64/ld-linux-x86-64.so.2 ./test.so
→ works, returns exit code 42.
-
Now repeat with GNU ld (bfd):
clang -fuse-ld=bfd -shared test.c \ /usr/lib/x86_64-linux-gnu/Scrt1.o \ -Wl,-pie \ -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2 \ -o test_bfd.so readelf -l test_bfd.so | grep INTERP→
INTERPis present, and./test_bfd.soruns fine.
Analysis
Looking at lld source:
-
needsInterpSectioninlld/ELF/SyntheticSections.cpp:static bool needsInterpSection(Ctx &ctx) { return !ctx.arg.relocatable && !ctx.arg.shared && !ctx.arg.dynamicLinker.empty() && ctx.script->needsInterpSection(); }
→ Explicitly excludes the
.interpsection when-sharedis passed. -
Writer<ELFT>::createPhdrsinlld/ELF/Writer.cpp:if (OutputSection *cmd = findSection(ctx, ".interp", partNo)) addHdr(PT_INTERP, cmd->getPhdrFlags())->add(cmd);
→ Since
.interpis never created in-sharedmode,PT_INTERPnever gets emitted.
This is the direct cause: lld refuses to emit PT_INTERP for -shared binaries, even if --dynamic-linker is given.
Expected Behavior
When passing -shared and --dynamic-linker=..., lld should:
- Emit
.interpsection - Emit a
PT_INTERPprogram header
so the shared object is executable directly via the kernel, matching GNU ld (bfd).
Actual Behavior
.interpandPT_INTERPare not emitted by lld.- Running the
.sodirectly segfaults, because the loader is never invoked.
Impact
- Rust issue for reference: rust-lang/rust#146780
Environment
- Rust 1.90.0 stable (where
lldbecame default forx86_64-unknown-linux-gnu) - LLVM/lld master (current behavior still the same)
- Reproduced on Ubuntu 22.04 (glibc-based)
Suggested Fix
Loosen the condition in needsInterpSection() so that .interp can be emitted if --dynamic-linker is provided, even under -shared. This matches bfd behavior and would fix the segfault.