目录

1.backtrace作用

2.backtrace原理

3.backtrace函数说明

4.实例

4.1.实例1

4.1.1.源码

4.1.2.编译

4.1.3运行

4.2.实例2

4.2.1.源码

4.2.2.编译

4.2.3.运行

4.2.4.分析Segmentation fault     


1.backtrace作用

        用户态或者内核态程序异常退出时回溯堆栈信息。

        开发应用程序时,经常碰到程序挂掉后,串口打印输出一大串让人看不懂的数据。其实这些数据是程序挂掉时的堆栈帧数据(stack frame data)。

       通过这些堆栈帧数据可以分析出程序当时的运行状态和定位程序哪里出现了问题。

        这就是本文要讲的—backtrace()和backtrace_symbols()函数的使用。

        backtrace()和backtrace_symbols()函数,包括另一个函数:backtrace_symbols_fd(),它们是GNU对程序调试的扩展支持。

2.backtrace原理

        通过对当前堆栈的分析,回溯上层函数在当前栈中的帧地址,直至顶层函数。帧地址是指在栈中存在局部变量、上一级函数返回地址、寄存器值的内存空间。

        由于不同处理器堆栈实现不同(向上增长和向下增长),此功能的具体实现是编译器内建的__buildin_frame_address及__buildin_return_address函数。

        如果编译器不支持此函数,也可以自己实现该函数。

3.backtrace函数说明

具体说明可以参考man backtrace帮助文档:

execinfo.h

int backtrace (void **buffer, int size)
The backtrace function obtains a backtrace for the current thread, as a list of pointers, and places the information into buffer.

char ** backtrace_symbols (void *const *buffer, int size)

The backtrace_symbols function translates the information obtained from the backtrace function into an array of strings. The argument buffer should be a pointer to an array of addresses obtained via the backtrace function, and size is the number of entries in that array (the return value of backtrace).

void backtrace_symbols_fd(void *const *buffer, int size, int fd)

头 文 件:

        #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, int fd);

函数描述:

1. int backtrace (void **buffer, int size):

        获取函数调用堆栈帧数据,即回溯函数调用列表。

        数据将放在buffer中,参数size用来指定buffer中可以保存多少个void*元素(表示相应栈帧的地址,一个返回地址)。

        如果回溯的函数调用大于size,则size个函数调用地址被返回。为了取得全部的函数调用列表,应保证buffer和size足够大。

2.char **backtrace_symbols (void *const *buffer, int size):

        参数buffer是从backtrace()函数获取的数组指针,size是该数组中的元素个数(backtrace()函数的返回值)。

        该函数主要功能:将从backtrace()函数获取的地址转为描述这些地址的字符串数组。

        每个地址的字符串信息包含对应函数的名字、在函数内的十六进制偏移地址、以及实际的返回地址(十六进制)。

        需注意的是,当前,只有使用elf二进制格式的程序才能获取函数名称和偏移地址,此外,为支持函数名功能,可能需要添加相应的编译链接选项如-rdynamic;否则,只有十六进制的返回地址能被获取。

        backtrace_symbols()函数返回值是一个字符串指针,是通过malloc函数申请的空间,使用完后,调用者必需把它释放掉。

注:如果不能为字符串获取足够的空间,该函数的返回值为NULL。

3.void backtrace_symbols_fd (void *const *buffer, int size, int fd):

        与backtrace_symbols()函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行。

        它不会调用malloc函数,因此,它可以应用在函数调用可能失败的情况下。

         返 回 值 backtrace()函数返回通过buffer返回的地址个数,这个数目不会超过size。

        如果这个返回值小于size,那么所有的函数调用列表都被保存;如果等于size,那么函数调用列表可能被截断,此时,一些最开始的函数调用没有被返回。

         成功时,backtrace_symbols()函数返回一个由malloc分配的数组;

        失败时,返回NULL。

注意事项:  这些函数对函数返回地址如何保存在栈内有一些假设,注意如下: 忽略帧指针(由gcc任何非零优化级别处理了)可能引起这些假设的混乱。

         内联函数没有栈帧。 Tail-call(尾调用)优化会导致栈帧被其它调用覆盖。

         为支持函数名功能,可能需要添加相应的编译链接选项如-rdynamic;否则,只有十六进制的返回地址能被获取。

         “static”函数名是不会导出的,也不会出现在函数调用列表里,即使指定了-rdynamic链接选项。

