linux 下调试剥去调试信息的程序崩溃

为什么要剥去调试信息

  • 减少程序文件尺寸

    剥去掉调试信息后,程序可能只有之前的 1/5 大小,占用的空间更少,下载安装更快。

  • 安全

    函数、变量名称去掉后能够大大增加逆向工程的难度。

如何添加调试信息

gcc 编译程序时指定 -g 选项。

如何剥去调试信息

strip /path/to/app

从程序崩溃后产生的 core 文件定位源代码行

  • 示例程序

    app.c

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <errno.h>
    #include <stdint.h>
    
    void bug(int id) {
      /* illegal pointer, si_code = 128 (send by kernel) */
      printf("[%d] This is bug\n", id);
      int *p = (int *)-1;
      printf("%d\n", *p);
    }
    
    void extra_func() {
      printf("");
    }
    
    int func_b(int id)
    {
      printf("[%d] This is func_b\n", id);
      sleep(rand()%5);
      extra_func();
      bug(id);
      extra_func();
    }
    
    int func_a(int id)
    {
      printf("[%d] This is func_a\n", id);
      sleep(rand()%5);
      extra_func();
      func_b(id);
      extra_func();
    }
    
    void* func(void* param) {
      int id = (int)param;
      sleep(1);
      extra_func();
      func_a(id);
      extra_func();
      return NULL;
    }
    
    int main(int argc, char *argv[])
    {
      int i;
      for(i = 0; i < 10; ++i) {
        pthread_t pid;
        if (0 != pthread_create(&pid, NULL, func, (void*)i)) {
          fprintf(stderr, "create thread %d failed (%d).\n", errno);
          exit(1);
        }
      }
    
      sleep(100);
    
      return 0;
    }
    

    编译

    gcc -g -O2 app.c -lm -lpthread -o app
    
  • 记得保留调试信息

    一种简单的方法是保留未剥去调试信息前的可执行程序

    cp app app_debug
    strip app
    

    或者保留调试信息到调试符号文件

    objcopy --only-keep-debug app app.debug
    strip app
    objcopy --add-gnu-debuglink=app.debug app
    

    这种方法在调试时需要将调试符号 app.debug 文件放到当前目录下,或者在 gdb 中通过 set debug-file-directory /tmp/debug 指定其它目录(如 /tmp/debug )。详情请参考 Debugging with GDB: Separate Debug Files

    并不是所有工具都和 gdb 一样支持分离的调试符号文件,如 str2line 就不支持,可以将调试符号文件和 strip 后的可执行程序重新合并成带调试符号的程序 app_debug

    cp ./app.debug ./app_debug
    eu-unstrip ./app ./app_debug
    

    参考 binutils - How to reverse the objcopy's strip with only-keep-debug? - Stack Overflow

  • 运行程序崩溃产生 core 文件

    $ ulimit -c unlimited
    $ ./app
    [2] This is func_a
    [5] This is func_a
    [8] This is func_a
    [7] This is func_a
    [3] This is func_a
    [4] This is func_a
    [7] This is func_b
    [4] This is func_b
    [9] This is func_a
    [0] This is func_a
    [6] This is func_a
    [1] This is func_a
    [5] This is func_b
    [7] This is bug
    [5] This is bug
    [0] This is func_b
    Segmentation fault (core dumped)
    

    Archlinux 为例,core 文件由 systemd 接管,提取 core 文件到当前目录下

    coredumpctl -r -1 -o app.core dump app
    
  • 使用 gdb 从 core 文件获取崩溃调用栈

    使用 gdb 提取崩溃调用栈使用 strip 掉调试符号的可执行程序即可,因此可以在现场进行提取。

    $ gdb -q --nh --nx --batch -ex bt ./app --core=./app.core
    [New LWP 7790]
    [New LWP 7783]
    [New LWP 7785]
    [New LWP 7791]
    [New LWP 7786]
    [New LWP 7787]
    [New LWP 7789]
    [New LWP 7784]
    [New LWP 7788]
    [New LWP 7782]
    [New LWP 7792]
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/usr/lib/libthread_db.so.1".
    Core was generated by `./app'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x000055de44fb79a4 in ?? ()
    [Current thread is 1 (Thread 0x7f60834ee700 (LWP 7790))]
    #0  0x000055de44fb79a4 in ?? ()
    #1  0x000055de44fb7a0b in ?? ()
    #2  0x000055de44fb7a65 in ?? ()
    #3  0x00007f60872e508a in start_thread () from /usr/lib/libpthread.so.0
    #4  0x00007f608701c47f in clone () from /usr/lib/libc.so.6
    
  • 使用 eu-addr2line 获取地址对应的代码行

    $ eu-addr2line -e ./app_debug --core ./app.core 0x000055de44fb79a4 0x000055de44fb7a0b 0x000055de44fb7a65
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:12
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:26
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:44
    

    app.c:12 正对应产生崩溃的代码行:

    printf("%d\n", *p);
    

    但是除了崩溃的位置给出了正确的代码行 app.c:12 ,其它两处给出的代码行指向了函数定义的尾扩号处,如果一个函数有在多个地方调用另一函数,则无法区分具体是哪个地方的调用引起了崩溃。

    问题:依赖于 core 文件

    一般来说崩溃可能发生在不受控的设备里,如果崩溃产生的 core 文件过大则上传会耗时过长,甚至有的网络环境是按流量收费的,需要找到一种方法直接从可执行程序或其它信息获取到崩溃的源代码行。

  • 使用 addr2line 获取地址对应的代码行

    $ addr2line -e ./app_debug 0x000055de44fb79a4
    ??:0
    

    使用 addr2line 获取 gdb 输出的调用栈地址对应的源代码行失败了,应该是 addr2line 不支持重定址(relocations)后的地址,程序运行时的地址一般都是重定址过的。这估计也是前面的 eu-addr2line 工具需要 core 文件的原因,它可以从 core 文件获取信息拿到重定址前的地址值。

    gdb 输出的地址需要减去基础地址(base address)才能用于 addr2line ,获取基础地址的方式有很多种:

    • 读取 /proc/<pid>/maps 文件

      使用 gdb 调试 core 文件时,程序往往已经结束,可能无法获取。

    • 通过 gdbinfo target 命令获取

      可以获得地址映射空间信息,只是对象名称被隐藏了,不方便分析。

    • 通过 gdbinfo proc mapping

      获取的地址映射空间信息,很简洁,很容易分析。

    获取了地址映射空间地址段信息后,遍历一下所有地址段看 gdb 崩溃堆栈地址在哪个段中,然后减去该地址段的起始地址,应该就是重定址前的地址值了,如下所示:

    首先从 core 文件获取地址映射空间

    $ gdb -q --nh --nx --batch -ex 'info proc mapping' --core ./app.core
    [New LWP 10780]
    [New LWP 10771]
    [New LWP 10777]
    [New LWP 10779]
    [New LWP 10778]
    [New LWP 10776]
    [New LWP 10774]
    [New LWP 10773]
    [New LWP 10775]
    [New LWP 10772]
    [New LWP 10781]
    Core was generated by `./app'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  0x0000563b029d09a4 in ?? ()
    [Current thread is 1 (LWP 10780)]
    Mapped address spaces:
    
              Start Addr           End Addr       Size     Offset objfile
          0x563b029d0000     0x563b029d1000     0x1000        0x0 /home/tangxinfa/Examples/c/addr2line_demo/app
          0x563b02bd0000     0x563b02bd1000     0x1000        0x0 /home/tangxinfa/Examples/c/addr2line_demo/app
          0x563b02bd1000     0x563b02bd2000     0x1000     0x1000 /home/tangxinfa/Examples/c/addr2line_demo/app
          0x7f9fb40e9000     0x7f9fb4297000   0x1ae000        0x0 /usr/lib/libc-2.26.so
          0x7f9fb4297000     0x7f9fb4497000   0x200000   0x1ae000 /usr/lib/libc-2.26.so
          0x7f9fb4497000     0x7f9fb449b000     0x4000   0x1ae000 /usr/lib/libc-2.26.so
          0x7f9fb449b000     0x7f9fb449d000     0x2000   0x1b2000 /usr/lib/libc-2.26.so
          0x7f9fb44a1000     0x7f9fb44ba000    0x19000        0x0 /usr/lib/libpthread-2.26.so
          0x7f9fb44ba000     0x7f9fb46b9000   0x1ff000    0x19000 /usr/lib/libpthread-2.26.so
          0x7f9fb46b9000     0x7f9fb46ba000     0x1000    0x18000 /usr/lib/libpthread-2.26.so
          0x7f9fb46ba000     0x7f9fb46bb000     0x1000    0x19000 /usr/lib/libpthread-2.26.so
          0x7f9fb46bf000     0x7f9fb480a000   0x14b000        0x0 /usr/lib/libm-2.26.so
          0x7f9fb480a000     0x7f9fb4a09000   0x1ff000   0x14b000 /usr/lib/libm-2.26.so
          0x7f9fb4a09000     0x7f9fb4a0a000     0x1000   0x14a000 /usr/lib/libm-2.26.so
          0x7f9fb4a0a000     0x7f9fb4a0b000     0x1000   0x14b000 /usr/lib/libm-2.26.so
          0x7f9fb4a0b000     0x7f9fb4a30000    0x25000        0x0 /usr/lib/ld-2.26.so
          0x7f9fb4c2f000     0x7f9fb4c30000     0x1000    0x24000 /usr/lib/ld-2.26.so
    

    可以看到崩溃地址 0x0000563b029d09a4 在第一个地址段( 0x563b029d0000 0x563b029d1000 )中,减去起始地址后的值为 0x9a4 ,尝试将该值用于 addr2line

    $ addr2line -e ./app_debug 0x9a4
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:12
    

    最终找到了正确的源代码行,这样我们通过在生产环境使用 gdb 提取崩溃调用栈以及地址映射空间,就可以在调试环境进行源代码级的问题定位,而不需要从生产环境取得 core 文件。

    相关讨论:

