進程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的打印結果)是沒有區別的.
參考閱讀:
浙公網安備 33010602011771號