函数栈帧的创建与销毁(这里使用vs2022进行演示)

本文详细介绍了函数栈帧的原理,包括寄存器的作用(如eax、ebx、ecx、eip、esp和ebp),以及main函数被调用时栈帧的创建过程,涉及局部变量的创建、形参传递和栈帧销毁的步骤。
摘要由CSDN通过智能技术生成

目录

先了解一些东西

 寄存器

 相关的汇编指令

 函数栈帧

了解main函数如何被调用 

main函数的核心代码

局部变量的创建 

函数的调用 

​编辑

栈帧的销毁 


先了解一些东西

 寄存器

我们在开始学习函数栈帧的创建与销毁之前,要先了解一下这几个寄存器:

 

 这里面eax, ebx, ecx是通用寄存器,保存临时数据;

eip是指令寄存器,保存当前指令的下一条指令的地址。

这里面最重要的是esp,ebp这两个寄存器。 我们要了解函数栈帧,就必须了解esp,ebp这两个寄存器。esp,ebp两个寄存器里面存放的都是地址。这里面esp是栈顶寄存器,保存的是函数栈帧的栈顶地址。ebp是栈底寄存器,保存的是函数栈帧的栈底地址。esp与ebp共同维护着函数的栈帧空间。

 相关的汇编指令

 函数栈帧

如图代码 

 

我们应该知道,每一次函数的调用,都要为本函数开辟新的空间,这就是函数的栈帧空间。并且该空间被栈顶栈底寄存器所维护。注意,栈底栈顶寄存器内部只保存一个地址。所以只能维护一个空间。比如如果在调用main函数,那栈底栈顶寄存器就是在维护main函数的栈帧空间。但是如果main函数正在调用另一个函数Add, 那栈底栈顶寄存器就正在维护Add函数的栈帧空间。等到Add结束调用,栈底栈顶寄存器重新维护main函数栈帧空间。

下图为本函数Add没有被调用前的esp,ebp维护栈帧情况

下图为main函数调用Add时的esp,ebp维护栈帧图 

了解main函数如何被调用 

接下来我们来看main函数是怎么被调用的。首先这需要用到函数堆栈 ,先进行调试,点击f10,然后不要动。打开调用栈堆,右击显示外部代码。然后可以看到以下信息:

 

然后,由前面的基础知识我们知道,invoke_main函数会有自己的栈帧空间,而main函数同样有自己的栈帧空间,Add函数也有自己的栈帧空间。 

那么我们就可以想到,在main函数被调用之前应该是这样的

 

现在我们进行继续程序调试。右击反汇编:

 

这是c语言的汇编代码,我们接下来进行逐一解读。

现在来看第一条代码push  ebp,我们在开始的部分已经介绍了一些基本的汇编指令(忘记的小伙伴请返回进行查看),push的意思就是进行压入数据。而在第一条这里,压入的是什么数据?压入的是ebp的数据。而ebp里面保存的是什么?我们说ebp是栈底寄存器,保存的是栈底的地址。所以,这第一条指令的意思就是,将栈底的地址压入栈帧空间。同时esp地址发生变化。那么到底是不是这样呢。我们来看一下vs2022编译器的监视:

首先要注意十六进制打开,这样好观察地址。 

 然后f10走一下。我们会发现,esp指向的地址,确实发生了变化,这个变化是四个字节。并且是变小。正好对应我们压入的数据ebp的地址所占用的内存大小。 

然后一条指令“move    ebp, esp”;意思是将esp的值赋值给ebp。注意,将esp的值赋值给ebp。那么ebp和esp都将指向一处空间。

 

我们接下来再看下一步:“sub    esp, 0E4h”。意思是让esp减去0E4h。此时esp再次发生变化。 esp往上走,指向上面的一个位置。现在ebp和esp所维护的这块空间就是为main函数预开辟好的空间。这一块空间同样可以通过vs2022的内存监视来查看。

 

 

三张内存图片,可以观察到就是ebp到esp的内存空间。而这么一大块空间就是为main函数预开辟的空间。接下来还有操作; 

接下来就是三个连续的压栈:“push    ebx”“push    esi”“push      edi”;意思是将ebx,esi,edi三个的值依次压入栈帧空间,同时esp向上移动。可以观察到,每压栈一个,esp的地址就向上移动四个字节。

 

再然后的四条语句就很重要了。首先是“lea    edi,[ebp - 24h]”这里要知道lea的意思。lea 就是load effective address。意思是加载有效的地址。而 “lea     edi,[ebp - 24h]”就是将[ebp - 24h]这块地址加载放进进入edi之中。 然后看“mov      ecx,9”。意思是将9这个数字赋值给ecx。“mov     eax,0xCCCCCCCC”是将0xcccccccc这个数赋值给eax。最后一步是最重要的

 rep stos    dword ptr es:[edi]  的意思是将从edi所指向地址向上的ecx个,也就是39h个四字节(dword就是double word双字,一个word是两个字节,也就是四个字节)的内存全部赋值成0xCCCCCCCC

这里应该是9 * 4 = 36个字节的空间被赋值为CC,这里应该是这样的:

到这里,main函数的栈帧空间基本创建完成; 

main函数的核心代码

进入main函数后,我们来看main函数的核心代码: 

局部变量的创建 

首先将显示符号名关掉。以便我们进行观察; 

然后我们会看到这样的代码。 

为了方便观察。我接下来直接上图:

 

 以上,就是main函数内部局部变量的创建和初始化。由上面我们可以得出结论。局部变量其实就是在函数栈帧中创建的。

函数的调用 

Add函数的栈帧创建与main函数类似。不过有了创建形参和语句地址传参的过程。就是在ebp地址压入栈帧之前先进行形参的创建。然后是语句地址的压栈。下面为形参的创建与语句地址的压栈过程: 

 

此时我们的栈帧空间是这样的: 

接下来进入Add函数 ,现在就是正式开辟Add函数的栈帧空间。

上图就是Add开辟好的栈帧空间;然后Add内部开辟局部变量后: 

return返回时需要将返回的值存入寄存器之中,通过寄存器返回想要带回的结果。 

栈帧的销毁 

当函数想要返回的时候,栈帧也要开始准备销毁了。这时候需要遵循先入的后出,后入的先出。 

下面为主要流程: 

 

恢复main函数栈帧维护后仍就需要进行多条指令: 

这就是一套大概的栈帧销毁流程 。当main函数被调用完之后也是同样的道理。先弹出三个寄存器的值,再将ebp的值赋值给esp,回收main函数栈帧空间,然后再将invoke_栈底的地址弹出还给ebp。再弹出call的下一条指令。然后返回,如果是正常返回。就将eax中的返回值0返回给操作系统。

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值