Linux でバックトレースを通じてプログラムがクラッシュする前にスタック情報を取得する

原文へのリンク: http://www.ccccxy.top/coding/archives/2020/10/23/linux_backtrace_87/
マスターの指導へようこそ!

1. 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);	//释放一整块存放信息的字符串内存
}

2. システム例外信号をキャプチャし、signal () シグナル関数を通じて呼び出しスタックを出力します。

void (*signal(int sig, void (*func)(int)))(int)

sig– シグナル ハンドラーで変数として使用されるシグナル コード:

大きい 信号
シガブト (Signal Abort) プログラムが異常終了しました。
シグペ (シグナル浮動小数点例外) ゼロ除算やオーバーフローなどの算術演算でエラーが発生しました (必ずしも浮動小数点演算である必要はありません)。
密閉 (不正な命令の信号) 不正な命令などの不正な関数イメージ。通常はコードのバリアントまたはデータを実行しようとする試みが原因です。
署名 (信号割り込み) Ctrl-C などの割り込み信号は通常、ユーザーによって生成されます。
シグセグブ (信号セグメンテーション違反) 存在しないメモリユニットへのアクセスなど、不正なメモリアクセス。
対象期間 (Signal Terminate) 本プログラムに送信される終了要求信号。

func– カスタム関数アドレス、または事前定義された関数の 1 つを指定できます。

定義済み関数 説明する
SIG_DFL デフォルトのシグナルハンドラー
SIG_IGN 信号を無視する

3. テスト例

これは 2 つのファイルに分かれており、1 つは値に 1 を加算する関数を実装する add.c ファイル、もう 1 つは出力バックトレース情報を含むファイル、およびプログラム エントリのメイン関数を含む backtrace.c ファイルです。

追加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;
}

バックトレース.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)
    

    libc.so の内容は 8 行目で呼び出され始めます。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. ダイナミックリンクの場合のエラー情報の分析と位置特定

    動的リンクの方法では、.so次に示すように、add.c ファイルをコンパイルして、一種の動的ライブラリを生成する必要があります。

    [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 関数がダ​​イナミック ライブラリを介してロードされ、実行可能ファイルにリンクされるためです。ダイナミック ライブラリはプログラムの実行中にのみ動的にロードされ、ダイナミック ライブラリ内のコードはアドレス非依存コードであり、対応するコード情報はこのアドレス指定方法では見つかりません。

    ここでは、システム関数を呼び出して現在のプロセスのマップ情報を取得します。

    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
    

    エラーが発生したコードの行番号を取得できます。

おすすめ

転載: blog.csdn.net/qq_38894585/article/details/109310194