• 进程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

  • 相关阅读:
    【NOIP2016提高A组8.12】奇袭
    【NOIP2016提高A组8.12】礼物
    tarjan算法
    【NOIP2016提高A组8.12】总结
    【NOIP2016提高A组8.11】自然数
    【NOIP2016提高A组8.12】通讯
    【NOIP2016提高A组8.11】种树
    【NOIP2016提高A组8.11】钱仓
    【NOIP2016提高组A组7.16】大鱼海棠
    【NOIP2012模拟8.20】Memory
  • 原文地址:https://www.cnblogs.com/hugetong/p/14243714.html
Copyright © 2020-2023  润新知