0 引言
在调试应用程序时,可能因各种原因导致运行发生段错误。因此,有必要将堆栈调用信息打印出来,方便定位错误。
1 方法
在glibc头文件execinfo.h中提供了三个函数获取当前线程的函数调用堆栈。
0) int backtrace(void **buffer, intsize)
该函数将获取的堆栈信息保存至buffer中,参数size则是表示buffer大小
1) char** backtrace_symbols(void * const *buffer, int size)
backtrace_symbols将从backtrace函数获取的信息转换一个字符串数组,参数buffer是从backtrace函数获取的指针数组。函数返回值是一个指向字符串数组的指针,它的大小同buffer相同,每个字符串包含了一个相对于buffer中对应元素的可打印的信息,包括函数名,函数偏移地址和实际返回地址。很可惜的是只有使用ELF二进制格式的程序才能获取函数名和函数偏移地址。其它格式文件只能返回实际返回地址。
注意:某些编译器的优化选项对获取正确的调用堆栈有干扰(如果源代码编译时使用了-O1或-O2优化选项,可执行代码会把ebp/rbp/rsp寄存器当作普通寄存器使用,导致backtrace失败。为了防止这种情况发生,可以在编译时使用-O2 –fno-omit-frame-pointer或-Og来避免优化中使用上述寄存器。),另外内联函数没有堆栈框架。另外,bactrace_symbols与backtrace不同,返回值需要手动free,即malloc是在函数内部实现中,但free交由调用者执行。
程序举例如下;
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <execinfo.h> #include <signal.h>
void dump(int signo) { void *buffer[30] = { 0 }; size_t size; char **strings = NULL; size_t i = 0;
size = backtrace(buffer, 30); fprintf(stdout, "Obtained %zd stack frames.nm\n", size); strings = backtrace_symbols(buffer, size); if (strings == NULL) { perror("backtrace_symbols."); exit(EXIT_FAILURE); }
for (i = 0; i < size; i++) { fprintf(stdout, "%s\n", strings[i]); } free(strings); strings = NULL; exit(0); }
void func_c() { *((volatile char *)0x0) = 0x9999; }
void func_b() { func_c(); }
void func_a() { func_b(); }
int main(int argc, const char *argv[]) { if (signal(SIGSEGV, dump) == SIG_ERR) perror("can't catch SIGSEGV"); func_a(); return 0; } |
2 addr2line工具
正如上所述,通常情况我们并不能拿到函数名,而是返回的地址。庆幸的是,gcc工具链中提供了一个将返回地址映射到函数文件的具体位置的工具----addr2line工具。
具体使用方法也比较简单,-e 可执行程序文件名,用于读取符号信息(因此要求输入的文件是debug文件)。
3 其它方法
即基于BSP寄存器,手动解析堆栈调用关系。可以参考 http://blog.csdn.net/littlefang/article/details/42295803和http://blog.chinaunix.net/uid-24774106-id-3457205.html文章。