Search code examples
clinuxgdbglibc

Use a custom dynamic linker with gdb


I have built a custom version of glibc. It introduces some new symbols that I use with a custom shared library. For this I add a new version: SHIM

I use gcc -g -o my_test my_test.c -l my_so.so -Wl,-dynamic-linker=/local/home/me/glibc-build/ld-linux-x86-64.so.2 to build a test executable. It needs to use the runtime linker of my custom glibc build so it can link the custom glibc symbols used in my_so.so. This actually works.

But when I use gdb to debug the executable, I find that the default runtime linker is used. I get the following error:

/bin/bash: /lib64/ld-linux-x86-64.so.2: version `SHIM' not found (required by /local/home/me/glibc-build/libc.so.6)

How can I have gdb use my custom runtime linker? I tried to use gdb --args /local/home/me/glibc-build/ld-linux-x86-64.so.2 ./my_test, but I end up with the same error


Solution

  • I recently faced the same issue in a cross compilation environment, and could not find any solution to change the dynamic linker after compilation. This post is a bit old but still very relevant, given that I had to spend some time before figuring out a solution, so I decided to contribute with my approach. I hope it will be helpful for some people.

    My use case was a bit awkward as I needed to run a program that was compiled with a new cross-compiler, and using the associated new libgcc.so, and a custom glibc version (hence the new dynamic linker).

    Short answer:

    I passed this linker option -Wl,-dynamic-linker,custom_path_to_dynamic_linker to GCC.

    Details:

    https://sourceware.org/binutils/docs-2.42/ld/Options.html

    --dynamic-linker=file

    Set the name of the dynamic linker. This is only meaningful when generating dynamically linked ELF executables. The default dynamic linker is normally correct; don’t use this unless you know what you are doing.

    From my experience, the dynamic linker cannot be changed easily after compilation, unless you edit the ELF program header manually. You can visualize its content as follows:

    $ aarch64-none-linux-gnu-readelf -e ./a.out
    ...
    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                     0x00000000000001f8 0x00000000000001f8  R      0x8
      INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                     0x0000000000000070 0x0000000000000070  R      0x1
          [Requesting program interpreter: ${target_runtime_sys_root}/libc/lib/ld-linux-aarch64.so.1]
      LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                     0x000000000001e8b4 0x000000000001e8b4  R E    0x10000
      LOAD           0x000000000001fd30 0x000000000042fd30 0x000000000042fd30
                     0x0000000000000458 0x0000000000000470  RW     0x10000
      DYNAMIC        0x000000000001fd90 0x000000000042fd90 0x000000000042fd90
                     0x0000000000000220 0x0000000000000220  RW     0x8
      NOTE           0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
                     0x0000000000000020 0x0000000000000020  R      0x4
      GNU_EH_FRAME   0x000000000001c58c 0x000000000041c58c 0x000000000041c58c
                     0x0000000000000734 0x0000000000000734  R      0x4
      GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                     0x0000000000000000 0x0000000000000000  RW     0x10
      GNU_RELRO      0x000000000001fd30 0x000000000042fd30 0x000000000042fd30
                     0x00000000000002d0 0x00000000000002d0  R      0x1
    ...
    

    https://docs.kernel.org/userspace-api/ELF.html

    PT_INTERP

    First PT_INTERP program header is used to locate the filename of ELF interpreter. Other PT_INTERP headers are ignored (since Linux 2.4.11).

    The value of PT_INTERP usually points ld.so.1 corresponding to your platform.

    You can change its value by passing this linker option -Wl,-dynamic-linker,custom_path_to_dynamic_linker to GCC.

    Build:

    aarch64-none-linux-gnu-g++ -march=armv9.4-a -O0 -g3 -std=c++23 \
       -o ./a.out \
       test.cpp \
       -Wl,-L${cross_gcc_sys_root}/lib/gcc/aarch64-none-linux-gnu/15.0.0 \
       -Wl,-lbacktrace \
       -Wl,-dynamic-linker,${target_runtime_sys_root}/libc/lib/ld-linux-aarch64.so.1
    

    In a VM, with a recent GDB installed from the distribution's repo:

    $ gdb ./a.out
    # Set the environment variables
    (gdb) python gdb.execute("set environment LD_LIBRARY_PATH=${target_runtime_sys_root}/lib64:${target_runtime_sys_root}/libc/lib64")
    # Run the program
    (gdb) run [arg1 arg2 ...]
    # That's all! It should work now :)
    

    Note: Please ignore all the VM and cross-compilation details if you do everything on the same host :)