linux/unix 段错误捕获【续】

本文为" 在C/C++中捕获段错误,打印出错的具体位置"的续篇,进一步解决涉及动态链接库的情况。
背景知识:
·linux/unix下动态链接库的基本原理
·/proc/pid/maps文件的基本格式
·动态链接库:在进程执行过程中动态加载,进程间可以共享代码,可用在发布升级包等场合
概述:
用户自己编写的代码均编译进了可执行文件里的时候," 在C/C++中捕获段错误,打印出错的具体位置"里给出了在发生段错误(或其他错误,读者可以修改附件里面的头文件,增加捕获的错误类型)的情况下,输出代码执行路径的方法。本文在此基础上,分析了当用户编写的部分代码不在可执行文件中时,如何获取代码执行路径。
为简洁起见,后文用"原方法"指代前一文章内的分析方法。
正文:
先给出本文示例代码( segvCatch_ext.rar)
命令行下执行的命令行次序如下:
root@redhat tcpBreak# g++ -fPIC -shared -g -o libtest.so lib.cpp
root@redhat tcpBreak# g++ -g test.cpp ./libtest.so
root@redhat tcpBreak# ./a.out
root@redhat tcpBreak# addr2line...(省略)
一、出错代码在动态链接库内时,原方法的输出
有些情况下,我们会采用动态链接库,如果出错代码行恰巧在动态链接库内,原方法仍可得到出错时的地址。例如:

  1. signal8 catched when running code at 8048ab3
  2. signal8 catched when running code at 4001771b
  3. signal8 catched when running code at 400176fd
    此例中,调用addr2line小工具的输出为
  4. root@redhat tcpBreak# addr2line 8048ab3 4001771b 400176fd -s -C -f -e a.out
  5. main
  6. test.cpp:15
  7. ??
  8. ??:0
  9. ??
  10. ??:0
    显然,后面两个地址翻译不出来了,因为其实出错代码根本不在可执行文件 a.out 内,而是位于一个动态链接库内。
    二、动态链接库的偏移地址
    动态链接库无非就是编译后的代码,里面有一些基本的段、符号信息。如果出错代码行在动态链接库内,那必然可以从动态链接库内找到出错时的代码行号。
    好吧,那就让我们试一下:
  11. root@redhat tcpBreak# addr2line 4001771b 400176fd -s -C -f -e libtest.so
  12. ??
  13. ??:0
  14. ??
  15. ??:0
    还是翻译不出来。当然出不来了,因为进程挂掉时输出的地址,和动态链接库文件内的静态偏移地址根本就不是一回事。所以我们需要知道出错时,所输出的代码地址与动态链接库偏移地址之间的关系。
    事实上,每一个进程都对应了一个 /proc/pid 目录,下面记载了诸多与该进程相关的信息,其中有一个maps文件,里面记录了各个动态链接库的加载地址。我们只需要根据所得到的出错地址,以及这个maps文件,就可以得出具体是哪一个库,相应的偏移地址是多少。本文用例产生的输出为:
  16. -------------------------- 进程挂掉时的MAPS文件 --------------------------
  17. 08048000-08049000 r-xp 00000000 00:09 17256 /mnt/hgfs/share/net/tcpBreak/a.out
  18. 08049000-0804a000 rw-p 00001000 00:09 17256 /mnt/hgfs/share/net/tcpBreak/a.out
  19. 0804a000-0804b000 rwxp 00000000 00:00 0
  20. 40000000-40015000 r-xp 00000000 08:02 271023 /lib/ld-2.3.2.so
  21. 40015000-40016000 rw-p 00014000 08:02 271023 /lib/ld-2.3.2.so
  22. 40016000-40017000 rw-p 00000000 00:00 0
  23. 40017000-40018000 r-xp 00000000 00:09 17255 /mnt/hgfs/share/net/tcpBreak/libtest.so
  24. 40018000-40019000 rw-p 00000000 00:09 17255 /mnt/hgfs/share/net/tcpBreak/libtest.so
  25. 40019000-4001b000 rw-p 00000000 00:00 0
  26. 40026000-400cf000 r-xp 00000000 08:02 350892 /usr/lib/libstdc++.so.5.0.3
  27. 400cf000-400d4000 rw-p 000a9000 08:02 350892 /usr/lib/libstdc++.so.5.0.3
  28. 400d4000-400d9000 rw-p 00000000 00:00 0
  29. 400d9000-400fa000 r-xp 00000000 08:02 286922 /lib/tls/libm-2.3.2.so
  30. 400fa000-400fb000 rw-p 00020000 08:02 286922 /lib/tls/libm-2.3.2.so
  31. 400fb000-40102000 r-xp 00000000 08:02 271272 /lib/libgcc_s-3.2.2-20030225.so.1
  32. 40102000-40103000 rw-p 00007000 08:02 271272 /lib/libgcc_s-3.2.2-20030225.so.1
  33. 40103000-40104000 rw-p 00000000 00:00 0
  34. 42000000-4212e000 r-xp 00000000 08:02 286920 /lib/tls/libc-2.3.2.so
  35. 4212e000-42131000 rw-p 0012e000 08:02 286920 /lib/tls/libc-2.3.2.so
  36. 42131000-42133000 rw-p 00000000 00:00 0
  37. bfffd000-c0000000 rwxp ffffe000 00:00 0

  38. --------------------------- 进程挂掉时的栈帧 --------------------------
  39. signal8 catched when running code at 8048ab3
  40. signal8 catched when running code at 4001771b
  41. signal8 catched when running code at 400176fd

