Linux下通过backtrace获取程序崩溃前堆栈信息
原文链接:http://www.ccccxy.top/coding/archives/2020/10/23/linux_backtrace_87/欢迎各位大神指导斧正!一、backtrace( )函数介绍/* Store up to SIZE return address of the current program state inARRAY and return the exact numbe
原文链接:http://www.ccccxy.top/coding/archives/2020/10/23/linux_backtrace_87/
欢迎各位大神指导斧正!
一、backtrace( )函数介绍
/* Store up to SIZE return address of the current program state in
ARRAY and return the exact number of values stored. */
extern int backtrace (void **__array, int __size) __nonnull ((1));
/*该函数用与获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针数组。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址。*/
/* Return names of functions from the backtrace list in ARRAY in a newly
malloc()ed memory block. */
extern char **backtrace_symbols (void *const *__array, int __size)
__THROW __nonnull ((1));
/*
backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的数组指针,size是该数组中的元素个数(backtrace的返回值),函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址
backtrace_symbols生成的字符串都是malloc出来的,但是不要最后一个一个的free,因为backtrace_symbols是根据backtrace给出的call stack层数,一次性的malloc出来一块内存来存放结果字符串的,所以,像上面代码一样,只需要在最后,free backtrace_symbols的返回指针就OK了。这一点backtrace的manual中也是特别提到的。
注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL
*/
/**/
/* This function is similar to backtrace_symbols() but it writes the result
immediately to a file. */
extern void backtrace_symbols_fd (void *const *__array, int __size, int __fd)
__THROW __nonnull ((1));
/*
backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行.它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。
*/
示例:
void dump(void)
{
int i = 0, nptrs = 0;
void *buf[BACKTRACE_SIZE]; //定义用于存放获取到堆栈信息的指针数组
char **strings;
nptrs = backtrace(buf, BACKTRACE_SIZE); //返回获取到的实际堆栈信息指针数
printf("backtrace() returned %d addresses\n", nptrs);
strings = backtrace_symbols(buf, nptrs); //将buf中存放的信息转换为可打印的字符串信息
if (strings == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (i = 0; i < nptrs; i++)
{
printf(" [%02d] %s\n", i, strings[i]);
}
free(strings); //释放一整块存放信息的字符串内存
}
二、通过signal( )信号函数捕获系统异常信号输出调用栈
void (*signal(int sig, void (*func)(int)))(int)
sig
– 在信号处理程序中作为变量使用的信号码:
宏 | 信号 |
---|---|
SIGABRT | (Signal Abort) 程序异常终止。 |
SIGFPE | (Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。 |
SIGILL | (Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。 |
SIGINT | (Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。 |
SIGSEGV | (Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。 |
SIGTERM | (Signal Terminate) 发送给本程序的终止请求信号。 |
func
– 可以是自定义的函数地址,也可以是预定义函数之一:
预定义函数 | 说明 |
---|---|
SIG_DFL | 默认的信号处理程序 |
SIG_IGN | 忽视信号 |
三、实例测试
一共分为两个文件,实现一个数值自加1功能的add.c文件,一个包括输出backtrace信息,和包含了程序入口main函数的backtrace.c
add.c:
#include <stdio.h>
#include <stdlib.h>
int add1(int num)
{
int ret = 0;
int *pTmp = NULL;
*pTmp = 1; //对未分配内存空间的指针进行赋值,模拟访问非法内存段错误
ret = num + *pTmp;
return ret;
}
int add(int num)
{
int ret = 0;
ret = add1(num);
return ret;
}
backtrace.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <execinfo.h>
#define BACKTRACE_SIZE 16
extern int add(int num);
void dump(void)
{
int i = 0, nptrs = 0;
void *buf[BACKTRACE_SIZE];
char **strings;
nptrs = backtrace(buf, BACKTRACE_SIZE);
printf("backtrace() returned %d addresses\n", nptrs);
strings = backtrace_symbols(buf, nptrs);
if (strings == NULL)
{
perror("backtrace_symbols");
exit(EXIT_FAILURE);
}
for (i = 0; i < nptrs; i++)
{
printf(" [%02d] %s\n", i, strings[i]);
}
free(strings);
}
void signal_handler(int signo)
{
#if 0
char buf[64] = {0};
sprintf(buf, "cat /proc/%d/maps", getpid());
system((const char*)buf);
#endif
printf("\n=================>>>catch signal %d<<<=====================\n", signo);
printf("Dump stack start...\n");
dump();
printf("Dump stack end...\n");
signal(signo, SIG_DFL);
raise(signo);
}
int main(int argc, char **argv)
{
int sum = 0;
signal(SIGSEGV, signal_handler);
sum = add(3);
printf("sum = %d\n", sum);
return 0;
}
-
静态链接情况下的错误信息分析定位
[xuanchen@rabbitmq1 backtrace]$ ./backtrace =================>>>catch signal 11<<<===================== Dump stack start... backtrace() returned 8 addresses [00] ./backtrace(dump+0x2d) [0x400aaa] [01] ./backtrace(signal_handler+0x2e) [0x400b70] [02] /lib64/libc.so.6(+0x35270) [0x7f146f30a270] [03] ./backtrace(add1+0x1a) [0x400bfc] [04] ./backtrace(add+0x1c) [0x400c31] [05] ./backtrace(main+0x2f) [0x400bc4] [06] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f146f2f6c05] [07] ./backtrace() [0x4009b9] Dump stack end... Segmentation fault (core dumped)
在第8行开始调用libc.so中内容,可见第9行堆栈即为出错堆栈,
[03] ./backtrace(add1+0x1a) [0x400bfc]
。可见错误发生在add1函数中,且对应行地址为0x400bfc,相对函数add1地址的偏移为0x1a。
此时可通过编译器自带工具,
addr2line -e <elf文件> <对应地址>
,来获取相应的代码行信息,结果如下:[xuanchen@rabbitmq1 backtrace]$ addr2line -e backtrace 0x400bfc /home/xuanchen/test/backtrace/add.c:8
结果显示为add.c中的第8行为错误发生处,即上文示例代码中注释处。
-
动态链接情况下的错误信息分析定位
动态链接的方式,需先将add.c文件编译生成
.so
类型的动态库,如下所示:[xuanchen@rabbitmq1 backtrace]$ gcc -g -rdynamic -O0 add.c -fPIC -shared -o libadd.so [xuanchen@rabbitmq1 backtrace]$ ls libadd.so
然后再以动态链接的方式,编译生成相应可执行文件:
[xuanchen@rabbitmq1 backtrace]$ gcc -g -rdynamic -O0 -L. -ladd backtrace.c -o backtrace
执行后可得结果:
[xuanchen@rabbitmq1 backtrace]$ ./backtrace 00400000-00401000 r-xp 00000000 fd:04 1099675474 /home/xuanchen/test/backtrace/backtrace 00601000-00602000 r--p 00001000 fd:04 1099675474 /home/xuanchen/test/backtrace/backtrace 00602000-00603000 rw-p 00002000 fd:04 1099675474 /home/xuanchen/test/backtrace/backtrace 7f566891c000-7f5668ad4000 r-xp 00000000 fd:00 33624368 /usr/lib64/libc-2.17.so 7f5668ad4000-7f5668cd4000 ---p 001b8000 fd:00 33624368 /usr/lib64/libc-2.17.so 7f5668cd4000-7f5668cd8000 r--p 001b8000 fd:00 33624368 /usr/lib64/libc-2.17.so 7f5668cd8000-7f5668cda000 rw-p 001bc000 fd:00 33624368 /usr/lib64/libc-2.17.so 7f5668cda000-7f5668cdf000 rw-p 00000000 00:00 0 7f5668cdf000-7f5668ce0000 r-xp 00000000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668ce0000-7f5668edf000 ---p 00001000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668edf000-7f5668ee0000 r--p 00000000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668ee0000-7f5668ee1000 rw-p 00001000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668ee1000-7f5668f02000 r-xp 00000000 fd:00 33624361 /usr/lib64/ld-2.17.so 7f56690cb000-7f56690ce000 rw-p 00000000 00:00 0 7f5669101000-7f5669102000 rw-p 00000000 00:00 0 7f5669102000-7f5669103000 r--p 00021000 fd:00 33624361 /usr/lib64/ld-2.17.so 7f5669103000-7f5669104000 rw-p 00022000 fd:00 33624361 /usr/lib64/ld-2.17.so 7f5669104000-7f5669105000 rw-p 00000000 00:00 0 7ffcca992000-7ffcca9b4000 rw-p 00000000 00:00 0 [stack] 7ffcca9e7000-7ffcca9e9000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] =================>>>catch signal 11<<<===================== Dump stack start... backtrace() returned 8 addresses [00] ./backtrace(dump+0x2d) [0x400b8a] [01] ./backtrace(signal_handler+0x6e) [0x400c90] [02] /lib64/libc.so.6(+0x35270) [0x7f5668951270] [03] libadd.so(add1+0x1a) [0x7f5668cdf6cf] [04] libadd.so(add+0x1c) [0x7f5668cdf704] [05] ./backtrace(main+0x2f) [0x400ce4] [06] /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f566893dc05] [07] ./backtrace() [0x400a99] Dump stack end... Segmentation fault (core dumped)
此时若仍然采用静态链接方式,获取到的结果如下所示:
[xuanchen@rabbitmq1 backtrace]$ addr2line -e backtrace 0x7f5668cdf6cf ??:0 [xuanchen@rabbitmq1 backtrace]$ addr2line -e libadd.so 0x7f5668cdf6cf ??:0
这是因为add1函数是通过动态库的方式加载链接进可执行文件,动态库仅在程序运行时动态加载,且动态库中代码为地址无关代码,无法通过这种寻址方式找到对应的代码信息。
在此处,我们通过调用system函数,获取当前进程的map信息
char buf[64] = {0}; sprintf(buf, "cat /proc/%d/maps", getpid()); system((const char*)buf);
可见libadd.so对应的地址范围如下:
7f5668cdf000-7f5668ce0000 r-xp 00000000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668ce0000-7f5668edf000 ---p 00001000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668edf000-7f5668ee0000 r--p 00000000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so 7f5668ee0000-7f5668ee1000 rw-p 00001000 fd:04 1099675477 /home/xuanchen/test/backtrace/libadd.so
而堆栈信息中的地址为0x7f5668cdf6cf,正好对应第1行的地址范围,所以正确的代码地址为0x7f5668cdf6cf - 7f5668cdf000 = 0x6cf。此时在通过addr2line查看:
[xuanchen@rabbitmq1 backtrace]$ addr2line -e libadd.so 0x6cf /home/xuanchen/test/backtrace/add.c:8
就能获取到发生错误的代码行号。
更多推荐
所有评论(0)