1、backtrace的用处
一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。主要用于程序异常退出时寻找错误原因。
通常情况下,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数。我们可以使用 signal(SIGSEGV, &your_function);函数来接管SIGSEGV信号的处理,程序在发生段错误后,自动调用我们准备好的函数,从而在那个函数里来获取当前函数调用栈。
2、提供的接口
int backtrace(void buffer, int size);
char backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
backtrace_symbols_fd()的buffer和size参数和backtrace_symbols()函数相同,只是它翻译后的函数回溯信息不是放到返回值中,而是一行一行的放到文件描述符fd对应的文件中。
注意:
- backtrace的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On参数)或加入了栈指针优化参数-fomit-frame-pointer后多将不能正确得到程序栈信息;加上 -g 选项以后创建符号表,关闭所有的优化机制。
- 在编译的时候需要加上-rdynamic选项让链接器将所有符号添加到动态符号表中,这样才能将函数地址翻译成函数名。另外,这个选项不会处理static函数,所以,static函数的符号无法得到。
- 内联函数没有栈帧,它在编译过程中被展开在调用的位置;
- 尾调用优化(Tail-call Optimization)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。
所以编译方法应该这样: $ gcc -g *.c -rdynamic -rdynamic需要放后面,不知道为什么?
3、示例:生成backtrace文件
/ add.c */ #include
#include
#include
int add1(int num) { int ret = 0x00; int *pTemp = NULL; *pTemp = 0x01; /* 这将导致一个段错误,致使程序崩溃退出 */ ret = num + *pTemp; return ret; } int add(int num) { int ret = 0x00; ret = add1(num); return ret; }
/ dump.c */ #include
#include
#include
#include
/* for signal */ #include
/* for backtrace() */ #define BACKTRACE_SIZE 16 void dump(void) { int j, nptrs; void *buffer[BACKTRACE_SIZE]; char strings; nptrs = backtrace(buffer, BACKTRACE_SIZE); printf("backtrace() returned %d addresses\n", nptrs); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { perror("backtrace_symbols"); exit(EXIT_FAILURE); } for (j = 0; j < nptrs; j++) printf(" [%02d] %s\n", j, strings[j]); free(strings); } void signal_handler(int signo) { #if 0 char buff[64] = {0x00}; sprintf(buff,"cat /proc/%d/maps", getpid()); system((const char*) buff); #endif printf("\n=========>>>catch signal %d <<<=========\n", signo); printf("Dump stack start...\n"); dump(); printf("Dump stack end...\n"); signal(signo, SIG_DFL); /* 恢复信号默认处理 */ raise(signo); /* 重新发送信号 */ }
/ backtrace.c */ #include
#include
#include
#include
/* for signal */ #include
/* for backtrace() */ extern void dump(void); extern void signal_handler(int signo); extern int add(int num); int main(int argc, char *argv[]) { int sum = 0x00; signal(SIGSEGV, signal_handler); /* 为SIGSEGV信号安装新的处理函数 */ sum = add(sum); printf(" sum = %d \n", sum); return 0x00; }
输出如下:
4、backtrace文件分析方法
由此可见在调用完函数add1后就开始调用段错误信号处理函数了,所以问题是出在函数add1中。这似乎还不够,更准确的位置应该是在地址0x563ef7e07267处,这好像是一个虚拟地址,addr2line需要的估计是错误信息在elf文件中的地址,所以还需要减去文件的加载地址

但这到底是哪一行呢,我们使用addr2line命令来得到,执行如下:

我们可以通过 /proc 观察到一个正在运行着的Linux系统的内核数据信息以及各进程相关的信息。所以我们如果要查看某一个进程的内存空间情况,也可以通过它来进行。
也可以用gcc编译生成map文件分析错误的位置。立个flag方便以后查找,不写了。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212985.html原文链接:https://javaforall.net
