首页 > 其他分享 >函数栈帧的创建和销毁(逐步分析)

函数栈帧的创建和销毁(逐步分析)

时间:2024-12-14 09:29:41浏览次数:11  
标签:销毁 函数 esp mov eax ebp main 栈帧

1. 栈

1.1 什么是栈

什么是栈?在内存中栈是一种特殊的数据结构,它遵循后进先出的规则。内存中的栈通常用于存储临时变量,函数调用的上下文(每一次函数调用,都会在内存上创建空间,用来存放函数参数,函数返回值,临时变量等),返回的地址,栈是由操作系统管理的。

1.2 栈在内存中的表示

在内存中,栈通常被实现为一个连续的内存区域。在经典的操作系统中,栈总是向下增长的(由高地址向低地址)的。

1.3 寄存器

什么是寄存器?寄存器是CPU内部用来存放数据的小型存储区域,常用来存放参与运算的数据和结果。通俗来讲,寄存器是用来存放一些数据的。

ebp:栈底寄存器

esp:栈顶寄存器,esp会随程序的运行不断改变

eax:通用寄存器,保留临时数据,常用于返回值

ebx:通用寄存器,保留临时数据

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

1.4 常见的汇编指令

1.4.1 数据传输指令

mov:将数据从一个位置移动到另一个位置

push:将数据压入栈中

pop:从栈中弹出数据

1.4.2 算术指令

add:加法指令

sub:减法指令

mul:乘法指令

div:除法指令

1.4.3 其他的一些相关指令

call:函数调用,用于压入返回地址和转入目标函数

jump:通过修改eip,转入目标函数,进行调用

ret:恢复返回地址,压入eip

2. 函数栈帧

简单来说,函数栈帧是函数在调用过程中,程序调用栈所开辟的一块空间。一个函数的栈帧就是函数在内存中栈上的一块空间。这些空间用来存放函数中的临时变量,函数的参数,函数的返回值,以及函数调用前后需要保持不变的寄存器和保存的上下文信息。

3. 函数栈帧的创建与销毁

3.1 演示代码

#include<stdio.h>
int Add(int x, int y) {
	int z = 0;
	z = x + y;
	return z;
}
int main() {
	int a = 1;
	int b = 2;
	int ret = 0;
	ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}

3.2 通过反编译观察

在C语言中,反编译是将翻译后的二进制代码还原为高级语言代码。我们可以通过反编译看到汇编指令(以Visual Studio 2019演示),main函数转化的汇编代码如下所示:

3.3 函数栈帧的创建

初步拆解每行汇编代码

00AB18B0         push                ebp

表示把ebp栈底寄存器中的值进行压栈操作。

00AB18B1         mov                 ebp,esp

move指令会把esp中的值存放到ebp中,这就是main函数的ebp。

00AB18B3         sub                 esp,0E4h

sub指令会让esp中的地址剪去一个16进制的数字0xe4,产生新的esp,此时esp是main函数栈帧的esp,结合上面的指令的ebp,esp和ebp之间就维护了一块栈空间,这个栈空间就是为main函数开辟的,也就是main函数的栈帧,后续add函数栈帧的创建与它几乎一样,着一块栈空间用来存放main函数中的局部变量和临时数据等。

00AB18B9         push                ebx  
00AB18BA         push                esi  
00AB18BB         push                edi  

将寄存器ebx,esi,edi的值依次压入栈中,esp进行三次esp-4操作,这三条指令保存了3个寄存器的值在栈区,这三个寄存器的值在函数随后执行中会被修改,所以先保存寄存器原来的值,以便退出函数时恢复。

00AB18BC          lea                 edi,[ebp-24h]  

把ebp-24h的地址,放在edi中。

00AB18BF          mov                 ecx,9  

把9放在ecx中。

00AB18C4          mov                 eax,0CCCCCCCCh  

把0xCCCCCCCC的值放在eax中。

00AB18C9          rep stos            dword ptr es:[edi] 

将从edp-0x2h到ebp这一段内存中的每一个字节都赋值为0xCC,如下图所示:

0xCCCCCCC表示内存中4个字节,可以看成4个一个字节的0xCC

接下来分析main函数中的核心代码:

右击,关闭显示符号名,更方便我们查看变量在内存中的位置。

00AB18D5          mov                 dword ptr [ebp-8],1 

