为什么要剥去调试信息
减少程序文件尺寸
剥去掉调试信息后,程序可能只有之前的 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 文件时,程序往往已经结束,可能无法获取。
通过
gdb
的info target
命令获取可以获得地址映射空间信息,只是对象名称被隐藏了,不方便分析。
通过
gdb
的info 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
/lib/libSegFault.so是glibc自身提供的一个动态库,我们可以通过LD_PRELOAD环境变量的设置来提前加载它。在这个 环境下程序的SIGSEGV信号都被libSegment.so中的逻辑处理了,并且它会打印出崩溃现场的寄存器信息,堆栈信息,内存map等
引用自 使用libSegFault.so拦截段错误信号-泛舟-搜狐博客
本节主要参考 Does addr2line work in Linux? - Quora
使用
catchsegv
来运行程序以获取崩溃堆栈信息catchsegv
对libSegFault.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.c
第12
行printf("%d\n", *p);
。接着继续找一下地址
0x998
,从.map
找出的对应函数为func_b
,计算出的偏移地址为0x62
对应的源代码行app.c
第21
行printf("[%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
文件。