在该系列的第一篇文章中已经教给大家一个通用的方法就是利用backtrace函数和addr2line命令定位问题,戳这里查看如何轻松搞定“Segmentation fault”,看这篇就够了!
至于如何把gdb异常调用栈不包含动态库的实现方法移植到任意平台(ARM/MIPS...)的方法,需要复习的小伙伴看这里:大招!如何把Gdb异常调用栈功能移植到任意平台?
今天主要分析包含动态库的异常调栈,如何实现。
先看下异常调用栈包含动态库的情况下gdb下bt的显示结果:
这个打印后三行的实现,前面几篇文章已经分析了。今天主要是讲第一行,对于调用栈包含动态库,需要打印出文件名和函数名,这里为什么没有行号?你可以结合编译原理去思考下呢。
下面需要做的就是自己编码实现这个功能,并应用于各种平台(ARM/MIPS...)。
前面讲过,当系统抛出异常,会触发异常调用函数,在该函数内线调用backtrace函数获取调用栈信息,然后执行dump_backtrace可以打印出异常栈。
在dump_backtrace里,先打开一个fd,然后根据addr,调用dump_current_pc_location进行地址解析。
具体到本文目的,修改点涉及两点。
第一,打开的fd,不一定是可执行文件本身,也可能是该可执行文件依赖的动态库,如何去判断?
第二,在dump_current_pc_location里获取符号表,需要区分是静态文件符号表还是动态库的符号表。
先解决第一个问题,首先我们知道每个可执行文件有个地址映射表,这个映射表可以通过/proc/pid/maps节点获取到,如下图所示:
例如0x7ffff7bd9000-0x7ffff7bda000存放的是libtest.so。如果异常栈地址正好落在这个范围,则这个地址属于libtest.so。
是不是有人会质疑上图libtest.so信息明明有四行,为什么我们这里需要解析的地址不会在其他三行?
简言之,第一行是libtest.so的可执行的代码段。具体原理可以参考前面讲的动态补丁机制文章。
下面我们可以把调用backtrace获取的栈地址打印出来,如下所示:
我们可以找到0x7ffff7bd9688这个地址,这个地址在0x7ffff7bd9000-0x7ffff7bda000中,它属于libtest.so。所以我们在解析这个地址时,打开的文件就是打开libtest.so获取fd。
根据之前文章讲的公式“动态库地址=动态库的基地址(随机分配)+ 地址偏移”。所以给dump_current_pc_location传递的pc,是一个偏移地址,需要将0x7ffff7bd9688减去libtest.so的基地址。在maps表中,可知libtest.so的基地址是0x7ffff7bd9000。
这样dump_current_pc_location两个入参都已针对动态库做了修复。dump_current_pc_location里还有一个地方需要小改下,因为bfd库动态库获取符号表的函数有些不同,需要调用bfd_get_dynamic_symtab_upper_bound和bfd_canonicalize_dynamic_symtab取获取动态库符号表。
此时,我们的剩余工作就是根据0x7ffff7bd9688这个地址怎么用代码实现找到libtest.so及其基地址。
回顾下我们手动怎么找的?
在maps中遍历每个已经加载的共享对象,如果目标地址属于某共享对象的地址范围,则大功告成!
Linux有一个实现这个功能的函数,即dl_iterate_phdr,它遍历每个已经加载的共享对象,并需要自己封装一个回调接口,来做地址是否在一个区间的判断操作,如下代码就是这个回调。
编码完,我们进行了验证,同时打印了0x7ffff7bd9688的归属,如下所示:
至此整个dump异常调用栈功能已经全部实现。
欢迎扫码关注,一起学习Linux