将1存储到ebp-8的地址处,ebp-8的位置就是a变量。

00AB18DC          mov                 dword ptr [ebp-14h],2 

将2存储到ebp-14h的地址处,ebp-14h的位置就是b变量。

00AB18E3          mov                 dword ptr [ebp-20h],0  

将0存储到ebp-20h的地址处,ebp-20h的位置就是ret变量。

00AB18EA          mov                 eax,dword ptr [ebp-14h]  
00AB18ED          push                eax  
00AB18EE          mov                 ecx,dword ptr [ebp-8]  
00AB18F1          push                ecx  

传递b,将ebp-14h处的值(2)放入eax寄存器中

将eax的值压栈,esp-4

传递a,将ebp-8处的值(1)放入ecx寄存器中

将ecx的值压栈,esp-4

上面的四步操作实际上就是表示函数的传参

00AB18F2          call                00AB10B4  
00AB18F7          add                 esp,8  
00AB18FA          mov                 dword ptr [ebp-20h],eax  

call指令是要执行函数调用的逻辑,在执行call指令之前会先把call指令的下一条指令的地址进行压栈操作,这个操作是为了解决当前函数调用结束时要返回到call指令的下一条指令的地方,然后往后执行。

代码执行到Add函数时就要开始创建Add函数的栈帧空间了

在Add函数中创建栈帧的方法和在main函数中是相似的,只是栈帧空间大小不同

00AB1770          push                ebp  
00AB1771          mov                 ebp,esp  
00AB1773          sub                 esp,0CCh  
00AB1779          push                ebx  
00AB177A          push                esi  
00AB177B          push                edi  

将main函数栈帧的ebp保存,压栈,esp-4

将main函数的esp赋值给新的ebp,ebp现在是Add函数的ebp

将esp-0xCC,求出Add函数的esp

将ebx,esi,edi的值进行压栈,并进行3次esp-4操作

00AB177C          lea                 edi,[ebp-0Ch]  
00AB177F          mov                 ecx,3  
00AB1784          mov                 eax,0CCCCCCCCh  
00AB1789          rep stos            dword ptr es:[edi]  

先将ebp-0Ch的地址放入edi中

把3放在ecx中

将0xCCCCCCCC放入eax中

将从edp-0x0Ch到ebp这段的内存中的每个字节都初始化为0xCC

Add函数的核心部分


    int z = 0;
00AB1795          mov                 dword ptr [ebp-8],0  

将0放在ebp-8的地址处,其实就是创建z
    z = x + y;


00AB179C          mov                 eax,dword ptr [ebp+8]  

将ebp+8地址处的数字存储在eax中


00AB179F  add         eax,dword ptr [ebp+0Ch]  

将ebp+0Ch地址处的数字加到eax寄存器中


00AB17A2  mov         dword ptr [ebp-8],eax  

将eax的结果保存在ebp-8的地址处,其实就是放在z中


    return z;
00AB17A5  mov         eax,dword ptr [ebp-8]  

将ebp-8地址处的值放在eax中,其实就是把z的值保存在寄存器eax中,这里是想通过寄存器带回计算的结果,作为函数的返回值。

3.4 函数栈帧的销毁

Add函数栈帧的销毁

00AB17A8          pop                 edi  
00AB17A9          pop                 esi  
00AB17AA          pop                 ebx  

依次在栈顶弹出一个值,放入edi,esi,ebx中,并进行3次esp+4的操作

00AB17B8          mov                 esp,ebp  

将Add函数的ebp的值赋值给esp,相当于回收了Add函数的栈帧空间


00AB17BA          pop                 ebp  

弹出栈顶的值存放在ebp中,栈顶此时的值恰好就是main函数的ebp,esp+4,此时就恢复了main函数的栈帧维护,esp指向main函数栈帧的栈顶,ebp指向了main函数栈帧的栈底


00AB17BB          ret  

ret指令的执行,首先是从栈顶弹出一个值,此时栈顶的值就是call指令下一条指令的地址,此时esp+4,然后直接跳转到call指令下一条指令的地址处。

00AB18F7          add                 esp,8  

esp直接+8,相当于跳过了main函数中压栈的x,y

00AB18FA          mov                 dword ptr [ebp-20h],eax 