4.实例

4.1.实例1

4.1.1.源码

#include <signal.h>

#include <stdio.h>

#include <stdlib.h>

#include <execinfo.h>

#include <fcntl.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>



#define PRINT_DEBUG

#define MAX_BACKTRACE_LEVEL 10

#define BACKTRACE_LOG_NAME "backtrace.log"



static void show_reason(int sig, siginfo_t *info, void *secret){

    void *array[MAX_BACKTRACE_LEVEL];

    size_t size;

#ifdef PRINT_DEBUG

    char **strings;

    size_t i;

    size = backtrace(array, MAX_BACKTRACE_LEVEL);

    strings = backtrace_symbols(array, size);

    printf("Obtain %zd stack frames.\n", size);

    for(i = 0; i < size; i++)

    printf("%s\n", strings[i]);

    free(strings);

#else

    int fd = open(BACKSTRACE_LOG_NAME, O_CREAT | O_WRONLY);

    size = backtrace(array, MAX_BACKTRACE_LEVEL);

    backtrace_symbols_fd(array, size, fd);

    close(fd);

#endif

    exit(0);

}



void die() {

    char *str1;

    char *str2;

    char *str3;

    char *str4 = NULL;

    strcpy(str4, "ab");

}



void let_it_die() {

    die();

}



int main(int argc, char **argv){

    struct sigaction act;

    act.sa_sigaction = show_reason;

    sigemptyset(&act.sa_mask);

    act.sa_flags = SA_RESTART | SA_SIGINFO;



    sigaction(SIGSEGV, &act, NULL);

    sigaction(SIGUSR1, &act, NULL);

    sigaction(SIGFPE, &act, NULL);

  sigaction(SIGILL, &act, NULL);

    sigaction(SIGBUS, &act, NULL);

    sigaction(SIGABRT, &act, NULL);

    sigaction(SIGSYS, &act, NULL);



    let_it_die();



   return  0;

}

4.1.2.编译

huawei@WUH1000002965:~/test$ gcc backtrace_test.c -o backtrace_test -g –rdynamic

(注:-rdynamic,这个option是传递给linker的,linker会将symbol放到.dydym table中,这样backtrace_symbols才能获取到地址对应的symbol。所以即使是使用了-g来编译程序,如果不使用-rdynamic的话,backtrace_symbols也找不到地址对应的symbol。这是backtrace系列函数的一个缺陷)

4.1.3运行

huawei@WUH1000002965:~/test$ ./backtrace_test

Obtain 7 stack frames.

./backtrace_test() [0x40096e]

/lib/x86_64-linux-gnu/libc.so.6(+0x364a0) [0x7f2ff171c4a0]

./backtrace_test(die+0x18) [0x400a03]

./backtrace_test(let_it_die+0xe) [0x400a1d]

./backtrace_test(main+0xf6) [0x400b15]

/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f2ff170776d]

./backtrace_test() [0x400889]

4.2.实例2

4.2.1.源码

001
    /**
002
     * \brief backtrace测试程序
003
     *
004
     * 编译指令:gcc -g -rdynamic backtrace.c -o backtrace
005
     */
006  
007
    #include <stdio.h>
008
    #include <stdlib.h>
009
    #include <unistd.h>
010
    #include <signal.h>   /* for signal */
011
    #include <execinfo.h> /* for backtrace() */
012     
013
    #define SIZE    100
014
015
    void dump(void)
016
    {
017
        int j, nptrs;
018
        void *buffer[100];
019
        char **strings;
020
     
021
        nptrs = backtrace(buffer, SIZE);
022
        printf("backtrace() returned %d addresses\n", nptrs);
023 
024
        strings = backtrace_symbols(buffer, nptrs);
025
        if (strings == NULL) {
026
            perror("backtrace_symbols");
027
            exit(EXIT_FAILURE);
028
        }
029  
030
        for (j = 0; j < nptrs; j++)
031
            printf("  [%02d] %s\n", j, strings[j]);
032    
033
        free(strings);
034
    }
035     
036
    void handler(int signo)
