浅谈函数栈帧
很抱歉我用的是vs19,这个笔记本不可以装两个vs,性能不够,不是什么好电脑,但vs19只有调用main函数的那个“东西”看不到,其他的还是可以看到很多的,在开辟空间上优化很多。废话不多说直接上
错误的地方本文有的地方ebp写成edp 由于修改地方太多就不改了,基本是写到一半才发现哎
什么叫栈帧
可以直接说他就是一个空间,再准确点是存储空间。
该明白的一点知识
1.ebp(栈底指针),esp(栈顶指针)这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。
2.从高地址向低地址移动
3.压栈push :esp上移也就是朝低地址移动
出栈pop:也叫弹出,栈顶元素弹出,esp下移
4.每一个函数调用,都要在栈区创建一个空间
讲例 #include<stdio.h>
int Add(int a, int b)
{
int sum = 0;
sum = a + b;
return sum;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n",c);
return 0;
}
讲例就不说了大家都可以看的明白。
调试
我们首先进入调试,然后进入反汇编,只有底层才能讲的透彻。打开调用堆栈窗口。我们粗略调一遍会发现在调用堆栈里面我们看到了main函数,说明什么,有人在调用它,vs19我没看到,所以借vs13的来用一下
我们非常模糊的看到main函数被__tmainCRTStartup()调用,而这个函数还被mainCRTStartup调用
push ebp 就是说push压栈 把ebp压到esp顶部导致esp上移
mov ebp,esp 是把esp的地址交给edp
这两步实现了esp,edp都指向了同一个地址
sub esp,0E4h sub是把esp减去0E4h导致esp上移也就是开辟了空间
3push 依次压栈edx,esi,edi
lea (load effective address)加载有效地址
word是两个字节,dword(double word)双字四字节
上面的意思就是从edi开始向下的9个 字节全都初始化成eax的内容CCCC...
(这里当时忘了监测,重新调试了,所以地址都变了)但基本现象没怎么变到了这里也就意味着main函数栈帧的开辟 也就准备完了
mov 把0Ah(也就是10)放到ebp-8的位置上,而ebp-8实际上就是为a开辟一个空间
至于为什么a,b,c为什么不是紧密排的,这是编译器的原因,是为了防止数据紧密容易出错
mov 把ebp-20给eax,上面可以看到那是b的空间,然后把eax压栈一下,把a给ecx再把ecx压栈一下,然后call我愿称他是奇计,我们可以非常清楚的看到a'上面的就是call指令的下一条指令的地址。他这一步是 在调用函数的同时把下一条指令的地址给压上去,这是回来的标记
这时才是真正的来到我们的Add函数上面和我们讲main函数栈帧一样
参数是从右向左压栈的,从上面我们也可以清楚的看到形参不是在Add函数内部创建的,而是回来找我们传参的空间,也直接的证明了形参是实参的临时拷贝这句话
请注意大风大浪过去了,千万不要在阴沟里面翻船
这里就是返回的变量明明销毁了,那如何把值给带回来的。把ebp-8里面的值也就是sum 放到eax里面,这里的eax可是寄存器啊,寄存器是不会因为程序退出就销毁的,相当于拿一个全局的寄存器把他保存起来,等我们到main函数里面再把它拿出来
pop弹出 把栈顶的元素取出放到edi里面去,依次pop三次,esp就加4加4往下走。当我们函数调用完了那这个空间就没必要存在了,所以把ebp的地址给esp
当esp指到这的时候pop一下把栈顶的元素弹出来,他里面放的是main函数的栈底指针,把结果弹到ebp里面去就可以瞬间到main栈底了
ret这条指令就是栈顶弹出call下一条指令地址然后跳过去,回来后就到了call下一条指令地方
这个add 就是把形参的空间还给操作系统
然后把eax的值给ebp-32空间就是c空间
结束
到了这里也就结束啦,只是非常浅显的讲解了一点,肯定还有好多地方没有讲到,寄存器这些就更底层了,也不是我们函数栈帧讲的主要内容,有机会以后再写寄存器的文章吧。