将eax中的值,存档在ebp-20h的地址处,其实就是把eax、寄存器中存储的结果存储到main函数中的变量ret中,eax中存储的值就是x和y的和。

至此,完成演示了main函数的创建和Add函数的创建和销毁的过程,希望对大家有所帮助,也感谢大家的阅读。

标签:销毁,函数,esp,mov,eax,ebp,main,栈帧
From: https://blog.csdn.net/2301_76928097/article/details/144447668

相关文章

  • 数据科学、数据分析、人工智能必备知识汇总-----多元函数微分学-----持续更新
    数据科学、数据分析、人工智能必备知识汇总-----主目录-----持续更新(进不去说明我没写完):https://blog.csdn.net/grd_java/article/details/140174015文章目录1.偏导数2.高阶偏导数3.梯度4.雅可比矩阵5.Hessian矩阵6.极值判别法则1.偏导数之前讲的导数,是......
  • STM32_HAL库所用到的函数(持续更新)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档目录文章目录HAL库概念及优点一、对HAl库常见的函数记录1.GPIOHAL库概念及优点HAL(HardwareAbstractionLayer)即硬件抽象层。它是ST(意法半导体)公司为其STM32系列微控制器推出的一种软件库。其主要......
  • [C++]纯虚函数与虚函数
    1.什么是虚函数?1.1定义虚函数是用virtual关键字声明的成员函数,允许子类覆盖它,并支持运行时多态。1.2特点1.动态绑定(运行时决定调用函数):虚函数在运行时,根据对象的实际类型,而不是指针或引用的类型,决定调用哪个函数。2.基类实现:虚函数在基类中必须有默认实现(即函数体......
  • 【MySQL中多表查询和函数】
    目录1.多表查询1.1外键1.2链接查询2.MySQL函数内置函数简介数值函数字符串函数时间日期函数条件判断操作开窗函数1.多表查询本质:把多个表通过主外键关联关系链接(join)合并成一个大表,在去单表查询操作1.1外键外键概念:在从表(多方)创建一个字段,引用主表(一方)......
  • 43. JavaScript流程控制、函数、对象、BOM、DOM
    1.流程控制1.1if判断[1]单if分支if(条件){条件成立运行的代码}[2]if...else分支if(条件){条件成立运行的代码}else{条件不成立运行的代码}vara=10;if(a>=20){console.log("ok")}else{console.log("g......
  • 函数(C语言)
    前后两个void最好都写上库函数举例:doublesqrt(doublex);//sqrt是函数名//x是函数的参数,表示调用sqrt函数需要传递一个double类型的值。//最前面的double是返回值类型,表示函数的计算结果是double类型的值。a,b未交换原因:实参传递给形参,这时候形参是实参的一份临......
  • Java模拟Oracle函数MONTHS_BETWEEN注意事项
    Java模拟Oracle函数MONTHS_BETWEEN注意事项MONTHS_BETWEEN(DATE1,DATE2)用来计算两个日期的月份差。最近接到一个迁移需求,把OracleSQL接口迁移到新平台上,但新平台是采用Java计算的方式,所以我需求把SQL逻辑转成Java语言。在遇到MONTHS_BETWEEN时,遇到一些奇怪的问题,在此记......
  • 网络字节序本地字节序点分十进制转换函数总结&&两种初始化socket并bind的步骤
    网络字节序本地字节序点分十进制转换函数总结&&两种初始化socket并bind的步骤文章目录网络字节序本地字节序点分十进制转换函数总结&&两种初始化socket并bind的步骤1.网络字节序、本地字节序和点分十进制的数据长啥样1.点分十进制2.本地字节序(主机字节序)和网络字节序3.......
  • c函数详细讲解
    C语言中的函数是实现代码复用、模块化和提高可读性的重要工具。以下是关于C语言函数的详细讲解:1.函数的基本概念函数是一组执行特定任务的代码片段,它具有一个名称,可以被调用来执行任务。C语言中的函数分为两类:库函数:如printf()、scanf()、sqrt(),由C标准库提供。用户自定......
  • 50个Excel函数以及公式合集,可收藏练习!(二)
    大家好,我是小鱼。接上一篇的20个超强常用函数,这次又补充了50个常用的函数,掌握这些函数以后再也不用为不会函数效率低发愁了! 希望这篇文章能对你有所帮助。点赞收藏不迷路呦!......