函数栈帧的创建与销毁

news/2024/11/13 4:17:07/

目录

1、寄存器的分类与作用

2、测试代码与所需要的知识点

        2.1、测试代码

        2.2 、关于main函数的调用

        2.3、关于栈、压栈、出栈简介

3、main函数栈帧的创建与分析

3 .1、push edp

3.2、mov    ebp,esp

3.3、sub    esp,0E4h

3.4、push ebx,esi,edi

3.5、lea  edi,[ebp- 0E4hh]  /  mov   ecx,9  /  mov   eax, 0CCCCCCCCh】

3.6、rep stos    dword ptr es:[edi]

3.7、mov     dword  ptr  [ebp - 8] ,0Ah/mov     dword  ptr  [ebp - 14h] ,14h/mov     dword  ptr  [ebp - 20h] ,0

3.8、mov   eax,dword ptr [ebp - 14h]   /   push    eax

3.9、call   00C210E1

4、Add函数栈帧的创建

4.1、mov    dword ptr [ebp-8],0 

4.2、mov    eax, dword ptr [ebp+8]

4.3、add     eax, dword ptr [ebp+0Ch]

4.4、mov     dword ptr [ebp-8], eax

4.5、mov     eax, dword ptr [ebp-8]

5、Add函数栈帧的销毁

5.1、pop    edi / esi / ebx

5.2、mov    esp, ebp 

5.3、 pop ebp

5.4、ret

5.5、 mov     dword ptr [ebp-20h],eax

6、main函数栈帧的销毁


在学习函数栈帧的创建和销毁前我们得知道学它们得作用:

知道了函数栈帧的创建和销毁,并且都会了,其实也就是修炼了自己的内功,内功越强大练武功就会事半功倍;

好了,接下来我们进入正题,博主今天所使用的环境是vs2013,这里注意:不要使用太高级的编译器,越高级的编译器,越不容易学习和观察。同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现 

1、寄存器的分类与作用

在C语言中我们可以把寄存器当成指针来看待,他可以指向一块空间,也可以用来存储数据。现在向大家介绍以下几种基本寄存器

2、测试代码与所需要的知识点

        2.1、测试代码

int Add(int x,int y)
{int z = 0;z = x + y;return z;
}int main()
{int a = 10;int b = 20;int c = Add(a,b);printf("%d\n",c);return 0;
}

        2.2 、关于main函数的调用

main函数其实也是被其他函数调用的,函数调用关系如下:

mainCRTStartup  →   __tmainCRTStartup  →  main 

         2.3、关于栈、压栈、出栈简介

我们知道,函数的调用都需要在栈区上开辟空间,那么我们先来解答几个问题:

1.什么是栈?

【答】栈是一种数据结构,它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。栈区内存空间的使用是从高地址向低地址处使用的。

2.什么是压栈?什么是出栈?

【答】一个形象的比喻就是机枪弹夹。压栈的过程就是压入一个元素,相当于向机枪弹夹压入子弹;出栈的过程就是弹出一个元素,相当于子弹弹出来的过程。这正好对应了栈的结构特点——先进入的数据被压在栈底,后进入的数据在栈顶


关于后续可能使用的大小端,可以去看看博主关于数据存储方面的讲解;

3、main函数栈帧的创建与分析

每一个函数的调用都要在栈区分配空间,同时注意,栈区上内存的使用是从高地址向低地址处使用的

 这块开辟的空间由我们前面所讲的esp和edp进行维护

 我们前面所讲的main函数其实也是被其他函数调用的,其实这儿也有调用空间

接下来我们调试代码后,进入反汇编模式,这里我们与要注意,我们需要将显示符号名去掉2,以方便后续的观察

 我们之前提到,main函数是由__tmianCRTStartup函数调用的,所以在创建main函数栈帧前,ebp和esp寄存器维护--tmainCRTStartup的栈区,分别存放指向栈帧的栈顶和栈底

3 .1、push edp

push指令的作用:它首先减小esp的值,再将源操作数复制到栈地址,每次esp地址减去四字节。最终效果就是在栈顶压入一个元素,元素的值为ebp的地址。(4个字节)

ebp里存放的是__tmianCRTStartup函数栈底的地址,由于esp指向栈顶,所以esp向上移动一位

3.2、mov    ebp,esp

mov指令作用:将一个数据从源地址传送到目标地址,源操作地址的内容不变。最终效果是将esp的地址赋值给ebp。寄存器指向的空间发生改变。 

