I am writing a custom dynamic loader, and one curious thing is when I try to run GDB on an executable that specifies my dynamic loader in the .interp section, I can't seem to be able to set a breakpoint in my dynamic loader code. The process roughly looks like this:
$ gdb ./a
Reading symbols from ./a...
(gdb) b ld_stage1_entry
Function "ld_stage1_entry" not defined.
Make breakpoint pending on future shared library load? (y or [n])
The dynamic loader is compiled with -g
flag, and when ran normally it successfully performs it's operation.
I'm assuming the issue here is that GDB somehow either not reading the symbols from the dynamic loader (after all I'm saying that I'm debugging the ./a
executable). My question is, is there any way to tell GDB to look for dynamic loader symbols when debugging, or at least, have some way of stepping through the dynamic loader code?
I know one way I could debug my dynamic loader is to run it as an executable, passing the program to run as a parameter, like so:
gdb ./ld.so ./a
But with that process a few of the early-stages details are different and I would ideally like to have a way to debug it would be run by linux from .interp
section.
EDIT: You could try reproducing it with glibc, but glibc doesn't provide debug symbols for it's dynamic loader anyway so I wouldn't expect anyone to be able to debug it. To reproduce you can clone my dynamic loader from github and compile an example with the following command:
./build.py --ld -t tests/hello.c
The location of the dynamic loader is in the lib
folder. The ./a
file should be in ciabatta
folder.
I'm assuming the issue here is that GDB somehow either not reading the symbols
Kind of. The issue is that GDB doesn't know about any dynamically loaded code -- it gets notified by the dynamic loader itself about such code (and where in memory that code is loaded, which file it's loaded from, etc.).
What you want to do is use starti
command, which should stop on the first user-space instruction. For a dynamically-linked program, that would be the start
symbol in the dynamic loader. From there you should be able to set any breakpoints you wish.
P.S. ./build.py --ld -t tests/hello.c
fails on Fedora 37 with:
/usr/bin/ld: /tmp/loader-self-reloc-47a291.o: relocation R_X86_64_32S against `.rodata.str1.16' can not be used when making a shared object; recompile with -fPIC
The following fixes were required to make it build:
diff --git a/build.py b/build.py
index 7c30718..bb4ffbf 100755
--- a/build.py
+++ b/build.py
@@ -88,7 +88,7 @@ def compile_obj(output, inputs, additional_flags=[]):
compile(output, inputs, ['-c -fpic'] + additional_flags)
def compile_exe(output, inputs, additional_flags=[]):
- compile(output, inputs, ['-pie'] + additional_flags)
+ compile(output, inputs, ['-pie', '-fPIE'] + additional_flags)
def compile_shared(output, inputs, additional_flags=[]):
compile(output, inputs, ['-shared'] + additional_flags)
@@ -134,6 +134,7 @@ cc_includes.append('include/linux')
cc_flags_push()
cc_defines_push()
cc_flags.extend([
+ '-fPIC',
'-fno-stack-protector',
'-fno-builtin',
'-Wl,-e,_dlstart',
With above fixes, GDB session looks like this:
(gdb) starti
Starting program: /home/ciabatta/a
warning: Unable to find dynamic linker breakpoint function.
Program stopped.
0x00007ffff7ff70c0 in _dlstart () from lib/ld-cia.so
(gdb) b ld_stage1_entry
Breakpoint 1 at 0x7ffff7ff70f3: file loader/loader-self-reloc.c, line 30.
(gdb) c
Continuing.
Breakpoint 1, ld_stage1_entry (sp=0x7fffffffdea0, dynv=0x7ffff7ffded0) at loader/loader-self-reloc.c:30
30 _dbg_print_string("Entered dynamic loader\n");
(gdb)