栈帧总结
用下面这个简单的C语言程序来讲解函数栈帧
#include<stdio.h>
int Add(int n_a, int n_b)
{
int z = n_a + n_b;
return z;
}
int main()
{
int a = 0;
int b = 0;
int c = Add(a, b);
printf("%d", c);
return 0;
}
在执行时,main函数被mainCRTStartup函数所调用,而一个函数要执行的话,必须在内存从中开辟一段空间,
因为函数是在栈上开辟空间的,所以ebp表示栈底地址,esp表示栈顶的地址(ebp,esp为寄存器来维护这块空间)
进入main函数后,转到反汇编,下图就是刚刚进入main函数的反汇编,第一句将ebp压入栈顶,第二句表示ebp与esp指向
同一位置,第三句表示,esp向上开辟了一块空间(为main函数开辟的),大小为76字节(下图一格代表4个字节)。第四句,第五句,第六句表示向栈顶压入ebx,esi,edi;
接下来第一句表示将ebp-4Ch的地址放入edi中,将19(13h)放入ecx中,将0CCCCCCCCh放入eax中,最后一句的意思是连续拷贝CCCCCCCC从edi(ebp-4Ch)开始,一次向下移动四个字节,连续19次。如下图所示
接下来的两句试将a,b,ret的值存入ebp-4和ebp-8,ebp-0c中(下图中一格是四个字节)
将ebp-8中的东西(20)存入eax,然后将eax压入栈顶,同样将ebp-4(10)存入ecx中,然后ecx压入栈顶,这个过程就是我们调用函数时形参创立。然后call表示调用Add函数,但是要先记录一个地址,为了调用函数之后可以返回原来的地方。
这时进入Add函数内,第一句将main函数的ebp压入栈,并且让esp与edp指向同一个位置,下面的操作与main函数创建的步骤相同,在这里就不做介绍了,创建好的图如下
将ebp-8中的东西放入eax中,然后第二句是将ebp-0c中的东西与eax中的东西相加,并且存入eax中,然后将eax中的结果存入ebp-4所指的(z )位置中。
将ebp-4所指位置中的30存入eax中,为什么呢?因为当一个函数结束之后,ebp-4现在这个位置会销毁,这样就无法传回我们想要的值了,所以要存入eax中,
接下来前三句的意思是edi,esi,ebx都出栈,第四句的意思是esp指向ebp的位置,第五句是ebp出栈,而ebp所存的内容是main函数的ebp,所以ebp直接指向main函数ebp的位置。而此时esp指向(call指令的下一个地址),最后一句是return的意思使esp向下移动一个位置,因为原来esp指向的内容是(call指令的下一个地址),所以当函数返回时可返回到原来的位置。这个过程也就是函数是如何销毁。
此时将esp+8的意思是esp向下移动八个字节,这是形参也就被销毁了,然后将eax中的值放入ebp-0c(也就是ret中)中。
这整个过成就是函数的栈帧是如何的创建和销毁。