函数栈帧详解

本文深入探讨函数调用的过程,包括ESP和EBP寄存器在维护栈帧中的作用,以及在VS2013下函数如何被调用。文章通过实例代码解析了函数传参、局部变量的创建及其默认值,以及调用与返回机制。在函数调用中,参数从右向左压栈,局部变量在栈帧内分配空间并可能初始化为随机值。函数返回时,通过EBP和ESP恢复调用现场。
摘要由CSDN通过智能技术生成

文章目录

本章详细讲解函数调用、传参、压栈

每一次函数调用,都要在栈区创建一个空间,有两个寄存器esp、ebp来维护这块空间,正在调用哪个函数,esp、ebp就在维护哪块栈帧。
在这里插入图片描述
不同编译器下,能够观察到的函数调用过程不一样。

我们在VS2013编译器下,main函数是被谁调用的?
mian函数被__tmainCRTStartup函数调用;__tmainCTRTStartup函数被mainCRTStartup函数调用。

现有如下代码:

int Add(int n,int m)
{
	int c = n + m;
	return c;
}

int main()
{
	int n = 10;
	int m = 20;

	int c = Add(n,m);
	printf("%d\n",c);
	return 0;
}

我们通过反汇编查看代码执行过程:
1.为__tmainCRTStartup函数开辟栈帧:
在这里插入图片描述
此时寄存器esp和ebp值如下图:
在这里插入图片描述
然后ebp压栈

在这里插入图片描述
ebp压栈(在栈顶上面放元素,并不是栈顶和栈底之间放),栈顶指针要移动,栈顶指针esp地址减少4个字节空间的地址,因为32位平台,指针大小是4个字节。

在这里插入图片描述
将esp的值赋值给ebp

在这里插入图片描述
在这里插入图片描述
2.为main函数开辟栈帧
0E4h是10进制228,将esp的值减去0E4h,esp的值变小,向低地址处移动,那么此时,esp(地址为0063fb40)~ebp(地址为0063fc24)之间的228个字节空间都是为main函数开辟的。
在这里插入图片描述
在这里插入图片描述
ebx入栈:从栈顶位置开始存放

在这里插入图片描述
esi入栈:
在这里插入图片描述
edi入栈:
在这里插入图片描述
在这里插入图片描述

将ebp-24h存入edi中
在这里插入图片描述

下图中三行代码的意思是:从edi开始,向下9*4个字节的空间,内容全部赋值为eax中的值。
在这里插入图片描述
一个word是2个字节,dword是4个字节,将edi后面36个字节的值全部初始化为0xCCCCCCCC,然后edi=ebp;至此,已经为main函数开辟好栈帧。然后执行call指令,开始调用main函数
在这里插入图片描述
在这里插入图片描述
3.在main函数中创建局部变量n,m在这里插入图片描述
给n分配空间,并初始化,值为10;单步执行,如下,所以如果创建局部变量不给初始化,默认值就是随机值,VS下随机放入0xCCCCCCCC,不同编译器下放的随机值不同。

继续执行,将14h放到ebp-14h中
在这里插入图片描述
因为创建局部变量c没有初始化,所以从c默认值为随机值。
在这里插入图片描述
在这里插入图片描述

下面开始准备调用Add函数:
1.函数传参:
将参数m的值传给eax,然后eax压栈
在这里插入图片描述
继续执行,把n的值赋值给ecx,然后ecx压栈

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.调用Add函数,为Add函数准备栈帧
执行call指令,调用Add函数
在这里插入图片描述
其中006819f0是call指令的下一条指令的地址,因为调用Add函数结束,还要返回到调用的地方,继续执行下一条指令
在这里插入图片描述
在这里插入图片描述

继续单步执行,进入Add函数内部
为Add函数准备栈帧
在这里插入图片描述
在这里插入图片描述

接下来将ebx、esi、edi压栈

在这里插入图片描述
在这里插入图片描述

接下来,edi = ebp - 0Ch = 0x0063fb18
在这里插入图片描述
ecx的值为3
eax的值为0xCCCCCCCC,然后为栈帧空间初始化一些值,
从edi地址开始向下3*4个字节的空间全部赋值为0xCCCCCCCC
在这里插入图片描述
在这里插入图片描述
因为变量c还没给初始值时,默认值为随机值
在这里插入图片描述

找到传过来的参数,将n的值给eax,n在ebp+8的空间上,然后将eax加上m的值,m的值在ebp+12的空间上,再把eax的值给c
在这里插入图片描述
将c的值放到eax中,然后edi、esi、ebx依次出栈,esp栈顶指针增大

在这里插入图片描述
在这里插入图片描述
将esp加上CCh(204个字节)
在这里插入图片描述

然后比较esp和ebp的值,接着ebp出栈(main函数ebp出栈),这里pop ebp的意思是把栈顶的元素弹出来,放到ebp中去,这里的ebp是之前存储的main函数的ebp,所以弹出ebp后,ebp又回到main函数的ebp了,那么esp随着栈顶元素的出栈,回到main函数,然后esp和ebp维护main函数栈帧空间,ebp是寄存器,我们存储在空间的都是地址。寄存器是独立于内存的,是集成到cpu上面的,在代码中(内存中)都是可以使用寄存器的。
![在这里插入图片描述

执行ret指令
在这里插入图片描述
回到Add函数call指令下一条指令的地方在这里插入图片描述
在这里插入图片描述

然后将esp+8
在这里插入图片描述
在这里插入图片描述
将寄存器eax的值给c。
在这里插入图片描述

1.局部变量是怎么创建的?
先给函数开辟栈帧,然后给栈帧空间初始化一些值(例如0xcccccccc),然后在栈帧中给局部变量分配空间。

2.为什么局部变量的值是随机值?
因为我们会先放入一些随机值,然后创建局部变量时,如果给初始值,那么初始值就会覆盖随机值,如果没有初始化,那么默认值就是随机值。

3.函数是怎么传参的?
在调用函数之前,先进行传参,将形参从右向左压栈。在函数内部是通过指针偏移量来找到形参的(在栈帧内部并不为形参再分配空间)。

4.形参和实参是什么关系?
形参是实参的一份临时拷贝,改变形参,不会影响实参的变化。

5.函数调用是怎么实现的?
先进行传参,然后执行call指令,记住call指令后面的一条指令的地址,然后为函数开辟栈帧,为栈帧空间初始化一些值,在栈帧中为局部变量分配空间(初始化,不初始化默认为随机值),在函数内部使用形参,根据指针偏移量来获取形参的值,在栈帧内部是不为形参分配空间的。

6.函数调用结束后怎么返回的?
在调用函数之前,先把call指令的下一条指令的地址压栈,然后将上一个函数的栈底指针ebp先压栈存储起来,当前函数调用完毕,弹出ebp,找到上一个函数的ebp,回到上一个函数的栈帧空间,然后回到上一个函数中调用该函数的地方。返回值通过寄存器的方式带回上一个函数,寄存器不会随着函数调用结束就销毁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值