进程coredump的elf debug信息补全方法

背景

我的一个运行CentOS上的进程由于bug crash掉了, 并留下了coredump文件, 使用gdb查看coredump文件时,

发现crash在了一个动态库上, 但是该动态库没有debug信息, 因为不是'-g'编译的. 如下:

# gdb /usr/sbin/nginx /export/Data/cores/core-nginx-sig11-pid61-time1608517384 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
(gdb) bt
#0  ASYNC_WAIT_CTX_get_fd (ctx=<optimized out>, key=0x7f7e3f49db46, fd=fd@entry=0x7f7e3d1d6c9c, custom_data=custom_data@entry=0x7f7e3d1d6ca0) at crypto/async/async_wait.c:73
#1  0x00007f7e3f48c82f in qat_wake_job (job=<optimized out>, jobStatus=<optimized out>) at qat_events.c:334
#2  0x00007f7e3f1ef26c in LacPke_MsgCallback () from /opt/quickassist/lib/libqat_s.so
#3  0x00007f7e3f20e617 in adf_user_notify_msgs_poll () from /opt/quickassist/lib/libqat_s.so
#4  0x00007f7e3f20a618 in adf_pollRing () from /opt/quickassist/lib/libqat_s.so
#5  0x00007f7e3f20a977 in icp_adf_pollInstance () from /opt/quickassist/lib/libqat_s.so
#6  0x00007f7e3f203b99 in icp_sal_CyPollInstance () from /opt/quickassist/lib/libqat_s.so
#7  0x00007f7e3f48e419 in qat_timer_poll_func (ih=<optimized out>) at qat_polling.c:254
#8  0x00007f7e425dbea5 in start_thread () from /lib64/libpthread.so.0
#9  0x00007f7e414d796d in clone () from /lib64/libc.so.6
(gdb) q

这个库是libqat_s.so,  由rpm安装管理. 现在要做的就是把上面的调用栈信息补全, 使其可以定位到代码行, 可以打印变量的值.

方案

已经知道, 之所以没有调试信息是因为库libqat_s.so在编译时,没有使用 -g 选项. 如果bug可以复现的话, 只要重新编译运行就可以了.

但是并不能,所以当前的难点在于, 需要利用旧的 coredump来做信息补全.

分析思路:

gdb elf的调试信息是通过:  内存地址->符号信息->调试信息->源代码  这样一个关联关系串起来的.  其中内存地址保存在coredump文件中, 

符号信息保存在二进制文件libqat_s.so中, 调试信息同样保存在二进制文件libqat_s.so中. 

所以我当前的问题是:二进制文件中没有调试信息.

于是我接下来需要做的是以下两件事情:

1.  在二进制文件中添加调试信息, 

2.  保持内存地址到符号信息的关联关系不变.

方法

操作方法比较简单:使用以下两个步骤,之后重新使用gdb打开coredump文件,便可以观察到想要的调试信息了.

1 增加编译选项-g,并生成新的rpm包.与debuginfo rpm包.

2 解压这两个包,分别将文件/opt/quickassist/lib/libqat_s.so与/usr/lib/debug/opt/quickassist/lib/libqat_s.so.debug 覆盖到目标机上.

解压rpm包的方法:

rpm2cpio qatdriver-4.10.0.14-1.el7.x86_64.rpm |cpio -idvm

最终的成功的效果:

(gdb) 
#0  ASYNC_WAIT_CTX_get_fd (ctx=<optimized out>, key=0x7f7e3f49db46, fd=fd@entry=0x7f7e3d1d6c9c, custom_data=custom_data@entry=0x7f7e3d1d6ca0) at crypto/async/async_wait.c:73
#1  0x00007f7e3f48c82f in qat_wake_job (job=<optimized out>, jobStatus=<optimized out>) at qat_events.c:334
#2  0x00007f7e3f1ef26c in LacPke_MsgCallback (pRespMsg=<optimized out>) at /usr/src/debug/qat1.7.l.4.10.0.14/quickassist/lookaside/access_layer/src/common/crypto/asym/pke_common/lac_pke_qat_comms.c:502
#3  0x00007f7e3f20e617 in adf_user_notify_msgs_poll (ring=ring@entry=0x55cf55e37750) at /usr/src/debug/qat1.7.l.4.10.0.14/quickassist/lookaside/access_layer/src/qat_direct/src/uio_user_ring.c:272
#4  0x00007f7e3f20a618 in adf_pollRing (accel_dev=<optimized out>, pRingHandle=pRingHandle@entry=0x55cf55e37750, response_quota=response_quota@entry=0)
    at /usr/src/debug/qat1.7.l.4.10.0.14/quickassist/lookaside/access_layer/src/qat_direct/src/adf_user_transport_ctrl.c:616
#5  0x00007f7e3f20a977 in icp_adf_pollInstance (trans_hnd=trans_hnd@entry=0x7f7e3d1d6e50, num_transHandles=num_transHandles@entry=2, response_quota=response_quota@entry=0)
    at /usr/src/debug/qat1.7.l.4.10.0.14/quickassist/lookaside/access_layer/src/qat_direct/src/adf_user_transport_ctrl.c:858
#6  0x00007f7e3f203b99 in icp_sal_CyPollInstance (instanceHandle_in=<optimized out>, response_quota=response_quota@entry=0) at /usr/src/debug/qat1.7.l.4.10.0.14/quickassist/lookaside/access_layer/src/common/ctrl/sal_crypto.c:2963
#7  0x00007f7e3f48e419 in qat_timer_poll_func (ih=<optimized out>) at qat_polling.c:254
#8  0x00007f7e425dbea5 in start_thread () from /lib64/libpthread.so.0
#9  0x00007f7e414d796d in clone () from /lib64/libc.so.6

实施过程中, 主要牵扯到两个方面的知识,

1.  rpmbuild工具都做了什么

        a. rpmbuild工具在调用gcc生成二进制文件后, 会将其strip并保存在 /opt/quickassist/lib/libqat_s.so,  strip之后的文件没有调试信息. 但是有符号信息.

        b. 原始的带有调试信息的二进制文件, 保存在这个地方: /usr/lib/debug/opt/quickassist/lib/libqat_s.so.debug,它的里边会保存一个libqat_s.so的CRC, gdb启动加载的时候会检测,检查不过会warning (从而导致加载失败??)

        c. 会把libqat_s.so.debug中 调试信息->源代码 这部分信息修改,让其重新指向到目录/usr/src/debug/下边去,之后可以把代码树考到这里来,gdb可以进行关联.

2.  elf格式的结构,以及对readelf工具的使用.主要用来观察与验证生成信息的正确性.

  用来对比每次编译后的二进制文件中,符号信息的地址偏移是否正确(也就是分析思路中的"二").方法见下文.

要点

1.地址如何关联到符号?

以 0x00007f7e3f1ef26c in LacPke_MsgCallback at lac_pke_qat_comms.c:502 为例

查看运行时地址:

# cat /proc/119329/maps |grep libqat_s.so
7f7e3f1ce000-7f7e3f230000 r-xp 00000000 08:03 557012                     /opt/quickassist/lib/libqat_s.so (deleted)
7f7e3f230000-7f7e3f42f000 ---p 00062000 08:03 557012                     /opt/quickassist/lib/libqat_s.so (deleted)
7f7e3f42f000-7f7e3f431000 r--p 00061000 08:03 557012                     /opt/quickassist/lib/libqat_s.so (deleted)
7f7e3f431000-7f7e3f432000 rw-p 00063000 08:03 557012                     /opt/quickassist/lib/libqat_s.so (deleted)

我们这里是coredump, 运行时信息已经看不见了, 在gdb里可以用下面的命令看:

(gdb) info proc mappings

查看symbol在二进制文件中的偏移:

[root@alb-p5vtwl03os-ins qatdriver-withg]# readelf -s /opt/quickassist/lib/libqat_s.so |grep LacPke_MsgCallback
   256: 0000000000021180   425 FUNC    GLOBAL DEFAULT    9 LacPke_MsgCallback

计算一下:

>>> print('%#x' % (0x7f7e3f1ce000 + 0x0000000000021180))
0x7f7e3f1ef180
>>> print('%d' % (0x00007f7e3f1ef26c - (0x7f7e3f1ce000 + 0x0000000000021180)))
236

代码:

400:void LacPke_MsgCallback(void *pRespMsg)
401:{
    ... ...
500:    /* call the client callback */
502:    (*pCbFunc)(status, pass, instanceHandle, &cbData);
503:}

解释:

7f7e3f1ce000 是运行时动态库加载在内存中的绝对地址
0000000000021180 是符号 LacPke_MsgCallback 在二进制文件中的静态相对地址
0x7f7e3f1ef180 是coredump文件中用来查找符号的地址, gdb正是通过这三者的对应关系查找到符号的.

(但是为什么差了236 ?? 大概是因为要对齐叭: https://jvns.ca/blog/2018/01/09/resolving-symbol-addresses/

2 elf的相关用法

查看符号:

readelf -s /usr/lib/debug//opt/quickassist/lib/libqat_s.so.debug

查看所有段:

readelf -S /usr/lib/debug//opt/quickassist/lib/libqat_s.so.debug

查看具体每一段的内容:

# 使用段名
readelf -x .debug_line /usr/lib/debug//opt/quickassist/lib/libqat_s.so.debug
# 使用段号
readelf -x 22 /usr/lib/debug//opt/quickassist/lib/libqat_s.so.debug

查看是否strip了

[root@T9 temp]# file ./opt/quickassist/lib/libqat_s.so
./opt/quickassist/lib/libqat_s.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped

对比一下带g不带g的,大小一样,符号一样,内容稍有不同

# md5sum libqat_s.so
876e56e347333b058cda4da89e19044f  libqat_s.so
# md5sum libqat_s.so.old 
f9ce4226250251f20db6508098ffff45  libqat_s.so.old
# ll libqat_s.so
-rwxr-xr-x 1 root root 408472 Jan  6 18:52 libqat_s.so
# ll libqat_s.so.old 
-rwxr-x--x 1 root root 408472 Jan  6 18:52 libqat_s.so.old
# readelf -s libqat_s.so |grep LacPke_MsgCallback
   256: 0000000000021180   425 FUNC    GLOBAL DEFAULT    9 LacPke_MsgCallback
# readelf -s libqat_s.so.old |grep LacPke_MsgCallback
   256: 0000000000021180   425 FUNC    GLOBAL DEFAULT    9 LacPke_MsgCallback

3 其他

1.strip之后的二进制文件在内容上,与是否使用了"-g",没有关系.

2.怎么观察一个二进制是否使用的"-g"编译?目前只能通过文件的大小,和.debug_xxx段的大小和内容来区分,我还没有找到别的办法.他们包含的段的种类(也就是readelf -S的打印结果)是没有区别的.

参考阅读:

[debug] 使用rpmbuild时gdb怎么找到debuginfo

原文地址:https://www.cnblogs.com/hugetong/p/14243714.html