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.

