快!这是关于"Segmentation fault"的最后一趟车!

在该系列的第一篇文章中已经教给大家一个通用的方法就是利用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

你可能感兴趣的:(Linux逆向)