037
    {
038
        printf("\n=========>>>catch signal %d (%s) <<<=========\n",
039
                    signo, (char *)strsignal(signo));
040
        printf("Dump stack start...\n");
041
        dump();
042
        printf("Dump stack end...\n");
043    
044
        /* 恢复并发送信号 */
045
        signal(signo, SIG_DFL);
046
        raise(signo);
047
    }
048
     
049
    void printSigno(int signo)
050
    {
051
        static int i = 0;
052         
053
        printf("\n=========>>>catch signal %d (%s) i = %d <<<=========\n",
054
                    signo, (char *)strsignal(signo), i++);
055
        printf("Dump stack start...\n");
056
        dump();
057
        printf("Dump stack end...\n");
058
    }
059
     
060
    void myfunc3(void)
061
    {
062
        /* 为SIGINT安装信号处理函数,通过Ctrl + C发出该信号 */
063
        signal(SIGINT, handler);
064
        signal(SIGSEGV, handler);   /* 为安装SIGSEGV信号处理函数 */
065
        signal(SIGUSR1, printSigno);
066
     
067
        /* 打印当前函数调用栈 */
068
        printf("Current function calls list is: \n");
069
        printf("----------------------------------\n");
070
        dump();
071
        printf("----------------------------------\n\n");
072     
073
        while (1) {
074
            ;/* 在这里通过Ctrl + C发出一个SIGINT信号来结束程序的运行 */
075
        }
076
    }
077
     
078
    /**
079
     * \brief
080
     * 使用static修饰函数,表明不导出这个符号。
081
     * 即使用-rdynamic选项,看到的只能是个地址。
082
     */
083
    static void myfunc2(void)
084
    {
085
        myfunc3();
086
    }
087
     
088
    void myfunc(int ncalls)
089
    {
090
        if (ncalls > 1)
091
            myfunc(ncalls -1);
092
        else
093
            myfunc2();
094
    }
095
     
096
    int main(int argc, char *argv[])
097
    {
098
        if (argc != 2) {
099
            fprintf(stderr, "%s num-calls\n", argv[0]);
100
            exit(EXIT_FAILURE);
101
        }
102         
103
        myfunc(atoi(argv[1]));
104
        exit(EXIT_SUCCESS);
105
    }
106
     
107
    // ----------------------------------------------------------------------------
108
    // End of backtrace.c

4.2.2.编译

gcc -g -rdynamic backtrace.c -o backtrace

4.2.3.运行

reille@ubuntu:backtrace$ ./backtrace 3
Current function calls list is:
———————————-
backtrace() returned 9 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(myfunc3+0x4b) [0x8048a88]
[02] ./backtrace [0x8048aa1]
[03] ./backtrace(myfunc+0x21) [0x8048ac4]
[04] ./backtrace(myfunc+0x1a) [0x8048abd]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(main+0x52) [0x8048b18]
[07] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[08] ./backtrace [0x8048891]
———————————-

^C (按钮Ctrl + C键)
=========>>>catch signal 2 (Interrupt) <<<=========
Dump stack start…
backtrace() returned 10 addresses
[00] ./backtrace(dump+0x1f) [0x8048943]
[01] ./backtrace(handler+0x3c) [0x8048a11]
[02] [0x1f4400]
[03] ./backtrace [0x8048aa1]
[04] ./backtrace(myfunc+0x21) [0x8048ac4]
[05] ./backtrace(myfunc+0x1a) [0x8048abd]
[06] ./backtrace(myfunc+0x1a) [0x8048abd]
[07] ./backtrace(main+0x52) [0x8048b18]
[08] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x644b56]
[09] ./backtrace [0x8048891]
Dump stack end…

reille@ubuntu:backtrace$
reille@ubuntu:backtrace$

4.2.4.分析Segmentation fault      

        对于Segmentation fault错误,有多种分析定位方法,如利用linux产生的core文件、使用gdb进行分析定位。但对于一直运行的程序,特别是大型程序,当意外出现Segmentation fault错误时,其分析定位,则比较棘手。

        我们知道,产生Segmentation fault错误时,一般会产生一个SIGSEGV信号。利用这个机制,上述问题传统的做法是,在程序中安装SIGSEGV信号,然后在该信号处理函数中,回溯函数调用列表,从而分析定位错误,一劳永逸。
        上面的程序用例中,已安装了SIGSEGV信号。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