此时就变为 

 3.3、sub    esp,0E4h

sub指令的作用:减操作指令,地址减去相应的数值。最终效果就是esp的地址减去0E4h

此时呢我们esp和ebp就为main函数预开辟了空间

3.4、push ebx,esi,edi

前面我们提到过push的过程就压入元素的过程。那压入这三个元素有什么用呢?等会就明白。 

3.5、lea  edi,[ebp- 0E4hh]  /  mov   ecx,9  /  mov   eax, 0CCCCCCCCh】

Load Effective Address,即装入有效地址的意思,它的操作数就是地址。在这里的效果就是将ebp+FFFFFF1Ch的值赋给edi。勾选“显示符号名后可以发现: ebp - 0E4h不正是当初esp - 0E4h时的地址

 3.6、rep stos    dword ptr es:[edi]

rep指令的作用是:重复后面的指令。stos指令的作用是将eax中的值拷贝到es:edi指向的地址。ecx表示重复操作的次数。dword表示4个字节。所以整句指令的作用是:从edi开始,向高地址方向,将ecx个4字节内存全部修改为eax的值。

可以看到,执行操作后edi上相应数量的四字节内存被赋值为cc cc cc cc。这也解释了为什么我们不初始化,变量默认的初始值为cc cc cc cc

 

main函数开辟就此完成,接下来我们要实现有效的代码了

3.7、mov     dword  ptr  [ebp - 8] ,0Ah/mov     dword  ptr  [ebp - 14h] ,14h/mov     dword  ptr  [ebp - 20h] ,0

将ebp - 8地址处的四字节内容修改为0Ah(10进制中的10),也就是完成了给a赋值为10的动作。下一条语句同理。 

每个变量之间相差两个整型,8个字节 

 

 至此main函数的栈帧创建的准备阶段完成。我们准备进入Add函数  

3.8、mov   eax,dword ptr [ebp - 14h]   /   push    eax

将ebp - 14h地址处的数值储存到eax处;压栈,将一个数值与eax相等的元素压入栈中。ebp - 14h这个地址好像似曾相识,没错,这正是b的地址,所以这条语句的作用实际上即使将b的值传到eax中。同理,a的值被存储到ecx中去。

(实际上,上述过程解释了函数究竟是如何传参的。传参的顺序和变量创建的顺序恰好相反,先传b再传a。同时注意函数传参并不是在add函数栈帧内完成的,而是在main函数的栈帧内完成,通过寄存器eax和ecx实现变量的传递)

压栈后如下 

3.9、call   00C210E1

call指令的作用是:将下一条的指令的ip压入栈中,并转移到即将被调用的子程序。相当于push ip +  jmp near ptr 标号。我们现在来观察栈顶的变化。

我们惊奇的发现栈顶自动压入了一个元素,元素的值为——call指令的下一条指令的地址 。那压入这个元素有什么用呢?试想,当call指令调用add函数后,我们跳转到add函数内部,那函数结束后如何保证我们从add函数后面的语句继续执行呢?靠的就是栈顶压入的地址,根据这个地址我们可以回到call指令的下一条语句

再次按下F11后我们就跳转到add函数内部。

4、Add函数栈帧的创建

 我们发现蓝色部分的操作和main函数内完全一致,都是为栈帧的创建做准备 

 当我们Add函数的栈帧创建后,我们现在研究接下来的指令

4.1、mov    dword ptr [ebp-8],0 

将ebp - 8 的地址赋值为0。也就代表着将Z初始化为0

 4.2、mov    eax, dword ptr [ebp+8]

将 ebp + 8 处的值赋值给eax。此时eax储存着形参a的值

4.3、add     eax, dword ptr [ebp+0Ch]

将eax加上 ebp + 12(0ch的十进制表现) 处的值。此时eax表示这a+b的值 

 4.4、mov     dword ptr [ebp-8], eax

将eax储存的a+b的值传送到ebp - 8的地址处,也就是赋值给变量Z

从上面我们也可以加深这样的认识:形参只是实参的一个临时拷贝,所以修改形参当然不会影响实参。 

4.5、mov     eax, dword ptr [ebp-8]

将ebp-8的值放在eax里面,防止被销毁

我们知道函数内创建的临时变量出函数后被销毁,那返回值是如何被带回主函数的呢?靠的就是寄存器eax。这一步的操作就是 return返回 值的过程。

