In previous c/c++: call stack article I wrote about obtaining function call stack. The method described in the article is good enough but it is linux-specific. There's no similar solutions out-of-box in other *nix-like operation systems as far as I know.
This time I would like to discuss more generic way. At list it is available for the code compiled with gcc.
gcc provides two built-in functions which could be used to obtain function call stack: __builtin_return_address and __builtin_frame_address. With __builtin_return_address it's possible to obtain return address of the current function, or of one of its callers. __builtin_frame_address returns the address of the function frame.
Both functions require constant argument - the number of frames to scan up.
To store function call struct call structure is used defined as following:
struct call { const void *address; const char *function; const char *object; };
With __builtin_frame_address it is checked if the top of the stack has been reached - in this case aforementioned built-in returns zero. In the loop the return value of this function is compared with zero and the loop is terminated if this expression in compare statement turns into true - the top of the call stack has been reached.
Finally __builtin_return_address is used to get the return address of the function.
The resulting function to get the backtrace looks like:
#define _GNU_SOURCE #include <dlfcn.h> int backtrace(struct call trace[], int maxlen) { Dl_info dlinfo; unsigned int i; for (i=0;i<maxlen;++i) { switch (i) { case 0: if(!__builtin_frame_address(0)) return i; trace[i].address = __builtin_return_address(0); break; case 1: if(!__builtin_frame_address(1)) return i; trace[i].address = __builtin_return_address(1); break; case 2: if(!__builtin_frame_address(2)) return i; trace[i].address = __builtin_return_address(2); break; /* SNIP */ /* .... */ /* SNIP */ case 63: if(!__builtin_frame_address(63)) return i; trace[i].address = __builtin_return_address(63); break; default: return i; } if (dladdr(trace[i].address, &dlinfo) != 0) { trace[i].function = dlinfo.dli_sname; trace[i].object = dlinfo.dli_fname; } } return i; }backtrace routine fills trace array with the call stack information and returns the depth of the function calls.
Following small example shows backtrace in action:
#include <stdio.h> #define CALLSTACK_MAXLEN 64 void f0() { struct call trace[CALLSTACK_MAXLEN]; int i; int depth; depth = backtrace(trace, CALLSTACK_MAXLEN); for (i=0;i<depth;++i) printf("%s: %s(%p)\n", trace[i].object, trace[i].function, trace[i].address); } void f1() { f0(); } void f2() { f1(); } void f3() { f2(); } void f4() { f3(); } int main(int argc, char **argv) { f4(); return 0; }Again the application should be compiled with -rdynamic gcc flag needed for dladdr function and linked with dl library for the same purpose:
gcc backtrace.c -o backtrace -ldl -rdynamicAfter execution the program should provide the following output:
./backtrace ./backtrace: f0(0x804b11c) ./backtrace: f1(0x804b1ac) ./backtrace: f2(0x804b1b9) ./backtrace: f3(0x804b1c6) ./backtrace: f4(0x804b1d3) ./backtrace: main(0x804b1e0) /lib/libc.so.6: __libc_start_main(0x6ff58a9e)Though it's gcc-specific method this compiler is available for most platforms and operation systems and is used almost everywhere as a default one.
No comments:
Post a Comment