显然 4001771b 400176fd 对应的库是 libtest.so,偏移地址分别为 71b 6fd。
三、临门一脚
知道了对应的动态链接库和偏移地址后,我们进一步用 addr2line 将这个偏移地址翻译一下就可以了。

  1. root@redhat tcpBreak# addr2line 71b 6fd -s -C -f -e libtest.so
  2. a()
  3. lib.cpp:14
  4. b()
  5. lib.cpp:10
    至此,大功告成。
    四、简而言之
    不管是否有用到动态链接库,我们将原方法得到的输出,结合进程挂掉时maps文件的内容,就可以得到代码出错时的执行路径。根据代码所在部分,指定相应的文件给 addr2line 的 -e 参数即可。对于上面那个例子:
  6. root@redhat tcpBreak# addr2line 8048ab3 -s -C -f -e a.out
  7. main
  8. test.cpp:15
  9. root@redhat tcpBreak# addr2line 71b 6fd -s -C -f -e libtest.so
  10. a()
  11. lib.cpp:14
  12. b()
  13. lib.cpp:10
    本文发布的捕获出错执行路径的方法:
    1 在含有main函数的那个源码文件里,包含segvCatch_ext.h这个头文件
    2 具体如何解析出错时代码的执行路径,阅读segvCatch_ext.h头部的说明
    适用场景已经在前一篇文章里面描述过了,有问题可以给我发邮件(fireworks2@foxmail.com)。
    五、似有余味
    一个程序启动后,地址是如何进行映射的,MAPS文件是怎么生成的,库又是怎么加载的,自行编写动态链接库时,有什么注意事项...
    这些问题我也不甚明了,因为我自己也没深究过,以后有时间可能会陆续补到博客里面。
    参考资料:
    1 Linux debug : addr2line追踪出错地址, http://www.linuxidc.com/Linux/2011-05/35780.htm
    2 addr2line,可以根据一个地址打印出对应的代码行, http://archive.cnblogs.com/a/1996110/
    3 Linux下 /proc/maps 文件分析, http://bbs.chinaunix.net/viewthread.php?tid=2000825
    4 《程序员的自我修养---链接、装载与库》,俞甲子,石凡,潘爱民. (PS 此书甚好,推荐大家阅读)
相关推荐
MuMuMu122336 分钟前
工业园区 VOCs 绿岛集中治理:越华环保集团项目技术方案与工程实践
大数据·运维
漫路在线2 小时前
无境红日靶场五 wp
网络·安全·web安全
sxstj2 小时前
Ubuntu 完整安装并启用 Flatpak 教程
linux·运维·ubuntu
国际云,接待2 小时前
Nginx 后面接负载均衡,502/504 到底该先查哪里?
运维·nginx·负载均衡
Sylvia33.2 小时前
从轮询到推送:足球数据API架构演进与火星数据技术拆解
java·服务器·网络·python·websocket·架构
天疆说3 小时前
Ubuntu 26.04 下 Intel Meteor Lake 核显 + NPU 驱动配置与 PyTorch NPU 推理实测
linux·pytorch·ubuntu
mounter6253 小时前
突破单点限制:BPF 多跟踪点高效附加机制与新 ftrace 架构解析
linux·ebpf·linux kernel·kernel·ftrace
刘某的Cloud3 小时前
Galera Cluster部署 mariadb 节点down机,log sequence number恢复
linux·运维·数据库·负载均衡·mariadb·集群·高可用
小此方3 小时前
Re:Linux系统篇(五十四)线程篇 · 七:互斥锁的底层实现原理:从硬件上下文、原子交换指令(Swap)到并发封装实战
linux·运维·驱动开发
2401_834636993 小时前
从零吃透 K8s 网络:ServiceIngressMetalLB 实操手册
网络·容器·kubernetes