剖析函数栈帧的创建与销毁,斯高一版本!!

简介: 剖析函数栈帧的创建与销毁,斯高一版本!!

寄存器

寄存器有哪些

32位寄存器有16个,分别是:

4个数据寄存器(EAX、EBX、ECX、EDX)。

2个变址和指针寄存器(ESI和EDI);2个指针寄存器(ESP和EBP)。

6个段寄存器(ES、CS、SS、DS、FS、GS)。

1个指令指针寄存器(EIP);1个标志寄存器(EFlags)。

寄存器分别有什么作用

简单的汇编指令

ebp、esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。每一个函数调用,(相当于领域展开)都要在栈区创建一个空间。(我的记忆方法是sb表示高低顺序(手动狗头))

上实操!

代码

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

函数栈帧讲解

main函数的函数栈帧创建

首先打破大家一个固有观点,main函数也是被其他函数调用的,具体如下图(call就是调用)

在创建main函数函数栈帧前esp和ebp是维护tmianCRTStartup函数的。

(下图是没有创建main函数前的esp和ebp位置)

首先是push ebp将ebp指针存放地址进行压栈操作,esp向上移动指向ebp压栈,就是下图蓝色小矩形,(每一次pushy,esp都会向上移动)是将tmianCRTStartup函数的栈底指针存放起来,便于销毁时能够找寻(销毁时会讲具体原理),然后是mov ebp,esp,意思是将esp的地址赋值给ebp,然后是sub esp,0E4h,意思是将esp减去一个16进制的数0E4h,esp往上移(往低地址移),这个16进制的数可以看成是main函数函数栈帧的大小。

一顿操作后就是这个样子

接下来push三连击,每压一次压栈,esp都会向上移动,然后是lea edi,[edp-24h]指令,意思是将地址edp-24加载到edi中去。

接下来意思是将edi以下的9个空间的值都赋为0CCCCCCCCh(这里就解释了当你不初始化变量时为什么是随机值)

一顿操作后就是下面这个样子

调用函数的函数栈帧创建过程

函数变量

mov三连击,分别将0Ah、14h和0的值放到[ebp-8]、[ebp-14h]和[ebp-20h]三个地址内也就是给这三个变量盖房子。

Add函数函数栈帧的创建

这里讲解的就是函数的传参问题,也就是解释了为什么传int这种变量时无法对实参的值进行修改,先打个预防针(形参是实参的临时拷贝);

在创建Add函数的函数栈帧前进行传参操作

mov eax,dword ptr [ebp-14h]

push eax

mov ecx,dword ptr [ebp-8]

push ecx

这几条指令的意思就是将[ebp-14h]和[ebp-8]中存放的值分别赋给eax和ecx,并它们压在栈顶。(这里有一个小细节就是先传b,再传a);

传参之后就是在原图加上上面的ecx eax

在进入函数前我们压入函数调用完执行的下一条代码的地址,以便进入函数内部后再出来后能够找到所需要执行的下一条代码

接下来进入函数内部和main函数的函数栈帧创建一样,大家自行理解,如果有不懂得可以评论区留言或者是私信我’

一顿操作下面就是add的函数栈帧

接下来执行Add函数内部的代码

函数返回值

将0存放到[ebp-8]的空间中去,然后将[ebp+8]存放的值(a=10)赋给eax,再然后给eax加上[ebp+0Ch]存放的值(b=20),此时eax的值为30,再将eax的值存放到[ebp-8]的空间中去,而[ebp-8]这个地址存放的就是z。最后执行return z语句,相应的汇编代码就是mov eax,dword ptr [ebp-8],将[ebp-8]存放的值赋给eax,现在eax的值就是30了。重点(eax就是存放函数返回值的寄存器)

函数栈帧销毁

Add函数执行完毕后就开始销毁

pop三连击,将edi,esi,ebx弹出栈,然后mov esp,ebp意思是将ebp指针赋值给esp,然后弹出ebp,这里ebp就回到了main函数的栈底,

然后执行ret返回到call执行的下一条语句