从程序崩溃后输出的调用栈定位崩溃对应的代码行

libSegFault The segmentation fault signal handler, used by catchsegv

引用自 6.9. Glibc-2.12.2

/lib/libSegFault.so是glibc自身提供的一个动态库,我们可以通过LD_PRELOAD环境变量的设置来提前加载它。在这个 环境下程序的SIGSEGV信号都被libSegment.so中的逻辑处理了,并且它会打印出崩溃现场的寄存器信息,堆栈信息,内存map等

引用自 使用libSegFault.so拦截段错误信号-泛舟-搜狐博客

本节主要参考 Does addr2line work in Linux? - Quora

  • 使用 catchsegv 来运行程序以获取崩溃堆栈信息

    catchsegvlibSegFault.so 进行了封装,如通过设置环境变量将崩溃信息输出到临时文件避免和程序的错误输出混在一起。

    $ catchsegv ./app > ./app.log
    $ cat ./app.log
    *** Segmentation fault
    Register dump:
    
     RAX: 0000000000000010   RBX: 0000000000000009   RCX: 0000000000000000
     RDX: 00007fb65877e980   RSI: 0000000000000000   RDI: 00007fb65877e980
     RBP: 0000000000000000   R8 : 0000000000000001   R9 : 0000000000000000
     R10: 0000000000000000   R11: 0000000000000000   R12: 00007ffdc4b5a34e
     R13: 00007ffdc4b5a34f   R14: 00007fb65877f700   R15: 0000000000000000
     RSP: 00007fb65877eee0
    
     RIP: 000055a0ae63e9a4   EFLAGS: 00010206
    
     CS: 0033   FS: 0000   GS: 0000
    
     Trap: 0000000e   Error: 00000005   OldMask: 00000000   CR2: ffffffff
    
     FPUCW: 0000037f   FPUSW: 00000000   TAG: 00000000
     RIP: 00000000   RDP: 00000000
    
     ST(0) 0000 0000000000000000   ST(1) 0000 0000000000000000
     ST(2) 0000 0000000000000000   ST(3) 0000 0000000000000000
     ST(4) 0000 0000000000000000   ST(5) 0000 0000000000000000
     ST(6) 0000 0000000000000000   ST(7) 0000 0000000000000000
     mxcsr: 1f80
     XMM0:  00000000000000000000000025252525 XMM1:  00000000000000000000000025252525
     XMM2:  00000000000000000000000025252525 XMM3:  00000000000000000000000025252525
     XMM4:  00000000000000000000000025252525 XMM5:  00000000000000000000000025252525
     XMM6:  00000000000000000000000025252525 XMM7:  00000000000000000000000025252525
     XMM8:  00000000000000000000000025252525 XMM9:  00000000000000000000000025252525
     XMM10: 00000000000000000000000025252525 XMM11: 00000000000000000000000025252525
     XMM12: 00000000000000000000000025252525 XMM13: 00000000000000000000000025252525
     XMM14: 00000000000000000000000025252525 XMM15: 00000000000000000000000025252525
    
    Backtrace:
    ./app(+0x9a4)[0x55a0ae63e9a4]
    ./app(+0xa0b)[0x55a0ae63ea0b]
    ./app(+0xa65)[0x55a0ae63ea65]
    /usr/lib/libpthread.so.0(+0x708a)[0x7fb65d61808a]
    /usr/lib/libc.so.6(clone+0x3f)[0x7fb65d34f47f]
    
    Memory map:
    
    55a0ae63e000-55a0ae63f000 r-xp 00000000 08:02 42863171 /home/tangxinfa/Examples/c/addr2line_demo/app
    55a0ae83e000-55a0ae83f000 r--p 00000000 08:02 42863171 /home/tangxinfa/Examples/c/addr2line_demo/app
    55a0ae83f000-55a0ae840000 rw-p 00001000 08:02 42863171 /home/tangxinfa/Examples/c/addr2line_demo/app
    55a0ae85d000-55a0ae87e000 rw-p 00000000 00:00 0 [heap]
    7fb648000000-7fb648021000 rw-p 00000000 00:00 0
    7fb648021000-7fb64c000000 ---p 00000000 00:00 0
    7fb650000000-7fb650021000 rw-p 00000000 00:00 0
    7fb650021000-7fb654000000 ---p 00000000 00:00 0
    7fb657d18000-7fb657d2e000 r-xp 00000000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7fb657d2e000-7fb657f2d000 ---p 00016000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7fb657f2d000-7fb657f2e000 r--p 00015000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7fb657f2e000-7fb657f2f000 rw-p 00016000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7fb657f2f000-7fb657f30000 ---p 00000000 00:00 0
    7fb657f30000-7fb658780000 rw-p 00000000 00:00 0
    7fb658780000-7fb658781000 ---p 00000000 00:00 0
    7fb658781000-7fb658fd1000 rw-p 00000000 00:00 0
    7fb658fd1000-7fb658fd2000 ---p 00000000 00:00 0
    7fb658fd2000-7fb659822000 rw-p 00000000 00:00 0
    7fb659822000-7fb659823000 ---p 00000000 00:00 0
    7fb659823000-7fb65a073000 rw-p 00000000 00:00 0
    7fb65a073000-7fb65a074000 ---p 00000000 00:00 0
    7fb65a074000-7fb65a8c4000 rw-p 00000000 00:00 0
    7fb65a8c4000-7fb65a8c5000 ---p 00000000 00:00 0
    7fb65a8c5000-7fb65b115000 rw-p 00000000 00:00 0
    7fb65b115000-7fb65b116000 ---p 00000000 00:00 0
    7fb65b116000-7fb65b966000 rw-p 00000000 00:00 0
    7fb65b966000-7fb65b967000 ---p 00000000 00:00 0
    7fb65b967000-7fb65c1b7000 rw-p 00000000 00:00 0
    7fb65c1b7000-7fb65c1b8000 ---p 00000000 00:00 0
    7fb65c1b8000-7fb65ca08000 rw-p 00000000 00:00 0
    7fb65ca08000-7fb65ca09000 ---p 00000000 00:00 0
    7fb65ca09000-7fb65d259000 rw-p 00000000 00:00 0
    7fb65d259000-7fb65d407000 r-xp 00000000 08:12 1575097 /usr/lib/libc-2.26.so
    7fb65d407000-7fb65d607000 ---p 001ae000 08:12 1575097 /usr/lib/libc-2.26.so
    7fb65d607000-7fb65d60b000 r--p 001ae000 08:12 1575097 /usr/lib/libc-2.26.so
    7fb65d60b000-7fb65d60d000 rw-p 001b2000 08:12 1575097 /usr/lib/libc-2.26.so
    7fb65d60d000-7fb65d611000 rw-p 00000000 00:00 0
    7fb65d611000-7fb65d62a000 r-xp 00000000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7fb65d62a000-7fb65d829000 ---p 00019000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7fb65d829000-7fb65d82a000 r--p 00018000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7fb65d82a000-7fb65d82b000 rw-p 00019000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7fb65d82b000-7fb65d82f000 rw-p 00000000 00:00 0
    7fb65d82f000-7fb65d97a000 r-xp 00000000 08:12 1575030 /usr/lib/libm-2.26.so
    7fb65d97a000-7fb65db79000 ---p 0014b000 08:12 1575030 /usr/lib/libm-2.26.so
    7fb65db79000-7fb65db7a000 r--p 0014a000 08:12 1575030 /usr/lib/libm-2.26.so
    7fb65db7a000-7fb65db7b000 rw-p 0014b000 08:12 1575030 /usr/lib/libm-2.26.so
    7fb65db7b000-7fb65db7f000 r-xp 00000000 08:12 1574894 /usr/lib/libSegFault.so
    7fb65db7f000-7fb65dd7e000 ---p 00004000 08:12 1574894 /usr/lib/libSegFault.so
    7fb65dd7e000-7fb65dd7f000 r--p 00003000 08:12 1574894 /usr/lib/libSegFault.so
    7fb65dd7f000-7fb65dd80000 rw-p 00004000 08:12 1574894 /usr/lib/libSegFault.so
    7fb65dd80000-7fb65dda5000 r-xp 00000000 08:12 1575098 /usr/lib/ld-2.26.so
    7fb65df5a000-7fb65df5c000 rw-p 00000000 00:00 0
    7fb65dfa2000-7fb65dfa4000 rw-p 00000000 00:00 0
    7fb65dfa4000-7fb65dfa5000 r--p 00024000 08:12 1575098 /usr/lib/ld-2.26.so
    7fb65dfa5000-7fb65dfa6000 rw-p 00025000 08:12 1575098 /usr/lib/ld-2.26.so
    7fb65dfa6000-7fb65dfa7000 rw-p 00000000 00:00 0
    7ffdc4b3a000-7ffdc4b5b000 rw-p 00000000 00:00 0 [stack]
    7ffdc4bc8000-7ffdc4bcb000 r--p 00000000 00:00 0 [vvar]
    7ffdc4bcb000-7ffdc4bcd000 r-xp 00000000 00:00 0 [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
    

    使用 addr2line 显示崩溃调用栈的源代码行

    $ grep -E '^Backtrace:$' -A 10 app.log | grep -E '^\./app' | sed -r ':a;s/.\/app\(\+0x(.*)\)\[.*/\1/;ta;' | xargs addr2line -e ./app_debug
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:12
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:26
    /home/tangxinfa/Examples/c/addr2line_demo/app.c:44
    

    需要注意的是 addr2line 需要依赖未 strip 调试符号的可执行程序版本,适合在调试环境进行。

    可以看到显示了调用栈对应的源代码行,但是除了崩溃的位置给出了正确的代码行 app.c:12 ,其它两处给出的代码行指向了函数定义的尾扩号,如果一个函数有在多个地方调用另一函数,则无法区分具体是哪个地方的调用引起了崩溃。

使用 MAP 文件

一开始有调研过使用 Map 文件,试验失败,看了一下 Map 文件的内容,其中记载了函数的地址,但没有详细的代码行地址,理论上是实现不了从崩溃地址获取源代码行地址的目标的,所以开始研究其它方案。

后来一直在查资料,通过阅读《使用 MAP file 除錯的技巧》一文,发现是之前姿势不对。

MAP file 是 linker 将 object files 编译成 binary (EXE, DLL, …) 时所产生的对应表,亦即系统的 linker loader 如何载入这个 binary image 的报表。有了这个 map 文件,再加上 compiler 产生的 assembly source code,就成为找程序崩溃的最佳利器。

引用自 使用 MAP file 除錯的技巧

这个方案需要在编译 .obj 文件时顺带输出 .cod ,在编译可执行程序时输出 .map 文件,获取到崩溃的地址后,查阅 .map 文件确定崩溃的函数,再根据崩溃地址减去 .map 文件记录的函数地址算出偏移量,再根据偏移量从 .cod 文件中找到代码行。

  • 编译过程中生成 .cod.map 文件

    gcc -c -g -Wa,-a,-ad app.c > app.cod
    gcc -g3 app.o -lpthread -Wl,-Map,app.map -o app
    strip app
    
  • 使用 libSegFault.so 输出崩溃调用栈

    $ catchsegv ./app > ./app.log
    $ cat ./app.log
    *** Segmentation fault
    Register dump:
    
     RAX: ffffffffffffffff   RBX: 0000000000000000   RCX: 0000000000000000
     RDX: 00007f6058689910   RSI: 0000000000000000   RDI: 00007f6058689910
     RBP: 00007f6058689e90   R8 : 0000000000000001   R9 : 0000000000000000
     R10: 0000000000000000   R11: 0000000000000000   R12: 00007ffdff6a66ae
     R13: 00007ffdff6a66af   R14: 00007f605868a700   R15: 0000000000000000
     RSP: 00007f6058689e70
    
     RIP: 00005641ac5ac917   EFLAGS: 00010202
    
     CS: 0033   FS: 0000   GS: 0000
    
     Trap: 0000000e   Error: 00000005   OldMask: 00000000   CR2: ffffffff
    
     FPUCW: 0000037f   FPUSW: 00000000   TAG: 00000000
     RIP: 00000000   RDP: 00000000
    
     ST(0) 0000 0000000000000000   ST(1) 0000 0000000000000000
     ST(2) 0000 0000000000000000   ST(3) 0000 0000000000000000
     ST(4) 0000 0000000000000000   ST(5) 0000 0000000000000000
     ST(6) 0000 0000000000000000   ST(7) 0000 0000000000000000
     mxcsr: 1f80
     XMM0:  00000000000000000000000025252525 XMM1:  00000000000000000000000025252525
     XMM2:  00000000000000000000000025252525 XMM3:  00000000000000000000000025252525
     XMM4:  00000000000000000000000025252525 XMM5:  00000000000000000000000025252525
     XMM6:  00000000000000000000000025252525 XMM7:  00000000000000000000000025252525
     XMM8:  00000000000000000000000025252525 XMM9:  00000000000000000000000025252525
     XMM10: 00000000000000000000000025252525 XMM11: 00000000000000000000000025252525
     XMM12: 00000000000000000000000025252525 XMM13: 00000000000000000000000025252525
     XMM14: 00000000000000000000000025252525 XMM15: 00000000000000000000000025252525
    
    Backtrace:
    ./app(+0x917)[0x5641ac5ac917]
    ./app(+0x998)[0x5641ac5ac998]
    ./app(+0xa07)[0x5641ac5aca07]
    ./app(+0xa45)[0x5641ac5aca45]
    /usr/lib/libpthread.so.0(+0x708a)[0x7f605a33d08a]
    /usr/lib/libc.so.6(clone+0x3f)[0x7f605a07447f]
    
    Memory map:
    
    5641ac5ac000-5641ac5ad000 r-xp 00000000 08:02 42863218 /home/tangxinfa/Examples/c/addr2line_demo/app
    5641ac7ad000-5641ac7ae000 r--p 00001000 08:02 42863218 /home/tangxinfa/Examples/c/addr2line_demo/app
    5641ac7ae000-5641ac7af000 rw-p 00002000 08:02 42863218 /home/tangxinfa/Examples/c/addr2line_demo/app
    5641aca9a000-5641acabb000 rw-p 00000000 00:00 0 [heap]
    7f6048000000-7f6048021000 rw-p 00000000 00:00 0
    7f6048021000-7f604c000000 ---p 00000000 00:00 0
    7f6050000000-7f6050021000 rw-p 00000000 00:00 0
    7f6050021000-7f6054000000 ---p 00000000 00:00 0
    7f6054a3d000-7f6054a53000 r-xp 00000000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7f6054a53000-7f6054c52000 ---p 00016000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7f6054c52000-7f6054c53000 r--p 00015000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7f6054c53000-7f6054c54000 rw-p 00016000 08:12 1644549 /usr/lib/libgcc_s.so.1
    7f6054c54000-7f6054c55000 ---p 00000000 00:00 0
    7f6054c55000-7f60554a5000 rw-p 00000000 00:00 0
    7f60554a5000-7f60554a6000 ---p 00000000 00:00 0
    7f60554a6000-7f6055cf6000 rw-p 00000000 00:00 0
    7f6055cf6000-7f6055cf7000 ---p 00000000 00:00 0
    7f6055cf7000-7f6056547000 rw-p 00000000 00:00 0
    7f6056547000-7f6056548000 ---p 00000000 00:00 0
    7f6056548000-7f6056d98000 rw-p 00000000 00:00 0
    7f6056d98000-7f6056d99000 ---p 00000000 00:00 0
    7f6056d99000-7f60575e9000 rw-p 00000000 00:00 0
    7f60575e9000-7f60575ea000 ---p 00000000 00:00 0
    7f60575ea000-7f6057e3a000 rw-p 00000000 00:00 0
    7f6057e3a000-7f6057e3b000 ---p 00000000 00:00 0
    7f6057e3b000-7f605868b000 rw-p 00000000 00:00 0
    7f605868b000-7f605868c000 ---p 00000000 00:00 0
    7f605868c000-7f6058edc000 rw-p 00000000 00:00 0
    7f6058edc000-7f6058edd000 ---p 00000000 00:00 0
    7f6058edd000-7f605972d000 rw-p 00000000 00:00 0
    7f605972d000-7f605972e000 ---p 00000000 00:00 0
    7f605972e000-7f6059f7e000 rw-p 00000000 00:00 0
    7f6059f7e000-7f605a12c000 r-xp 00000000 08:12 1575097 /usr/lib/libc-2.26.so
    7f605a12c000-7f605a32c000 ---p 001ae000 08:12 1575097 /usr/lib/libc-2.26.so
    7f605a32c000-7f605a330000 r--p 001ae000 08:12 1575097 /usr/lib/libc-2.26.so
    7f605a330000-7f605a332000 rw-p 001b2000 08:12 1575097 /usr/lib/libc-2.26.so
    7f605a332000-7f605a336000 rw-p 00000000 00:00 0
    7f605a336000-7f605a34f000 r-xp 00000000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7f605a34f000-7f605a54e000 ---p 00019000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7f605a54e000-7f605a54f000 r--p 00018000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7f605a54f000-7f605a550000 rw-p 00019000 08:12 1575121 /usr/lib/libpthread-2.26.so
    7f605a550000-7f605a554000 rw-p 00000000 00:00 0
    7f605a554000-7f605a558000 r-xp 00000000 08:12 1574894 /usr/lib/libSegFault.so
    7f605a558000-7f605a757000 ---p 00004000 08:12 1574894 /usr/lib/libSegFault.so
    7f605a757000-7f605a758000 r--p 00003000 08:12 1574894 /usr/lib/libSegFault.so
    7f605a758000-7f605a759000 rw-p 00004000 08:12 1574894 /usr/lib/libSegFault.so
    7f605a759000-7f605a77e000 r-xp 00000000 08:12 1575098 /usr/lib/ld-2.26.so
    7f605a932000-7f605a935000 rw-p 00000000 00:00 0
    7f605a97b000-7f605a97d000 rw-p 00000000 00:00 0
    7f605a97d000-7f605a97e000 r--p 00024000 08:12 1575098 /usr/lib/ld-2.26.so
    7f605a97e000-7f605a97f000 rw-p 00025000 08:12 1575098 /usr/lib/ld-2.26.so
    7f605a97f000-7f605a980000 rw-p 00000000 00:00 0
    7ffdff688000-7ffdff6a9000 rw-p 00000000 00:00 0 [stack]
    7ffdff75b000-7ffdff75e000 r--p 00000000 00:00 0 [vvar]
    7ffdff75e000-7ffdff760000 r-xp 00000000 00:00 0 [vdso]
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
    
  • 定位源代码行

    app.map 文件中找到第一个地址小于等于 0x917 的函数是 bug

    0x00000000000008ea                bug
    

    计算偏移量

    0x917 - 0x8ea = 0x2d
    

    bug 函数对应的 .cod 文件 app.cod 中查找这个偏移量

    12:app.c         ****   printf("%d\n", *p);
    32                    .loc 1 12 0
    33 0029 488B45F8      movq    -8(%rbp), %rax
    34 002d 8B00          movl    (%rax), %eax
    

    找到了正确的崩溃代码行 app.c12printf("%d\n", *p);

    接着继续找一下地址 0x998 ,从 .map 找出的对应函数为 func_b ,计算出的偏移地址为 0x62 对应的源代码行 app.c21printf("[%d] This is func_b\n", id) ,找到了错误的代码行。

docker 容器内的 core 文件

docker 环境下,容器使用的是宿主机的 linux 内核,core 文件的生成位置无论是在宿主机中还是在容器中都是由宿主机的 /proc/sys/kernel/core_pattern 配置决定,为了顺利生成 core 文件,宿主机以及容器都要创建好 core 文件目录。

通常来说宿主机与容器中程序及库的位置、环境变量及版本会有比较大的差异,core 文件跟它们是密切相关的,因此宿主机生成的 core 文件应该在宿主机中使用 gdb 进行分析,而容器生成的 core 文件也应该在在容器中使用 gdb 进行分析。

总结

三种方案都可以获得同样的结果。

使用 gdb 从 core 文件获取崩溃调用栈的方案依赖 core 文件。但如果要实现接管操作系统级的 core dump 处理,然后统一上报则会成为自然的选择,这个方案会非常通用。生产环境需要开启 core dump 以及安装 gdb 。需要预先保管好调试信息或未 strip 调试信息的可执行程序。

使用 libSegFault.so 输出崩溃调用栈的方案不依赖 core 文件,更轻量级。但是需要在运行程序时提前以 LD_PRELOAD 环境变量注入 libSegFault.so 。需要预先保管好未 strip 调试信息的可执行程序。

使用 Map 文件需要使用前面两种方案一样的方法来获取崩溃的调用栈,只是定位源代码行时不依赖可执行程序。需要进行的手工查找、计算,暂时没有找到简化操作的工具。需要预先保管好 .cod 以及 .map 文件。