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 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;
}
  1. 静态链接情况下的错误信息分析定位

    [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行为错误发生处,即上文示例代码中注释处。

  2. 动态链接情况下的错误信息分析定位

    动态链接的方式,需先将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
    

    就能获取到发生错误的代码行号。

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值