使用 GDB 调试 coredump
一、生成 coredump
core,又称 coredump 文件,准确来讲是 Unix/Linux 的记录机制产生的一种保存程序崩溃时现场状态的记录性文件。当系统中的一些程序在遇到一些错误以及 crash(崩溃)时,系统会自动产生 core 文件记录 crash 时刻的系统信息,主要包括程序运行时候的内存状态、寄存器状态、堆栈指针、内存管理等现场信息,用以程序员 debug 时使用。
- core:内存 / 核心
- dump:抛出 / 扔出
- coredump 连起来可以直译为「吐核」
1.1 允许系统生成 coredump
coredump 文件有时可能在程序发生错误时,并没有产生,发生这种情况的最有可能的原因为:当前终端被设置为不能弹出 core 文件。一般默认情况下,core 文件的大小被设置为 0,这样系统就不 dump 出 core 文件了。
我们可以使用 ulimit -c
命令查看 core 文件的大小:
$ulimit -c
0
或者使用 ulimit -a
查看所有的信息:
$ulimit -a
core file size (blocks, -c) 0 # 被设置为不产生
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 1888
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1888
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
可以使用下述的命令修改 core 大小:
ulimit -c unlimited
:设置 core 文件的大小为无限大;ulimit -c [size]
:指定 core 文件的大小,这里的 size 单位是 block(1block = 512byte)。
1.2 设置 coredump 的生成路径
当允许系统生成 coredump 时,默认在程序执行的路径下生成名为 core.PID 的 coredump 文件,其中 PID 指的时程序运行时的 PID,可以通过 ps -T
得到。
查询默认的 coredump 文件格式:
$cat /proc/sys/kernel/core_pattern
core
当然我们也可以自定义 coredump 的生成路径以及名称显示。
自定义 coredump 文件格式:
# 方式一:使用 echo 设置
$echo "${PWD}/core-%e-%t%-%p" > /proc/sys/kernel/core_pattern
# 方式二:使用 sysctl 设置
$sysctl -w kernel.core_pattern=${PWD}/core-%e-%t%-%p
如果不指定路径,则生成在程序执行的路径下。
如果生成的文件名中不包含 %p,则会默认在最后添加上 .%p
core_pattern 接收的是 core 文件名称的格式,它包含任何字符串,并且用 %
作为转义字符生成一些标识符,以此为 core 文件名称加入特殊含义。
已定义的标示符有如下这些:
转义字符 | 含义 | 中文解释 |
---|---|---|
%% |
相当于 % |
|
%p |
insert pid into filename | 添加pid |
%u |
insert current uid into filename | 添加当前uid |
%g |
insert current gid into filename | 添加当前gid |
%s |
insert signal that caused the coredump into the filename | 添加导致产生core的信号 |
%t |
insert UNIX time that the coredump occurred into filename | 添加core文件生成时的unix时间 |
%e |
insert coredumping executable name into filename | 添加可执行文件名 |
%h |
insert hostname where the coredump happened into filename | 添加主机名 |
二、解析 coredump
2.1 准备工作
2.1.1 生成 coredump
$ulimit -c unlimited
$sysctl -w kernel.core_pattern=core-%e-%t%-%p
2.1.2 core 程序
首先准备一个 libtest.so,该动态库中对外提供了一个接口 TEST_Div,当入参 b 传入 0 时就会触发 coredump。
然后准备一个 main.c 链接 libtest.so 并传入 b = 0,此处我是用多线程实现的,起一个线程调用 Test_Div,并随机两个数 a 和 b。
$git clone https://gitee.com/melephant/my-project.git
$cd my-project/
$git checkout GDB
$cd DynamicallyLinkedDemo/
$make clean ; make
执行完上述指令后,在当前路径下生成一个 strip 后的 main.exe,运行 main.exe:
$export LD_LIBRARY_PATH=/program/lib:${LD_LIBRARY_PATH}
$./main.exe
[test.c:8]calculate 1 divided by 1.
[test.c:8]calculate 0 divided by 0.
Floating point exception (core dumped)
$ls | grep core
core-main.exe-1713682067-12472
2.2 使用 GDB 解析 coredump
2.2.1 gdb 解析 core
命令格式:gdb [可执行文件] -c [core 文件]
$gdb -q main.exe -c core-main.exe-1713682067-12472
(gdb) bt
#0 0x00007fa364d046df in TEST_Div () from /program/lib/libtest.so
#1 0x000000000040087d in ?? ()
#2 0x00007fa364aefea5 in start_thread () from /lib64/libpthread.so.0
#3 0x00007fa364818b0d in clone () from /lib64/libc.so.6
由于 main.exe 是 strip 后的,所以 #1 显示了 ??() 表明符号解析失败了。
2.2.2 symbol-file
导入 main.exe.sym:
(gdb) symbol-file ./main/main.exe.sym
Reading symbols from xxx/DynamicallyLinkedDemo/main/main.exe.sym...done.
(gdb) bt
#0 0x00007fa364d046df in TEST_Div () from /program/lib/libtest.so
#1 0x000000000040087d in MAIN_CallSub (arg=<error reading variable: can't compute CFA for this frame>)
at main.c:18
#2 0x00007fa364aefea5 in start_thread () from /lib64/libpthread.so.0
#3 0x00007fa364818b0d in clone () from /lib64/libc.so.6
导入 main.exe.sym 后,#1 可正常解析了,但是 libtest.so 还是无法解析出行号。
2.2.3 add-symbol-file
命令格式:add-symbol-file 含符号的库 addr
其中 addr 可通过指令 info sharedlibrary
获取:
$info sharedlibrary
From To Syms Read Shared Object Library
0x00007fa364d045c0 0x00007fa364d046e4 Yes (*) /program/lib/libtest.so
0x00007fa364aed8f0 0x00007fa364af8db1 Yes (*) /lib64/libpthread.so.0
0x00007fa3647399f0 0x00007fa364889b6f Yes (*) /lib64/libc.so.6
0x00007fa364f06af0 0x00007fa364f22060 Yes (*) /lib64/ld-linux-x86-64.so.2
(*): Shared library is missing debugging information.
找到 libtest.so 的起始地址 0x00007fa364d045c0,这个就是 add-symbol-file 的第三个参数。
(gdb) add-symbol-file ./libtest/libtest.so.sym 0x00007fa364d045c0
add symbol table from file "./libtest/libtest.so.sym" at
.text_addr = 0x7fa364d045c0
(y or n) y ---> 输入 y 确认
Reading symbols from xxx/DynamicallyLinkedDemo/libtest/libtest.so.sym...done.
(gdb) bt
#0 0x00007fa364d046df in TEST_Div (a=0, b=0) at test.c:9
#1 0x000000000040087d in MAIN_CallSub (arg=<error reading variable: can't compute CFA for this frame>)
at main.c:18
#2 0x00007fa364aefea5 in start_thread () from /lib64/libpthread.so.0
#3 0x00007fa364818b0d in clone () from /lib64/libc.so.6
至此,崩溃堆栈解析完成,从堆栈信息中可以获取到崩溃点所在行号为 test.c:9,符合预期:
2.2.4 一个小想法
既然最外层的崩溃堆栈出现在了 libtest.so 中,那么只导入 libtest.so.sym 可不可以:
(gdb) add-symbol-file ./libtest/libtest.so.sym 0x00007fa364d045c0
add symbol table from file "./libtest/libtest.so.sym" at
.text_addr = 0x7fa364d045c0
(y or n) y
Reading symbols from /root/MElephant/05.gdb/my-project/DynamicallyLinkedDemo/libtest/libtest.so.sym...done.
(gdb) bt
#0 0x00007fa364d046df in TEST_Div (a=0, b=0) at test.c:9
#1 0x000000000040087d in ?? ()
#2 0x00007fa364aefea5 in start_thread () from /lib64/libpthread.so.0
#3 0x00007fa364818b0d in clone () from /lib64/libc.so.6
(gdb)
诶,是可以解析出 #0 的堆栈信息的。
2.3 $sp 的使用
有时候堆栈信息的 #0 并不能成功显示,那么我们可以使用 $sp 完成问题定位:
(gdb) x/128a $sp
0x7fa364718ed0: 0x7fa364718f10 0x0
0x7fa364718ee0: 0x7fa364718f10 0x40087d <MAIN_CallSub+144>
...
一般函数符号出现时,其格式为「地址 <函数名+十进制偏移量>」,这个地址是其后所显示函数中的一个代码行在 core 文件中的地址,
可结合 readelf 和 addr2line 工具分析得到对应的代码行。
首先查看 MAIN_CallSub 属于那个符号表:
(gdb) info symbol MAIN_CallSub
MAIN_CallSub in section .text of xxx/DynamicallyLinkedDemo/main/main.exe.sym
使用 readelf 工具获取 MAIN_CallSub 的偏移量:
$readelf --syms main.exe.sym | grep MAIN_CallSub
66: 00000000004007ed 171 FUNC GLOBAL DEFAULT 13 MAIN_CallSub
MAIN_CallSub 在符号表中的偏移量是 00000000004007ed,再结合 $sp 中的 <MAIN_CallSub+144> 信息可以定位出崩溃时行号:
# 0x40087d = 0x4007ed + 144
$addr2line -fpse main.exe.sym 40087d
MAIN_CallSub at main.c:19 (discriminator 2)
这个行号,有时候对应问题行号,有时候对应问题行号附近的代码行,具体可分析相应代码。