背景
我的一个运行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的打印结果)是没有区别的.
参考阅读: