相关文章推荐

许多高级程序语言都提供了出错时候的调用栈打印功能,以方便尽快得到基本的出错信息,比如Java的runtime异常栈打印和Python的pdb库都提供了详细到行号的运行时信息以便调试。作为接近系统底层的高级语言,C/C++中要达到类似的功能却是很麻烦的,因为程序中的符号信息可能被strip,甚至编译器在优化阶段也会内嵌部分函数实现代码;一旦出现内存错误或者其它异常,所能借助的手段只有产生错误现场,事后拿到coredump来验尸了。

GNU/Linux glibc提供了 backtrace 系列API可以方便地在运行时取得栈信息。

#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, fd);
  • 第一个API根据用户传入的buffer数组(长度为size)将当前的调用栈返回,数组中每一个元素为一个函数调用地址,如果数组长度小于当前的栈帧数,那么只返回最近的调用
  • 第二个API接受第一个函数返回的buffer和size,将每一个函数地址通过符号表进行翻译,生成一个包含函数名,内部指令偏移和实际返回地址的字符串,并返回一个malloc出来的字符串数组,用户需要手工释放数组本身,但是数组的每一个指针本身则不必被用户释放
  • 在某些特殊的情况下,用户可能不能安全的申请内存(比如在signal处理函数中进行malloc就是不安全的),这时可以用第三个API,传入一个打开的fd,这样每一个栈帧都被写入到给定的fd中
  • 打印coredump时候的栈

    当程序执行到非法指令的时候或者访问非法内存的时候(更完整的情况可参考man -s 7 signal),系统会产生一个对应的signal,而某些signal的默认操作就是产生一个coredump;出于诊断的目地我们也可以为这些signal注册一个处理函数,在这个处理函数中打印出当前的函数调用栈帧以便快速诊断和定位;同时人就抛出原来的signal,以方便后期调查问题根源。

    例如下边的程序:

     
    推荐文章