Backtracing C Function Calls
The goal of this page is to show how to backtrace C function calls. The code isn’t portable, it was tested on a i386 computer, perhaps it won’t work on yours.
Stack’s behavior
By default (without -fomit-frame-pointer), assembly code generated by GCC for a function includes a prolog/epilog, which can be summed up to :
func:
push ebp ; save previous frame pointer
mov ebp, esp ; set the new frame pointer
; ... stuff here
mov esp, ebp ; restore position in the stack
pop ebp ; restore previous frame pointer
ret ; jump back where we were before the call
This allows a function to have its own stack with an automatic release of the allocated resources. So the following C code :
void
func(void)
{
int stuff, here;
}
int
main(int argc, char *argv[])
{
int stuff, here;
func();
return 0;
}
Will lead to the following situation :
HIGH ADDRESSES (Top of the stack)
> call main
- return address
- previous frame
- local variables of main()
> call fun
- return address
- previous frame
- local variables of frame
LOW ADDRESS
When a call to func
is made, the return address is pushed on the
stack, then a jump to the label is performed, and the function is
executed.
Back to C
What we have on the stack before each frame can be stored in the following structure :
struct frame
{
void * previous_frame;
void * return_addr;
} __attribute__((packed));
I don’t know if the packed attribute is really relevant here, but I prefer to be sure the compiler doesn’t try to optimize this with paddings. This can be seen as a LIFO list, where we can navigate between each return addresses, until we reach the main, and other stuff generated by GCC. Now, let’s dump those addresses:
void
backtrace(void)
{
int count;
struct frame * current_frame;
__asm__("movl %%ebp, %0"
: "=r" (current_frame));
count = 0;
fprintf(stderr, "backtrace");
while (current_frame->return_addr < (void *) &main)
{
fprintf(stderr, "%d\t- %p\n", count, current_frame->return_addr);
current_frame = current_frame->previous;
++count;
}
fprintf(stderr, "end of backtrace (total=%d)\n", count);
fflush(stderr);
}
I haven’t found any nicer way to stop tracing than comparing with main’s adress, that’s ugly but works well if there’s no library (as the code section of libraries may be located in higher addresses). Something interesting is that there are other calls performed before arriving to the main, if you are curious about them, you know what to do :-) .
Expected result
A call to backtrace will dump something like:
$ ./a.out
backtrace
0 - 0x1c00083c
1 - 0x1c000849
2 - 0x1c00085d
3 - 0x1c000876
end of backtrace (total=4)
What’s next?
Now to have a little diagnostic of what happened after a crash,
without launching any debugger, you can redirect the signal to that
function and use a tool which will print information about the return
address (see the manual page of addr2line
). A cool usage of this is
made in Varnish, which executes nm
at
its to retrieve its symbol addresses.