在程序运行的过程中,如果出现异常,通常会发出一个信号进入信号处理函数中处理。有些故障过于严重到无法实现程序的自恢复。这个时候,程序只能无奈的输出一些错误信息。当然这些错误信息对程序的调试也是非常有帮助的,我们在Java中如果出现异常的话,一般都会打印出堆栈跟踪的信息。
当然,除了打印堆栈信息外,也能在程序的某些点设置一些调试信息方便输出程序出错的行号,函数名和文件名。但是这种方式的功能毕竟是有限的,很多异常出现的位置可能并没有设置这样的调试语句。这样,还是堆栈信息比较重要。因为这是运行时输出的信息,而输出行号,文件名,函数名的方式只能在编译时确定。
堆栈跟踪主要和三个函数相关,分别为backtrace, backtrace_symbols,以及backtrace_symbols_fd.关于这三个函数的信息如下:
#include <execinfo.h>
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函数返回backtrace函数被调用时的堆栈信息。这些信息存放在缓冲区buffer中。backtrace函数有两个参数:
buffer:用于存放堆栈信息的缓冲区
size:用于指示buffer的大小。
要充分考虑到buffer的大小,因为如果函数调用的层次过深可能导致buffer空间不够,这样就只能保存一部分堆栈信息,靠近main函数这端的信息会因为空间不够而被裁掉。buffer是一个指向二维数组的指针,类型为void. buffer中所保存的是一系列堆栈帧的返回地址。traceback函数将返回堆栈中跟踪到的函数的个数。
将trace函数返回的buffer和返回的函数个数分别作为backtrace_symbols就可以解析出这些跟踪到的函数的符号名称,确切的说,backtrace_symbols函数将针对buffer中每一个函数返回地址进行解析,解析后的格式为:./程序名(<函数名+>函数的在程序中的十六进制格式偏移地址) [函数实际的返回十六进制格式的地址],解析后的结果保存为二维字符数组,返回值为二维数组的起始地址。
设返回的二维数组为strings,由于在backtrace_symbols内部调用了malloc开辟空间,所以需要用户来释放空间。不过用户只需要释放strings所指向的字符串指针数组即可,对于每个字符串不用释放,也不应该释放,内部会自动维护。
函数traceback_symbols_fd将输出堆栈信息到fd所说明的文件中。这样有一个好处就是不用调用malloc函数了,避免了内存分配失败的可能性。
然而,需要注意的是,有些函数是不能通过traceback_symbols函数解析出来的,这些情况为:
l 内联函数(没有对应的堆栈帧)
l 优化级别较高的编译选项会导致某些函数的堆栈指针不会被保存
l 尾递归优化可能导致多个函数(递归)只使用一个堆栈帧
l 静态函数的名字不能解析到
另外在链接的时候需要添加一些特殊的选项,对于GCC这个选项是 –rdynamic。下面是一个简单的程序,展示怎样使用backtrace系列函数。
/*
*Author:Chaos Lee
*Date:2012-02-26 21:35
*/
#include<stdio.h>
#include<execinfo.h>
#include<stdlib.h>
#define MAX_LEN 256
void show_stack_info()
{
void *buffer[MAX_LEN];
int returned_size;
char **strings;
int i=0;
returned_size=backtrace(buffer,MAX_LEN);
printf("%d addresses are returned.\n",returned_size);
strings=backtrace_symbols(buffer,returned_size);
if(strings==NULL)
exit(1);
for(i=0;i<returned_size;i++)
{
printf("%s\n",strings[i]);
}
}
void func4(int a)
{
if(a>0)
func4(--a);
else
show_stack_info();
}
static void func3(int a)
{
func4(--a);
}
void func2(int a)
{
func3(--a);
}
void func1(int a)
{
func2(--a);
}
int main()
{
func1(10);
return 0;
}
编译指令为:
gcc traceback.c -o traceback –rdynamic
然后运行之:./traceback
输出结果如下:
15 addresses are returned.
./traceback(show_stack_info+0x23) [0x40085b]
./traceback(func4+0x29) [0x4008e9]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback(func4+0x1d) [0x4008dd]
./traceback [0x400902]
./traceback(func2+0x17) [0x40091b]
./traceback(func1+0x17) [0x400934]
./traceback(main+0xe) [0x400944]
/lib64/libc.so.6(__libc_start_main+0xf4) [0x38ba61d8a4]
./traceback [0x4007a9]
注意,func3声明为静态函数,所以不能将其符号名称解析出来。其实,backtrace函数更多的是用在信号处理函数中,这在下一篇文章再作介绍。
©著作权归作者所有:来自51CTO博客作者hipercomer的原创作品,如需转载,请与作者联系,否则将追究法律责任
unix调试tracebackC/C++编程
共同学习,写下你的评论
评论加载中...
作者其他优质文章