C语言·关于栈帧

来源:互联网 发布:电脑淘宝安全中心 编辑:程序博客网 时间:2024/06/03 23:50

近日学到C语言中关于函数的相关知识,其中由函数的调用引入栈帧这个概念。栈帧很重要但对于初次接触的我来说,是有点晦涩难懂的,所以趁热打铁总结记录下来,为以后方便复习巩固。若有错误也请大家不吝赐教。

#include<stdio.h>#include <windows.h>int myadd(int x,int y){int z = x+y;return z;}int main(){int a = 0xAAAAAAAA;int b = 0xBBBBBBBB;int c = myadd(a,b);printf("you should run here!\n");printf("result: %d\n", c);system("pause");return 0;}
运行结果如下:

接下来我们进行调试,观察函数的具体调用过程。首先我们运行然后调出memory和register窗口以便后续观察。然后调出汇编。

一开始ebp和esp分别指向main函数的栈结构的栈底和栈顶,执行ebp-4和ebp-8以后就将a和b先后放入(地址空间是向上增长的,而其中的栈结构是向下而生的,因此栈结构中越往下地址越低),先定义谁则谁先入,即先给a开辟地址空间,在存储a或者b时则是在a的地址空间从下向上存储的。



此时,我们对a,b的定义的语句执行结束,下一步就将调用myadd函数。

其中汇编前四句是在为调用myadd函数做准备,首先将b Mov到eax中然后压栈,再将a Mov到ecx中然后再压栈



前四句执行完成以后将执行下一句call指令,执行call指令第一步事先将正在执行的指令的下一条指令的地址压入栈中,此时就相当于把main函数的返回地址继续放进去。


之后就要执行JMP指令进入我们的myadd函数,而程序需要执行到哪里又EIP说了算,因此JMP指令就是要将我们的EIP修改为一个特定的值。


JMP以后就进入我们的myadd函数,在此之前一直是在我们的main函数栈帧结构里,接下来就将进入我们所需要调用的函数的栈帧结构里。至此就需要形成我们的myadd函数的栈帧结构。因此可以总结出,一个函数的栈帧结构是由他自己实现的。

首先将ebp中的内容压入栈中,再将esp中的内容 MOV到ebp中,所以就会有两个一样的esp。然后sub指令将esp-44h。至此3条语句执行完我们的myadd函数的栈帧结构已经形成。




接下来就是执行下一语句。语句对应的汇编有三句依次是将ebp+8(a) MOV到eax中,再将ebp+0ch(b)和eax中的内容相加,最终将eax移动到ebp-4的位置。



接下来执行的就是返回的语句,将ebp-4(z)  MOV到eax中,然后一直执行到后面的将ebp  MOV到esp中,至此,我们就达到一个目的就是将myadd函数的栈帧结构销毁。接下来就是执行pop指令,pop指两个动作,其一就是将栈顶上移(esp上移)其二就是将pop出来的值放入ebp中。至此也就恢复了main函数的栈帧结构。



最后执行一条ret指令,ret指令也会做两个动作,其一就是弹出栈帧。那么我们这里就是将返回值放入EIP中,而我们一开始存入的值是00401093,因此执行完以后EIP的值就会改为00401093,此时我们已经返回到了call指令的下一条指令,此时我们就将myadd函数的调用工作做完了。虽然说我们完成了myadd函数的调用,但是在功能上返回值还没有进行处理,而且形参实例化的两个临时变量依旧还存在。此时只要我们执行完call指令的下一条指令,我们的整个myadd函数的调用过程才算是完全结束。然后将返回值以寄存器eax返回。



总结:

1 形参实例化的临时变量存在于调用函数和被调用函数的两者栈帧结构之间。

2 main函数传参时从右到左。

3  临时变量为什么具有临时性:函数调用形成栈帧,返回时销毁栈帧,而函数定义的临时变量存在于自己的栈帧里,所以临时变量的生命周期伴随着自己函数的栈帧结构。

4 任何函数都有自己的栈帧结构,且是自己的生成的(通过操纵esp和ebp)。

5 常规情况下,函数的返回值会以寄存器的形式供我们返回。