I’m working on a project where one program loads an ELF file at runtime and then transfers control to that dynamically loaded file. The file will be loaded at an address obtained by malloc(3), so the address will be arbitrary (from a user perspective).
Normally, if a crash occurs while a program is being run under a debugger, the debugger automatically shows you where in the source code the crash occurred. This works only if the debugged program includes debug information. That debug information is stored in the executable file itself. It is ignored by the operating system, but the debugger can use it to resolve addresses to symbols, i.e. to variable and function names.
In my case, if a crash is caused by the dynamically loaded file, then all I see is the register contents and maybe a plain stack trace. That’s because the debugger does not know where to find the debug information. Seeing the addresses is useful as it can be used to look up the problematic instruction with objdump. However, debugging would be a lot easier if the addresses could be automatically resolved to symbols by the debugger.
The GNU Debugger (gdb), my debugger of choice, has a add-symbol-file command that instructs the debugger to load symbol information from a file on disk. This command can be issued before the program to be debugged is started, while it’s running or even after it has crashed. There are two parameters required by the command. The first one will tell it where to load the debug information from, i.e. the path to the ELF file on disk. The other one will tell it at what address the ELF .text section of the ELF file has been loaded. So the syntax is:
(gdb) add-symbol-file file.elf <address>
For my project, I’m expecting frequent crashes in the dynamically loaded program. So in order to make debugging less time consuming, I created a little .gdbinit file in my home directory that does these things:
- Tell gdb which program is being debugged. This is the program that will load another one at runtime.
- Set a breakpoint in that first stage program. The breakpoint needs to be somewhere where the address of the .text section of the dynamically loaded file can be determined.
- Then start the program.
- When the breakpoint is hit, print the address of the .text section of the dynamically loaded ELF file.
My .gdbinit file that does the above looks like this:
file primary_stage break jump_to_second_stage run p/x entry_point
Now anytime the debugger is started, it will automatically load the primary stage ELF file, set a breakpoint and start the program. As soon as the breakpoint is hit, the address of the .text section will be printed. Since that’s the first print instruction in the debug session, it can now be accessed through the use of a placeholder: $1.
Unfortunately, automatically loading the second stage ELF program from the .gdbinit file does not work. So I then need to manually enter this command:
(gdb) add-symbol-file second_stage $1
Well, it turns out that you can add the above command to the .gdbinit and it will work just fine. I have no idea why that did not work the last time I tried it.
Now, a crash in the second_stage program will be reported with full symbol information available to the debugger.