浅谈函数栈帧

1,130 阅读4分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

很抱歉我用的是vs19,这个笔记本不可以装两个vs,性能不够,不是什么好电脑,但vs19只有调用main函数的那个“东西”看不到,其他的还是可以看到很多的,在开辟空间上优化很多。废话不多说直接上

错误的地方本文有的地方ebp写成edp 由于修改地方太多就不改了,基本是写到一半才发现哎

什么叫栈帧

可以直接说他就是一个空间,再准确点是存储空间。

该明白的一点知识

1.ebp(栈底指针),esp(栈顶指针)这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。

2.从高地址向低地址移动

3.压栈push :esp上移也就是朝低地址移动

出栈pop:也叫弹出,栈顶元素弹出,esp下移

4.每一个函数调用,都要在栈区创建一个空间

讲例

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空间

结束

到了这里也就结束啦,只是非常浅显的讲解了一点,肯定还有好多地方没有讲到,寄存器这些就更底层了,也不是我们函数栈帧讲的主要内容,有机会以后再写寄存器的文章吧。