优秀博客:
https://blog.csdn.net/skdkjzz/article/details/46046271
https://blog.csdn.net/littlefang/article/details/42295803
http://blog.chinaunix.net/uid-24774106-id-3457205.html
一.利用backtrace,栈信息找bug
一般察看函数运行时堆栈的方法是使用GDB(bt命令)调试工具。但是,有些时候为了分析程序的BUG(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。
作用:用于程序异常退出时寻找错误原因。
功能:回溯堆栈,简单的说就是可以列出当前函数调用关系。
原理:
- 1. 通过对当前堆栈的分析,找到其上层函数在栈中的帧地址,再分析上层函数的堆栈,再找再上层的帧地址……一直找到最顶层为止,帧地址指的是一块:在栈上存放局部变量,上层返回地址,及寄存器值的空间。
- 2. 由于不同处理器堆栈方式不同,此功能的具体实现是编译器的内建函数
__buildin_frame_address
和
__buildin_return_address
中,它涉及工具glibc和gcc, 如果编译器不支持此函数,也可自己实现此函数,举例中有arm上的实现。
··
二. 相关函数接口简析
在glibc头文件”execinfo.h”中声明了三个函数用于获取当前线程的函数调用堆栈。
2.1 backtrace()
/*
说明:
1. 该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。
参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,
最大不超过size大小。
2. 在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。
3. 某些编译器的优化选项会对获取正确的调用堆栈有干扰;
4. 内联函数没有堆栈框架;
5. 删除框架指针也会导致无法正确解析堆栈内容。
*/
int backtrace(void **buffer, int size)
2.2 backtrace_symbols ()
/*
说明:
1. backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组。
2. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)。
3. 函数返回值是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于
buffer中对应元素的可打印信息。它包括函数名、函数的偏移地址、实际的返回地址。
4. 目前,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址。在其他系统,只有16进制的返回地址能被
获取。另外,你可能需要传递相应的符号给链接器,以能支持函数名功能。(比如,在使用GNU ld链接器的系统
中,器支持-rdynamic的话,建议将其加上!)
5. 该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针。
6. 如果不能为字符串获取足够的空间函数的返回值将会为NULL。
*/
char ** backtrace_symbols (void *const *buffer, int size)
2.3 backtrace_symbols_fd()
/*
说明:
1. backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者
返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,
因此适用于有可能调用该函数会失败的情况。
*/
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
三. 实例
3.1 编译
- 如果是x86平台gcc编译:
gcc -g -rdynamic backtrace.c -o backtrace
- 如果是ARM平台arm-linux-gcc交叉编译,需要带如下编译参数:
-rdynamic
-funwind-tables
-ffunction-sections
否则可能backtrace返回值为0,得不到需要的信息。
3.2 实例代码
/* backtrace.c */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include <time.h>
#include <stdbool.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <memory.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <poll.h>
#include <sys/epoll.h>
#include <sys/select.h>
#include <dirent.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h>
#include <sys/types.h>
#include <execinfo.h>
#define BACKTRACE_BUF_SIZE 50
void sig_children(int signal)
{
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
printf("children %d terminated\n", pid);
}
}
void SigSegv_handler(int signal)
{
int j, nptrs;
void *buffer[BACKTRACE_BUF_SIZE];
char **strings;
#if 1
nptrs = backtrace(buffer, BACKTRACE_BUF_SIZE);
#else
nptrs = backtrace_arm(buffer, BACKTRACE_BUF_SIZE);
#endif
printf("signal_test exit: backtrace() returned %d addresses, sig:%d\n", nptrs, signal);
switch (signal) {
case SIGFPE: // 8:
printf("The signal type: Floating point exception!\n");
break;
case SIGILL: // 4:
printf("The signal type: Illegal instruction!\n");
break;
case SIGSEGV: // 11:
printf("The signal type: Segmentation fault!\n");
break;
case SIGBUS: // 7:
printf("The signal type: Bus error!\n");
break;
case SIGABRT: // 6:
printf("The signal type: Aborted!\n");
break;
case SIGTRAP: // 5:
printf("The signal type: Trace/breakpoint trap!\n");
break;
case SIGSYS: // 31:
printf("The signal type: Bad system call!\n");
break;
case SIGTERM: // 15:
printf("The signal type: Terminated!\n");
break;
case SIGINT: // 2:
printf("The signal type: Interrupt!\n");
break;
case SIGQUIT: // 3:
printf("The signal type: Quit!\n");
break;
case SIGKILL: // 9:
printf("The signal type: Killed!\n");
break;
case SIGHUP: // 1:
printf("The signal type: Hangup!\n");
break;
case SIGALRM: // 14:
printf("The signal type: Alarm clock!\n");
break;
case SIGVTALRM: // 26:
printf("The signal type: Virtual timer expired!\n");
break;
case SIGPROF: // 27:
printf("The signal type: Profiling timer expired!\n");
break;
case SIGIO: // 29:
printf("The signal type: I/O possible!\n");
break;
case SIGPIPE: // 13:
printf("The signal type: Broken pipe!\n");
break;
case SIGXCPU: // 24:
printf("The signal type: CPU time limit exceeded!\n");
break;
case SIGXFSZ: // 25:
printf("The signal type: File size limit exceeded!\n");
break;
case SIGUSR1: // 10:
printf("The signal type: User defined signal 1!\n");
break;
case SIGUSR2: // 12:
printf("The signal type: User defined signal 2!\n");
break;
default:
printf("The signal type: Unkown signal:%d!\n", signal);
break;
}
/* The call backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO)
would produce similar output to the following: */
strings = backtrace_symbols(buffer, nptrs);
if (strings == NULL) {
printf("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (j = 0; j < nptrs; j++) {
printf("%s\n", strings[j]);
}
free(strings);
exit(-1);
}
void main(int argc, char *argv[])
{
signal(SIGCHLD, sig_children);
signal(SIGSEGV, SigSegv_handler);
signal(SIGILL, SigSegv_handler);
signal(SIGBUS, SigSegv_handler);
signal(SIGFPE, SigSegv_handler);
signal(SIGABRT, SigSegv_handler);
signal(SIGTRAP, SigSegv_handler);
signal(SIGSYS, SigSegv_handler);
signal(SIGTERM, SigSegv_handler);
signal(SIGINT, SigSegv_handler);
signal(SIGQUIT, SigSegv_handler);
signal(SIGKILL, SigSegv_handler);
signal(SIGHUP, SigSegv_handler);
signal(SIGALRM, SigSegv_handler);
signal(SIGVTALRM, SigSegv_handler);
signal(SIGPROF, SigSegv_handler);
signal(SIGIO, SigSegv_handler);
signal(SIGPIPE, SigSegv_handler);
signal(SIGXCPU, SigSegv_handler);
signal(SIGXFSZ, SigSegv_handler);
signal(SIGUSR1, SigSegv_handler);
signal(SIGUSR2, SigSegv_handler);
while(1)
sleep(10);
}
3.3 定位问题(c文件:backtrace.c ;编译结果:backtrace;报错行:0x4007e9)
1.通过objdump工具(注:只有加了编译选项的,汇编结果才能有对应的c语言。)
x86:
objdump
arm:
arm-poky-linux-gnueabi-objdump
例如:
objdump -d backtrace > backtrace.s
//没有对应的c语句
objdump -S backtrace | less
//可能有对应的c语句,取决于编译时添加的编译选项。
2.通过addr2line工具
addr2line 0x4007e9 -e backtrace -f