探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』(二)

简介: 探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』

二、函数调用中的栈帧

🔖我们在VS2013上进行观察比较方便,因为较高编译器版本底层的封装逻辑太严密,函数调用过程中的栈帧的创建是略有差异的,所以不同的编译器具体细节是取决于编译器的。


我们常常以main函数开始编写代码,调用自己写的函数,那么main函数会被其他函数调用吗?答案是有的。


为了方便查看函数栈帧调用过程的细节问题,我们直接把代码划分的足够细致。


⌨️以下用C语言代码编写:

#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;
}

🔖 我们简单写一个Add函数的代码程序,按「F10」进行调试起来,在【调试】->【窗口】->【调用堆栈】里就可以看到,(如果看不到,继续尝试往下调试),main函数是被__tmainCRTStartup函数调用的,而__tmainCRTStartup函数其实又被mainCRTStartup函数调用。

1.4.png1.5.png

每个函数调用,都会为此分配一块栈空间,需要函数栈帧来维护。

那么在调用main函数之前,有一块函数栈帧空间用来维护__tmainCRTStartup的。1.6.png

1.探究main函数栈帧的创建

接下来我们还是在【调试】->【窗口】打开反汇编,通过分析汇编指令来具体研究函数栈帧创建与销毁的逻辑。

⚠️注意:在分析汇编指令之前,最好右键取消显示符号名,不然有些代码不方便观察。1.7.png

在观察第一行 push ebp之前,我们打开监视,先观察一下esp的值为0x008ffba8、ebp的值为0x008ffbf41.8.png

🔔观察上图,第一行的 push ebp指令,进行压栈操作

📌图形展示:1.9.png

按 「F10」调试走一步,2.0.png

发现esp的地址减小了,由原来的0x008ffba8 变为了0x008ffba4 2.1.png

查看内存,esp的值被修改为0x008ffbf4 说明ebp压栈成功。2.2.png

🔔再次观察第二行mov ebp,esp  这条语句可以译为将esp的值给ebp,说明ebp此时应该指向esp所指向的位置。

📌图形展示:2.3.png

不信的话,我们「F10」往下走一步,通过监视1,我们可以发现esp的值确实达到了与ebp的值相等的效果2.4.png2.5.png

🔔我们继续往下走 sub esp,0E4h  即将esp减去一个0E4h(0E4h,h表示HEX,十六进制的意思,实际表示的是0xe4)的值,说明esp的地址减小了,esp指向的位置就会跑向更低的地址去了。

F10继续走一步,发现esp的值确实减少了许多。2.6.png

📌图形展示: 2.7.png

我们可以预料到,此时esp与ebp之间的空间就是为main函数预开辟好的函数栈帧空间了。

🔔接下来,有三次压栈操作:push ebx、push esi、push edi2.8.png

🔻对ebx进行压栈操作,esp栈顶指针指向了ebx2.9.png

🔻对esi进行压栈操作,esp栈顶指针指向了esi3.0.png

🔻对edi进行压栈操作,esp栈顶指针指向了edi3.1.png

📌图形展示: 3.2.png

继续往下走,3.3.png

🔔lea   edi,[ebp-0E4h]  lea指令的意思是Load effective address,译为加载有效地址,把ebp-0E4h的地址加载到edi中,咦?看到这里,乍一看,我们0E4h这个值怎么这么眼熟?,对,这个值在前面出现过:“esp减去了0E4h”,原来如此,ebp-0E4就是esp在三次压栈操作之前指向的位置。

3.4.png

🔔 move ecx,39h:  将十六进制的39赋值给ecx寄存器中,这里其实表示的是39h次,这里的多少次并不是固定的,需要根据编译器确定。3.5.png

🔔move eax,0CCCCCCCCh: 将0xcccccccc这个十六进制的数字赋值给eax中


继续走到rep stos  这个指令,才是正儿八经的改变栈帧里的数据了,rep指令的目的是重复其上面的指令,ecx的值是重复的次数。stos指令的作用是将eax中的值拷贝到edi所指向的地址处。dword:表示double word(4个字节),1个word表示2个字节。

3.6.png

以上过程完整叙述就是:将edi位置开始,向下的ecx次,也就是39h次,这么多个空间(每个空间4个字节)全部修改为eax的值,即0xCCCCCCCCimage.png

我们继续调试一步,观察内存中,从 0x008FFAC0 开始,一共39h*4个字节大小的空间,直到0x008FFBA4 之前,都被修改为0xcccccccc   其实为当前main函数开辟的空间都被修改为cccccccc这样的值。image.pngimage.png

📌图形展示:image.png

ok,到此为止,为main函数开辟的栈帧空间就准备完毕了。

目录
相关文章
|
2月前
|
存储 编译器 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
|
2月前
|
存储 安全 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
|
程序员 编译器 C语言
细谈函数栈帧的创建与销毁
我们在写C语言代码时,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数如何调用?函数的返回值如何返回的?函数参数是如何传递的?这些问题都与函数栈帧有关系。
141 0
|
9月前
|
存储 编译器
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(二)
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(二)
68 0
|
9月前
|
存储 机器学习/深度学习 编译器
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(一)
函数栈帧深度剖析(一篇带你牢牢掌握函数栈帧)(一)
179 0
|
9月前
|
存储 编译器 程序员
|
11月前
|
存储 编译器 程序员
C语言代码函数栈帧的创建与销毁(修炼内功)
目录 在前期的学习中我们可能有很多困惑 例如:局部变量是怎么创建的 为什么局部变量的值是随机值 函数是怎么样传参的 传参的顺序是什么 形参和实参的关系是什么 函数调用是怎么做的 函数掉调用结束后怎么返回的 这篇博客我们来修炼自己的内功,掌握好这篇博客的大部分知识就已经很不错了 我们用到VS2013这个编译器,目的是为了看到更详细的函数封装内容 提示不要使用太过高级的编译器,因为越高级的编译器越不容易观察。同时这里需要注意的是在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,不是完全相同的,具体细节取决于编译器
|
11月前
|
编译器 C语言
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(上)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
|
11月前
|
C语言
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(下)
抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解
|
11月前
|
存储 编译器 C语言
函数栈帧的创建与销毁(反汇编万字讲解)
局部变量是怎么创建的? 为什么未初始化的局部变量的值是随机值? 函数是怎样传参的?传参的顺序是怎样的? 形参和实参是什么关系? 函数调用是怎样做的? 函数调用后是怎样返回的? 我们本章就来研讨这个问题,掌握了函数栈帧的创建和销毁更有利于后期的学习这里建议大家要从头往后一个内容一个内容看,因为这里每一个部分关联性很强!