Search code examples
linuxassemblyx86-64ldmusl

Why doesn't `ld -L -l...` match the behavior of `ld /path/to/library.so`?


I have two questions highlighted below. I'm using 64-bit Linux.

I saw on another post that MUSL was mentioned as a libc implementation.

I tried using this with the following Hello world assembly program that uses two libc functions, write and _exit.

  .data
hello:
  .ascii "Hello, world\n"

  .text
  .globl _start
_start:
  movl $13, %edx
  movl $hello, %esi
  movl $1, %edi
  call write

  movl $0, %edi
  call _exit

I assembled the code with:

# Command 1
$ as -o hello.o hello.s

I then ran ld to generate an executable that statically links MUSL libc.

# Command 2
$ ld hello.o /usr/lib/x86_64-linux-musl/libc.a 

That generated an a.out file that works as expected, outputting "Hello, world" when executed.

I also tried a different invocation of the preceding ld command, using -static -lc instead of specifying the path directly, and also using -L to give the path to MUSL so that glibc is not used, since the latter is already on ld's search path.

# Command 3
$ ld hello.o -L/usr/lib/x86_64-linux-musl/ -static -lc

That worked as expected.

Next I tried to dynamically link MUSL libc.

# Command 4
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 hello.o \
     /usr/lib/x86_64-linux-musl/libc.so

That appears to work as expected. I can run a.out, and calling ldd on a.out shows that MUSL's libc is linked.

Lastly, I tried an analogous modification relative to the statically linked version earlier, using -lc and -L instead of specifying the path to the .so file directly.

# Command 5
$ ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 hello.o \
     -L/usr/lib/x86_64-linux-musl -lc

The program does not run properly, outputting the error:

bash: ./a.out: No such file or directory

When I run that same ld command with the --verbose flag, the output is the same as when passing --verbose to the earlier ld command (Command 4 that generated a working executable).

Running ldd on a.out also outputs an error:

./a.out: error while loading shared libraries: /usr/lib/x86_64-linux-gnu/libc.so: invalid ELF header

Question 1: Why does calling ld with -L and -lc in this case not match the behavior from earlier, where I specified the .so file directly?

I noticed that if I change the specified dynamic linker to /lib/ld-musl-x86_64.so.1, the generated a.out runs as expected.

# Command 6
$ ld -dynamic-linker /lib/ld-musl-x86_64.so.1 hello.o \
     -L/usr/lib/x86_64-linux-musl -lc

However, calling ldd on the generated a.out produces the following error, which was not the case earlier when I did not use -lc and -L in Command 4:

./a.out: error while loading shared libraries: /usr/lib/x86_64-linux-gnu/libc.so: invalid ELF header

Question 2: Why does ld fail on this binary, but worked earlier when I passed the path of the .so file to ldd and used a different dynamic linker?


Solution

  • The problem I encountered was due to using -L with the linker, but not having that path available for loading libc.so at runtime.

    I noticed this by calling readelf --dynamic --program-headers on the programs generated by Command 4 and Command 5.

     # Command 4
     0x0000000000000001 (NEEDED) Shared library: [/usr/lib/x86_64-linux-musl/libc.so]
     # Command 5
     0x0000000000000001 (NEEDED) Shared library: [libc.so]
    

    I was able to resolve the issue for the program generated by Command 5 by using an environment variable, LD_LIBRARY_PATH=/usr/lib/x86_64-linux-musl, when running the program, or alternatively by passing an extra argument to ld, -rpath /usr/lib/x86_64-linux-musl.