使用 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 大小:

  1. ulimit -c unlimited:设置 core 文件的大小为无限大;
  2. 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
  1. 如果不指定路径,则生成在程序执行的路径下。

  2. 如果生成的文件名中不包含 %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,符合预期:

image-20240421150152539

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 文件中的地址,

可结合 readelfaddr2line 工具分析得到对应的代码行。

首先查看 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)

这个行号,有时候对应问题行号,有时候对应问题行号附近的代码行,具体可分析相应代码。

参考资料

posted @ 2024-04-21 15:38  MElephant  阅读(696)  评论(0编辑  收藏  举报