回来之后,给esp加上8,那么esp就向下移,此时形参x和y的空间也销毁了。接下来的指令是,将eax的值放到[ebp-20h]的空间中去,而[ebp-20]这个空间存放的就是变量c,相当于将eax的值赋给c,那么Add函数的返回值就成功地带回来了。最后就是将c的值打印在屏幕上了。

然后就是main函数的销毁,大家也是自行理解,我就不做赘述了

最后总结几个问题

1.局部变量是如何创建的?

当main函数的函数栈帧创建好之后,然后在函数栈帧中给局部变量开辟一个空间。

2.为什么局部变量的值是随机值?

因为编译器会给函数栈帧里的空间放入随机值,而局部变量也是在函数栈帧中开辟空间的,所以局部变量的值是随机值。

3.函数是如何传参的?传参的顺序是怎么样的?

将b和a的值分别存在寄存器eax、ecx,然后把eax和ecx压在栈顶。再通过地址找到eax和ecx,也就是形参,将形参加起来,就这样完成了函数的传参。传参的顺序是从右往左传。

4.形参和实参是什么什么关系?

形参和实参的值是相同的,空间上是独立的,形参只是实参的一份临时拷贝,对形参的修改不会影响实参。

5.函数调用是怎么做的呢?

通过esp和ebp的移动来为调用的函数开辟函数栈帧,然后再将执行函数里的代码。

6.函数调用结束后是怎么返回的?

在调用函数之前,就把call指令的下一条指令的地址压在栈顶了,再将main函数的ebp也进行压栈。调用完函数之后,pop main函数的ebp,ebp就会重新指向main函数的栈底。然后通过call指令的下一条指令的地址找回到call的下一条指令,继续往后执行,至此被调用函数的函数栈帧就被销毁了,通过函数的返回值也通过寄存器eax带回来了。


ok今天的分享到这里就结束了,希望大家三连一波狠狠的支持博主一下!!!感谢

目录
相关文章
|
存储 C语言
打通你学习C语言的任督二脉-函数栈帧的创建和销毁(上)
打通你学习C语言的任督二脉-函数栈帧的创建和销毁(上)
65 0
|
程序员 编译器 C语言
细谈函数栈帧的创建与销毁
我们在写C语言代码时,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数如何调用?函数的返回值如何返回的?函数参数是如何传递的?这些问题都与函数栈帧有关系。
186 0
|
存储 编译器 程序员
|
编译器 C语言
函数的栈帧与销毁(栈帧可不是战争哦)
函数的栈帧与销毁(栈帧可不是战争哦)
55 0
|
存储 编译器 程序员
C语言代码函数栈帧的创建与销毁(修炼内功)
目录 在前期的学习中我们可能有很多困惑 例如:局部变量是怎么创建的 为什么局部变量的值是随机值 函数是怎么样传参的 传参的顺序是什么 形参和实参的关系是什么 函数调用是怎么做的 函数掉调用结束后怎么返回的 这篇博客我们来修炼自己的内功,掌握好这篇博客的大部分知识就已经很不错了 我们用到VS2013这个编译器,目的是为了看到更详细的函数封装内容 提示不要使用太过高级的编译器,因为越高级的编译器越不容易观察。同时这里需要注意的是在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,不是完全相同的,具体细节取决于编译器
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(下)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
|
编译器 C语言
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(上)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
【函数栈帧的创建和销毁】 -- 神仙级别底层原理,你学会了吗?(1)
1.函数的调用方式 相信你对调用函数一点都不陌生,但是在调用函数的过程中,却存在着很多你无法见到的东西,这是底层信息,想要理解透彻,就得深入底层去观察。 本文以一个最简单的加法函数为例,深入讲解内存空间中的每一条指令。
【函数栈帧的创建和销毁】 -- 神仙级别底层原理,你学会了吗?(2)
1.函数的调用方式 相信你对调用函数一点都不陌生,但是在调用函数的过程中,却存在着很多你无法见到的东西,这是底层信息,想要理解透彻,就得深入底层去观察。 本文以一个最简单的加法函数为例,深入讲解内存空间中的每一条指令。
|
存储 程序员 C语言
探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』(一)
探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』
117 0