如下函数可以在任意函数中打印出当前的调用堆栈
输出到标准输出设备,一般就是命令行了
需要注意的是必须包含下面的标准库头文件,因为man backtrace,它的声明就在这个头文件中
#include
下面简单介绍一下工作原理
主要基本2个调用来实现的:
首先是命令行的addr2line。他可以把一个可执行文件中的函数地址转换为源代码的位置以及当前函数名
要实现这个还有个前提,必须编译调试版本,应此在Makefile里面CFLAGS必须有-rdynamic -g 编译选项
其次,调用backtrace 获得当前函数的指针
最后通过popen打开一个标准输出的文件句柄,把addr2line 带上正确的参数发送过去,标准输出会等待继续函数地址,就会连续的打印函数地址对应的源代码信息
在backtrace获得一个函数地址数组以后,循环执行fprintf向标准输出写入这些函数地址,注意,每个指针后面要跟上\r\n作为结束符才能正确执行,否则看不到正确的输出
下面是完整的代码:
void print_trace(void) {
int i;
int MAX_CALLSTACK_DEPTH = 32;
void *traceback[MAX_CALLSTACK_DEPTH];
char cmd[512] = "addr2line -f -e ";
char *prog = cmd + strlen(cmd);
int r = readlink("/proc/self/exe",prog,sizeof(cmd)-(prog-cmd)-1);
/*printf("%s\n",cmd);*/
FILE *fp = popen(cmd, "w");
int depth = backtrace(traceback, MAX_CALLSTACK_DEPTH);
for (i = 0; i < depth; i++)
{
/*printf("%p\n",traceback[i]);*/
fprintf(fp, "%p\n\r", traceback[i]);
}
fclose(fp);
}
如果是后台程序,输出没有定向到终端,那么可以用backtrace_symbols函数,把调用栈信息写到文件里面,上面的函数可以这样改一下
void print_trace(char *filename)
{
int i = 0;
int len = strlen(filename) + 1;
char *name_t = (char *)malloc(len + 1);
memset(name_t,0,len);
strcpy(name_t,filename);
const int MAX_CALLSTACK_DEPTH = 32;
void *traceback[MAX_CALLSTACK_DEPTH];
char **stackinfo_string;
printf("%s\n",name_t);
char *temp = strrchr(name_t,'/');
temp ++;
for (;*temp != 0;temp++){
putchar(*temp);
*temp = 0;
}
strcat(name_t,"trace_info.log");
printf("%s\n",name_t);
int fd = open((const char *)name_t,O_CREAT|O_WRONLY|O_APPEND,0);
int depth = backtrace(traceback, MAX_CALLSTACK_DEPTH);
stackinfo_string = backtrace_symbols(traceback,depth);
for(i =0 ; i < depth ;i++) {
write(fd,stackinfo_string[i],strlen(stackinfo_string[i]));
write(fd,"\r\n",2);
}
close(fd);
free(stackinfo_string);
}
backtrace_symbols的不足之处是无法解析静态函数的符号信息,这点在使用中需要考虑清楚