Debugging ELF Loadable Modules

Debugging ELF modules loaded in memory can be tricky because the load address in memory does not match the addresses in the ELF file. This challenge has long existed for debugging uClinux programs and Linux kernel modules; the same solution can be used with NuttX ELF files (and probably with NxFLAT modules as well). Below is a summary of one way to approach this:

1. Get ELF Module Load Address

Put a change in nuttx/binfmt so that you print the address where the ELF text was loaded into memory.

Turning on BINFMT debug (CONFIG_DEBUG_BINFMT=y) should give you the same information, although it may also provide more output than you really want.

Alternatively, you could place a printf() at the beginning of your main() function so that your ELF module can print its own load address. For example, the difference between the address of main() in your object file and the address of main() at run time reveals the actual load address.

2. Make the ELF Module Wait for You

Insert an infinite loop in the main() routine of your ELF program. For example:

volatile bool waitforme;
int main (int arc, char **argv)
{
    while (!waitforme);
    ...

When you start the ELF program, you will see where it was loaded in memory, and the ELF program will remain stuck in the infinite loop. It will continue to wait for waitforme to become true before proceeding.

3. Start the Debugger

Start the debugger, connect to the GDB server, and halt the program. If your debugger is well-behaved, it should stop at the infinite loop in main().

4. Load Offset Symbols

Load symbols using the offset where the ELF module was loaded:

(gdb) add-symbol-file <myprogram> <load-address>

Here, <myprogram> is your ELF file containing symbols, and <load-address> is the address where the program text was actually loaded (as determined above). Single-step a couple of times and confirm that you are in the infinite loop.

5. And Debug

Set waitforme to a non-zero value. Execution should exit the infinite loop, and now you can debug the ELF program loaded into RAM in the usual way.

An Easier Way?

There might be an alternative that allows you to step into the ELF module without modifying the code to include the waitforme loop. You could place a breakpoint on the OS function task_start(). That function runs before your ELF program starts, so you should be able to single-step from the OS code directly into your loaded ELF application—no changes to the ELF application required.

When you step into the application’s main(), you have the relocated address of main() and can use that address (see step #1) to compute the load offset.