一、导读
在程序调试过程中如果遇到程序崩溃死机的情况下我们通常多是通过出问题时的栈信息来找到出错的地方,这一点我们在调试一些高级编程语言程序的时候会深有体会,它们通常在出问题时会主动把出问题时的调用栈信息打印出来,比如我们在eclipse中调试java程序时。
当这些换到Linux上的C/C++环境时情况将变的稍微复杂一些,通常在这种情况下是通过拿到出问题时产生的core文件然后再利用gdb调试来看到出错时的程序栈信息,这是再好不过的了,但当某些特殊的情况如不正确的系统设置或文件系统出现问题时导致我们没有拿到core文件那我们还有补救的办法吗?
本文将介绍在程序中安排当出现崩溃退出时把当前调用栈通过终端打印出来并定位问题的方法。
二、输出程序的调用栈
1、获取程序的调用栈
在Linux上的C/C++编程环境下,我们可以通过如下三个函数来获取程序的调用栈信息。
#include <execinfo.h>
/* Store up to SIZE return address of the current program state in
ARRAY and return the exact number of values stored. */
int backtrace(void **array, int size);
/* Return names of functions from the backtrace list in ARRAY in a newly
malloc()ed memory block. */
char **backtrace_symbols(void *const *array, int size);
/* This function is similar to backtrace_symbols() but it writes the result
immediately to a file. */
void backtrace_symbols_fd(void *const *array, int size, int fd);
使用它们的时候有一下几点需要我们注意的地方:
backtrace
的实现依赖于栈指针(fp
寄存器),在gcc
编译过程中任何非零的优化等级(-On
参数)或加入了栈指针优化参数-fomit-frame-pointer
后多将不能正确得到程序栈信息;backtrace_symbols
的实现需要符号名称的支持,在gcc
编译过程中需要加入-rdynamic
参数;
内联函数没有栈帧,它在编译过程中被展开在调用的位置;- 尾调用优化(
Tail-call Optimization
)将复用当前函数栈,而不再生成新的函数栈,这将导致栈信息不能正确被获取。
2、捕获系统异常信号输出调用栈
当程序出现异常时通常伴随着会收到一个由内核发过来的异常信号,如当对内存出现非法访问时将收到段错误信号SIGSEGV
,然后才退出。
利用这一点,当我们在收到异常信号后将程序的调用栈进行输出,它通常是利用signal()
函数,关于系统信号的
三、从backtrace信息分析定位问题
1、测试程序
为了更好的说明和分析问题,我这里将举例一个小程序,它有三个文件组成分别是backtrace.c
、dump.c
、add.c
,其中add.c
提供了对一个数值进行加一的方法,我们在它的执行过程中故意使用了一个空指针并为其赋值,这样人为的造成段错误的发生;
dump.c
中主要用于输出backtrace
信息,backtrace.c
则包含了我们的man
函数,它会先注册段错误信号的处理函数然后去调用add.c
提