5、Add函数栈帧的销毁

5.1、pop    edi / esi / ebx

pop的作用是将栈顶的数据弹出,弹出数据储存到相应寄存器中。每次pop过程中esp的地址自动加4字节

 

 5.2、mov    esp, ebp 

把edp赋给esp,一句话就回收了为add函数开辟的内存

5.3、 pop ebp

弹出栈顶的元素,并将弹出的数据储存到ebp寄存器中。由于此时的栈顶元素事先存入main函数中ebp的地址,所以pop ebp时,将main函数中的ebp元素的地址存入ebp寄存器中

5.4、ret

ret指令的作用实际相当于 pop IP。在这里实际就是弹出了栈顶事先存储的call指令下一条指令的地址,并跳转到该地址处。 

 

这里其实当实行pop指令后,两个形参也就已于销毁了 

5.5、 mov     dword ptr [ebp-20h],eax

将eax的值放入edp-20h里面,也就是我们返回值放入c里面

6、main函数栈帧的销毁

main函数和add函数栈帧的销毁基本是一致的,因为我们前面提到,main函数也是被其他函数调用的。以此类推。后面我就不多讲了,有问题的可以在评论区或者私信博主。


http://www.ppmy.cn/news/119289.html

相关文章

40V 高精度 LDO 稳压器-QX6126

QX6126 是一款支持最高 40V 输入的 三端 LDO 稳压器芯片。其内置高精度的 输出运算放大器,进而可得到精准且稳定 的输出电压。 芯片具有低静态电流,并可实现最大 100mA 的电流输出,并具有短路保护、温 度保护等功能。 QX6126 采用 SOT23、S…

STM32自学笔记-5-SPI和Flash芯片2

在W25Qxx.c中,可以重点看以下几个函数 BSP_W25Qx_WriteEnable()函数 uint8_t BSP_W25Qx_WriteEnable(void) {uint8_t cmd[] {0x06};uint32_t tickstart HAL_GetTick(); //开始计时W25Qx_Enable(); //将NSS拉低,使芯片可操作HAL_SPI_Transmit(&h…

LED台灯方案:QX6126+QX5567

QX6126是一款支持40V输入的三端LDO稳压器芯片。其内置高精度的输出运算放大器,进而可得到精准且稳定的输出电压。 芯片具有低静态电流,并可实现最大100mA的电流输出,并具有短路保护、温度保护等功能。 QX6126采用SOT-23-3、SOT-89封装&…

小风扇专用芯片-QX5311F

概述 QX5311F是一款集成了锂电池线性充电箮和三种档位输出驱动的手持风扇驱动芯片,只需极少的外接元件,便能适用于手持风扇等便携式产品的应用。 QX5311F 根据电池电压的不同可分别有涓流充电,恒流充电和恒压充电等三种充电模式。浮流电压被固…

创建一个长度是100的字符串数组,使用长度是2的随机字符填充该字符串数组,统计这个字符串数组里重复的字符串有多少种(忽略大小写)

创建一个长度是100的字符串数组 使用长度是2的随机字符填充该字符串数组 统计这个字符串数组里重复的字符串有多少种(忽略大小写) 例如 输出: 20个字符串一行 共5行 01 0S 1M 1q 20 2R 2S 3d 3k 3x 43 4A 4R 5T 7C 7U 7e 7k 80 88 8p AD AG AY BF BO Db E2 Eg El GF Gm HC HO I…

数据库实验 | 第5关:使用游标的存储过程

任务描述 本关任务: jdxx数据表有四个字段,分别是省份(sf)、城市(cs)、区县(qxmc)、街道(name)。 例如,查询天心区(qxmc)的所有字段的值结果如图所示 任务要求 建立存储过程 tjdq(in sf varchar(10)) 输入省份的名称,将该省份…

是男人就下100层(简仿)

近 来,事情不多,闲暇之时,就想写个简单的游戏练练手。太复杂了,不使用游戏引擎来做,是非常困难的。这里,其实也没有打算说,开发一款游戏上线,就是练习如何自定义一个View。我想啊想&…

Perl学习笔记—100个知识点

1、Perl里的注释是从井号(#)开始到行尾结束的部分; 2、在Unix系统里,如果文本文件开头的额最前端两个字符是#!,那么后面跟着的就是用来执行这个文件的程序路径。如:#! /usr/bin/perl; 3、在Unix系统上&am…