栈调用关系跟踪

在发生段错误的时候,打印函数的调用栈信息是定位问题很好的手段,一般来讲,我们可以捕获SIGSEGV信号,在信号处理函数中将函数调用栈的关系打印出来。gdb调试中的backtrace,简称bt就是这个作用。

CU的二娃子前两天写了个Linux下进程崩溃时定位源代码位置,这篇文章写的很好,调用的GNU的backtrace函数,打印了函数的调用栈信息。我想补充一些内容,把这个话题补充的更加丰富一些。

我们遇到的很多难题,前辈都会遇到,很多有分享精神的前辈会写很多精彩的总结。国外布法罗大学的一个牛人总结了跟踪栈调用关系的文章,写了一篇博客。英文水平高的筒子,不要听我JJYY,直接跳转到http://www.acsu.buffalo.edu/~charngda/backtrace.html,去看这篇博文,当然本文提到的第二种方法还是值得一看的。作者提到了4种方法来解决栈调用关系其中二娃子用的是第二种方法。我阅读了self-service linux这本书,这本书也很详尽的描述了栈的结构。我们补充一种方法,自己实现backtrace。

我的栈调用关系如下:

第一种方法 : glibc 提供的 backtrace 函数

先说二娃子的方法: GNU提供的backtrace函数

编译的时候,我们需要加上 -rdynamic 选项,否则的话,符号表信息打印不出来。

这种方法好是好,不过,需要加上-rdynamic选项。否则会出现如下打印:

第二种方法,自己动手丰衣足食的方法。

下面的图来自雨夜听声的博客,函数调用如下图所示。如果有N个参数,将N个参数压栈(顺序也很有意思,希望了解这个的可以看程序员的自我修养),然后是将返回地址压栈,最后是将ebp压栈保存起来。

如果我们只传递一个参数个某个函数,那么我们完全可以根据参数的地址推算出ebp存放的地址,进而得到ebp的值。参数地址-4(32位系统指针的长度为4Byte)可以得到返回地址的位置。参数的地址-8 得到ebp在栈存放的地址。我们一旦得到ebp,我们就可以回朔出整个栈调用。

24774106_1357023120qz5e

先看第一步:getEBP

原理很简单,就是入参的地址下面是返回地址,返回地址的下面是被保存的ebp的地址。

第二步,有了ebp, 我们可以一步一步前回退,得到调用者的栈的ebp,调用者的调用者的栈的ebp,。。。。直到NULL

对这个过程不太理解的筒子可以看下我下面的实验:

光有这个也是不行的,只能拿到栈的信息,和返回地址的信息,拿不到函数名也是白扯。这时候我们可以利用libdl.so,我们用dladdr这个函数可以得到距离入参地址最近的符号表里面的symbol。

把整个函数书写一下:

注意两点:
1 头文件 dlfcn.h
2 编译的时候加上-rdynamic ,同时链接libdl.so 即加上-ldl选项

执行效果如下:

3 第三种是 libunwind。

编译的时候加上 -lunwind -lunwind-x86 ,如果是X86_64,则是 -lunwind -lunwind-x86_64
优点是不需要-rdynamic选项,不需要-g选项。

执行结果如下:

参考文献1 提到了改进的backtrace,同时给出了cario的相关代码,很有意思,感兴趣的可以去读一下。

参考文献
1 http://www.acsu.buffalo.edu/~charngda/backtrace.html (强烈推荐)
2 程序员的自我修养
3 CU 二娃子的博客
4 Self-Service Linux chapter 5 :Stack(推荐)

1 1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部