概述
一般情况下,编译的时候可能需要加 -g 选项,对于android ndk的-g选项添加请参见android类目下的另一篇文章。
以下文章中的__builtin_return_address() 宏,若要确认它是否在编译器源码中存在,可以在编译器源码中搜索 return_address, 若找到则可以直接使用(不同架构的编译器调用方法可能不同),而且同时也搜索到了需要包含哪些头文件。
__builtin_return_address() 宏简单好用,如果只支持层级0,则只有不断的循环用0来找调用者的调用者了。
http://codingrelic.geekhold.com/2009/05/pre-mortem-backtracing.html
A backtrace is often the first step in debugging a problem. Generating a backtrace is generally thought of as a function of the debugger, on a core file after a process has died. However it is sometimes quite useful to generate a live backtrace while a process runs. For example, crashing the process in the field may not be acceptable if a problem is survivable. Logging a backtrace and other information can provide enough to locate the root cause, without having to trigger any customer downtime.
gcc backtrace support
The simplest way to get a crude backtrace is the __builtin_return_address() macro supplied by gcc. You provide the frame number you want to retrieve, and get the return address for that stack frame:
void do_backtrace2() { void *pc0 = __builtin_return_address(0); void *pc1 = __builtin_return_address(1); void *pc2 = __builtin_return_address(2); void *pc3 = __builtin_return_address(3); printf("Frame 0: PC=%pn", pc0); printf("Frame 1: PC=%pn", pc1); printf("Frame 2: PC=%pn", pc2); printf("Frame 3: PC=%pn", pc3); }
This code will produce the following output:
Frame 0: PC=0x80483ca Frame 1: PC=0x80483e1 Frame 2: PC=0x62079d Frame 3: PC=0x80482b9
__builtin_return_address() has significant limitations. It constructs code at compile time to walk back through the stack frames. That means you cannot use a variable in a loop, you can only use integer constants like the 0,1,2,3 shown above. Also on some architectures, including my beloved MIPS, only __builtin_return_address(0) works. MIPS has no frame pointer, making it difficult to walk back up the stack. Frame 0 can use the return address register directly.
glibc's backtrace()
glibc contains a simple backtrace function, which is somewhat more powerful than __builtin_return_address(). The backtrace() call populates an array with the program counter of each calling function, while a separate backtrace_symbols() call can look up the symbolic names for each address:
#include <execinfo.h> #define BACKTRACE_SIZ 64 void do_backtrace() { void *array[BACKTRACE_SIZ]; size_t size, i; char **strings; size = backtrace(array, BACKTRACE_SIZ); strings = backtrace_symbols(array, size); for (i = 0; i < size; i++) { printf("%p : %sn", array[i], strings[i]); } free(strings); // malloced by backtrace_symbols }
The output shows the backtrace with the address of each function call site:
# gcc -g -o backtrace ./backtrace.c # ./backtrace 0x8048422 : ./backtrace(backtrace_symbols+0xd6) [0x8048422] 0x80484be : ./backtrace(backtrace_symbols+0x172) [0x80484be] 0x80484d5 : ./backtrace(backtrace_symbols+0x189) [0x80484d5] 0x071479d : /lib/tls/libc.so.6(__libc_start_main+0xed) [0x71479d] 0x804837d : ./backtrace(backtrace_symbols+0x31) [0x804837d]
To get useful symbolic names, the -rdynamic option must be passed to the linker:
# gcc -g -rdynamic -o backtrace ./backtrace.c
# ./backtrace
0x804874a : ./backtrace(do_backtrace+0x1a) [0x804874a]
0x80487e6 : ./backtrace(foo1+0xb) [0x80487e6]
0x80487fd : ./backtrace(main+0x15) [0x80487fd]
0x012679d : /lib/tls/libc.so.6(__libc_start_main+0xed) [0x12679d]
0x80486a5 : ./backtrace(backtrace_symbols+0x31) [0x80486a5]
There is also a backtrace_symbols_fd() function, which nicely prints the output to a file descriptor without having to malloc a table of strings. If thats all you're trying to do, it is a simpler API.
As an aside: notice how the address of __libc_start_main varies in the examples above, 0x62079d versus 0x71479d versus 0x12679d? That is address space randomization in action. libc is mapped at a randomized base address every time a binary is started. The offset of __libc_start_main within the page is a constant 0x79d, but the upper bits of the address will vary from one run to the next. This helps prevent buffer overflow and other code injection attacks, by randomizing the address of library routines.
libunwind
libunwind is a library for extracting call chain information. It supports many different CPU architectures. Here is an example of using libunwind to accomplish a similar result as glibc's backtrace() function:
#include <libunwind.h> void do_backtrace2() { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); while (unw_step(&cursor) > 0) { unw_word_t offset, pc; char fname[64]; unw_get_reg(&cursor, UNW_REG_IP, &pc); fname[0] = '