Skip to content

lld does not emit PT_INTERP when using --dynamic-linker with -shared (works with GNU ld/bfd) #160059

@izissise

Description

@izissise

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

  1. Minimal C file:

    int main() { return 42; }
  2. 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
  3. Inspect program headers:

    readelf -l test.so | grep INTERP

    → no INTERP segment is present.

  4. Run:

    ./test.so

    → segfault (jump to 0).

  5. Run with loader explicitly:

    /lib64/ld-linux-x86-64.so.2 ./test.so

    → works, returns exit code 42.

  6. 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

    INTERP is present, and ./test_bfd.so runs fine.

Analysis

Looking at lld source:

  • needsInterpSection in lld/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 .interp section when -shared is passed.

  • Writer<ELFT>::createPhdrs in lld/ELF/Writer.cpp:

    if (OutputSection *cmd = findSection(ctx, ".interp", partNo))
        addHdr(PT_INTERP, cmd->getPhdrFlags())->add(cmd);

    → Since .interp is never created in -shared mode, PT_INTERP never 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 .interp section
  • Emit a PT_INTERP program header
    so the shared object is executable directly via the kernel, matching GNU ld (bfd).

Actual Behavior

  • .interp and PT_INTERP are not emitted by lld.
  • Running the .so directly segfaults, because the loader is never invoked.

Impact

Environment

  • Rust 1.90.0 stable (where lld became default for x86_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    lld:ELFquestionA question, not bug report. Check out https://llvm.org/docs/GettingInvolved.html instead!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions