||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |
| $ there is no spoon |
| ___ ___ ___ |
| /\__\ |\__\ /\ \ |
| / | | | | | / \ \ |
| / | | | | | / /\ \ \ |
| / /| |__|__ | |__|___ _\ \ \ \ \ |
| / / |___ \__\ ____/ ____\__\ /\ \ \ \ \__\ |
| \/__/ / / / \__ / | \ \ \ \ \/__/ |
| / / / | | | \ \ \ \__\ |
| / / / | | | \ \/ / / |
| / / / | | | \ / / |
| \/__/ \|__| \/__/ |
| |
| ------------------------------------------------------------------------ |
| code/backtracing C function calls |
| by mxs, March 01 2010 22:08:28 |
| ------------------------------------------------------------------------ |
| warning: code in this article isn't portable, it was tested on a |
| i386/32 bits machine, perhaps it won't work on yours. I'd be strongly |
| interested in seeing other behaviors of this on different |
| architectures, if you have any experiences about it and time to waste, |
| feel free to contact me :-) |
| |
| I'll try to explain here how debuggers can trace the list of last |
| function calls when a program crashes (gdb backtrace). |
| |
| 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 ; same 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. Here is a quick attempt to summerize the |
| state of the stack after a call of main() followed by a call to func() : |
| |
| |
| high addrs |
| |
| [ top of the stack ] |
| [ ... ] |
| |
| ; call main(int, char **) |
| |
| [ return address ] ; pushed by call main |
| 0x1c00085d [ previous frame ] ; pushed by the prolog of main |
| ^ |
| | ; here peacily live arguments and local variables of main() |
| | |
| | [ ... ] |
| | [ ... ] |
| | |
| | ; func() |
| | |
| | [ return address ] ; pushed by call func |
| ----- [ 0x1c00085d ] ; pushed by the prolog of func |
| |
| ; here peacily live local variables of func() |
| |
| [ ... ] |
| |
| | | |
| | | |
| | | |
| \ / |
| \ / |
| v |
| |
| low addrs |
| |
| (yaw, that's ugly) |
| |
| 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. So what we have on the stack before each frame can be summed |
| up with the following structure. |
| |
| struct frame |
| { |
| void * previous_frame; |
| void * return_addr; |
| } __attribute__((packed)); |
| |
| I don't know if ((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 linked list, where we can navigate between each |
| return addresses, until we reach the main, and other stuff generated by |
| GCC. |
| |
| Now we can dump return addresses until we reach the main: |
| |
| 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 :-) |
| |
| A call to backtrace() will dump something like: |
| |
| backtrace |
| 0 - 0x1c00083c |
| 1 - 0x1c000849 |
| 2 - 0x1c00085d |
| 3 - 0x1c000876 |
| end of backtrace (total=4) |
| |
| 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 informations about the return address |
| (man addr2line for more informations). |
| |
| --- [download as html] |
| |
| +--------------------------------------------------------------------------+ |
| + - quotes/Ummon and the gardener (February 22 2010 22:46:54) + |
| + - sys/freebsd and jails (February 14 2010 19:29:43) + |
| + - randoms/buffout is out (February 12 2010 15:15:28) + |
| +--------------------------------------------------------------------------+ |
| |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||