Issue 160059
Summary `lld` does not emit `PT_INTERP` when using `--dynamic-linker` with `-shared` (works with GNU ld/bfd)
Labels lld
Assignees
Reporter 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:

   ```c
   int main() { return 42; }
   ```

2. Link with lld:

   ```bash
   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:

   ```bash
   readelf -l test.so | grep INTERP
   ```

   → no `INTERP` segment is present.

4. Run:

   ```bash
   ./test.so
   ```

   → segfault (jump to 0).

5. Run with loader explicitly:

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

   → works, returns exit code 42.

6. Now repeat with GNU ld (bfd):

   ```bash
   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`:

  ```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`:

  ```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

* Rust issue for reference: [rust-lang/rust#146780](https://github.com/rust-lang/rust/issues/146780)

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





_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to