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

 

  1. /* 
  2.  
  3. *Author:Chaos Lee 
  4.  
  5. *Date:2012-02-26  21:35 
  6.  
  7. */ 
  8.  
  9. #include<stdio.h> 
  10.  
  11. #include<execinfo.h> 
  12.  
  13. #include<stdlib.h> 
  14.  
  15. #define MAX_LEN 256 
  16.  
  17. void show_stack_info() 
  18.  
  19.  
  20.          void *buffer[MAX_LEN]; 
  21.  
  22.          int returned_size; 
  23.  
  24.          char **strings; 
  25.  
  26.          int i=0; 
  27.  
  28.          returned_size=backtrace(buffer,MAX_LEN); 
  29.  
  30.          printf("%d addresses are returned.\n",returned_size); 
  31.  
  32.          strings=backtrace_symbols(buffer,returned_size); 
  33.  
  34.          if(strings==NULL) 
  35.  
  36.                    exit(1); 
  37.  
  38.          for(i=0;i<returned_size;i++) 
  39.  
  40.          { 
  41.  
  42.                    printf("%s\n",strings[i]); 
  43.  
  44.          } 
  45.  
  46.  
  47. void func4(int a) 
  48.  
  49.  
  50.          if(a>0) 
  51.  
  52.                    func4(--a); 
  53.  
  54.          else 
  55.  
  56.                    show_stack_info(); 
  57.  
  58.  
  59. static void func3(int a) 
  60.  
  61.  
  62.          func4(--a); 
  63.  
  64.  
  65. void func2(int a) 
  66.  
  67.  
  68.          func3(--a); 
  69.  
  70.  
  71. void func1(int a) 
  72.  
  73.  
  74.          func2(--a); 
  75.  
  76.  
  77. int main() 
  78.  
  79.  
  80.          func1(10); 
  81.  
  82.          return 0; 
  83.  

编译指令为:

  1. gcc traceback.c -o traceback –rdynamic 

然后运行之:./traceback

输出结果如下:

  1. 15 addresses are returned. 
  2.  
  3. ./traceback(show_stack_info+0x23) [0x40085b] 
  4.  
  5. ./traceback(func4+0x29) [0x4008e9] 
  6.  
  7. ./traceback(func4+0x1d) [0x4008dd] 
  8.  
  9. ./traceback(func4+0x1d) [0x4008dd] 
  10.  
  11. ./traceback(func4+0x1d) [0x4008dd] 
  12.  
  13. ./traceback(func4+0x1d) [0x4008dd] 
  14.  
  15. ./traceback(func4+0x1d) [0x4008dd] 
  16.  
  17. ./traceback(func4+0x1d) [0x4008dd] 
  18.  
  19. ./traceback(func4+0x1d) [0x4008dd] 
  20.  
  21. ./traceback [0x400902] 
  22.  
  23. ./traceback(func2+0x17) [0x40091b] 
  24.  
  25. ./traceback(func1+0x17) [0x400934] 
  26.  
  27. ./traceback(main+0xe) [0x400944] 
  28.  
  29. /lib64/libc.so.6(__libc_start_main+0xf4) [0x38ba61d8a4] 
  30.  
  31. ./traceback [0x4007a9] 

注意,func3声明为静态函数,所以不能将其符号名称解析出来。其实,backtrace函数更多的是用在信号处理函数中,这在下一篇文章再作介绍。