手记

在unix系统中实现堆栈跟踪


 在程序运行的过程中,如果出现异常,通常会发出一个信号进入信号处理函数中处理。有些故障过于严重到无法实现程序的自恢复。这个时候,程序只能无奈的输出一些错误信息。当然这些错误信息对程序的调试也是非常有帮助的,我们在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++编程


0人推荐
随时随地看视频
慕课网APP