天天看点

Linux Debugging(一): 使用反汇编理解C++程序函数调用栈

        拿到coredump后,如果看到的地址都是????,那么基本上可以确定,程序的栈被破坏掉了。gdb也是使用函数的调用栈去还原“事故现场”的。因此理解函数调用栈,是使用gdb进行现场调试或者事后调试的基础,如果不理解调用栈,基本上也从gdb得不到什么有用的信息。当然了,也有可能你非常“幸运”, 一个bt就把哪儿越界给标出来了。但是,大多数的时候你不够幸运,通过log,通过简单的code walkthrough,得不到哪儿出的问题;或者说只是推测,不能确诊。我们需要通过gdb来最终确定coredump产生的真正原因。

       本文还可以帮助你深入理解c++函数的局部变量。我们学习时知道局部变量是是存储到栈里的,内存管理对程序员是透明的。通过本文,你将明白这些结论是如何得出的。

       栈,是lifo(last in first out)的数据结构。c++的函数调用就是通过栈来传递参数,保存函数返回后下一步的执行地址。接下来我们通过一个具体的例子来探究。

可以使用以下命令将上述code编程成汇编代码:

 g++ -g -s -o0 -m32 main.cpp -o-|c++filt >main.format.s

c++filt 是为了demangle symbols。-m32是为了编译成x86-32的。因为对于x86-64来说,函数的参数是通过寄存器传递的。

main的汇编代码:

对于call指令,这个指令有两个作用:

<code>func0</code>函数调用完之后要返回到<code>call</code>的下一条指令继续执行,所以把<code>call</code>的下一条指令的地址压栈,同时把<code>esp</code>的值减4。

修改程序计数器<code>eip</code>,跳转到<code>func0</code>函数的开头执行。

至此,调用func0的栈就是下面这个样子:

Linux Debugging(一): 使用反汇编理解C++程序函数调用栈

下面看一下func0的汇编代码:

需要注意的是esp也是留了5个地址空间给func0使用。并且ebp的下一个地址就是留给局部变量b的,调用栈如图:

Linux Debugging(一): 使用反汇编理解C++程序函数调用栈

通过调用栈可以看出,8(%ebp)其实就是传入的参数1234。

func1的代码:

<code>leave</code>指令,这个指令是函数开头的<code>push %ebp</code>和<code>mov %esp,%ebp</code>的逆操作:

把<code>ebp</code>的值赋给<code>esp</code>

现在<code>esp</code>所指向的栈顶保存着<code>foo</code>函数栈帧的<code>ebp</code>,把这个值恢复给<code>ebp</code>,同时<code>esp</code>增加4。注意,现在esp指向的是这次调用的返回地址,即上次调用的下一条执行指令。

最后是<code>ret</code>指令,它是<code>call</code>指令的逆操作:

现在<code>esp</code>所指向的栈顶保存着返回地址,把这个值恢复给<code>eip</code>,同时<code>esp</code>增加4,<code>esp指向了当前frame的栈顶</code>。

修改了程序计数器<code>eip</code>,因此跳转到返回地址继续执行。

调用栈如下:

Linux Debugging(一): 使用反汇编理解C++程序函数调用栈

至此,func1返回后,控制权交还给func0,当前的栈就退化成func0的栈的情况,因为栈保存了一切信息,因此指令继续执行。直至func0执行

leave

ret

以同样的方式将控制权交回给main。

     到这里,你应该知道下面问题的答案了:

1. 局部变量的生命周期,

2. 局部变量是怎么样使用内存的;

3. 为什么传值不会改变原值(因为编译器已经帮你做好拷贝了)

4. 为什么会有栈溢出的错误

5. 为什么有的写坏栈的程序可以运行,而有的却会crash(如果栈被破坏的是数据,那么数据是脏的,不应该继续运行;如果破坏的是上一层调用的bp,或者返回地址,那么程序会crash,or unexpected behaviour...)

    小节一下:

   1. 在32位的机器上,c++的函数调用的参数是存到栈上的。当然gcc可以在函数声明中添加_attribute__((regparm(3)))使用eax, edx,ecx传递开头三个参数。

   2. 通过bp可以访问到调用的参数值。

   3. 函数的返回地址(函数返回后的执行指令)也是存到栈上的,有目的的修改它可以使程序跳转到它不应该的地方。。。

   4. 如果程序破坏了上一层的bp的地址,或者程序的返回地址,那么程序就很有可能crash

   5. 拿到一个coredump,应该首先先看有可能出问题的线程的的frame的栈是否完整。

   6. 64位的机器上,参数是通过寄存器传递的,当然寄存器不够用就会通过栈来传递

支持原创,转载请注明出处:anzhsoft  http://blog.csdn.net/anzhsoft/article/details/18730605