C语言代码函数栈帧的创建与销毁(修炼内功)

news2024/9/22 4:12:43

目录
在前期的学习中我们可能有很多困惑
例如:局部变量是怎么创建的
为什么局部变量的值是随机值
函数是怎么样传参的 传参的顺序是什么
形参和实参的关系是什么
函数调用是怎么做的
函数掉调用结束后怎么返回的

这篇博客我们来修炼自己的内功,掌握好这篇博客的大部分知识就已经很不错了
我们用到VS2013这个编译器,目的是为了看到更详细的函数封装内容 提示不要使用太过高级的编译器,因为越高级的编译器越不容易观察。同时这里需要注意的是在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,不是完全相同的,具体细节取决于编译器

一、基础知识

1.1 什么是栈区?
C/C++程序内存分配的几个区域:
//1. 栈区(stack):
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
//2. 堆区(heap):
一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
//3. 数据段(静态区)(static):
存放全局变量、静态数据。程序结束后由系统释放。
//4. 代码段:
存放函数体(类成员函数和全局函数)的二进制代码

接下来,补充一下栈的知识,了解到这就可以了,足够使用了
栈区的使用是从高地址到低地址
栈区的使用遵循先进后出,后进先出
栈区的放置是从高地址往低地址放置:push 是压栈
删除是从低往高删除:pop 是出栈

1.2 寄存器

这里简单介绍一些寄存器,其它的先不要过多理解
常见寄存器有eax、ebx、ecx、edx,这四个都当做通用寄存器,保留临时数据,ebp和esp较为特殊

**eax “累加器” 它是很多加法乘法指令的缺省寄存器。
ebx "基地址"寄存器, 在内存寻址时存放基地址。
ecx 计数器,是重复(REP)前缀指令和LOOP指令的内定计数器。
edx 总是被用来放整数除法产生的余数。
esi 源索引寄存器
edi
目标索引寄存器
ebp (栈底指针)“基址指针”,存放的是地址,用来维护函数栈帧
esp (栈顶指针)专门用作堆栈指针,存放的是地址,用来维护函数栈帧
使用的测试代码

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

接下来还有一些汇编代码的含义:
mov:mov是数据传送指令(move的缩写),用于将一个数据从源地址传送到目标地址
sub:减法,subtraction的缩写
lea:Load effective address的缩写,取有效地址
call:用于调用其他函数
add:加法
pop:出栈
push:入栈或压栈

二、函数栈帧的创建和销毁的过程

_tmainCRTStartup函数(调用main函数)栈帧的创建
先了解 main 函数是被谁调用的,按 F10 或 F11 进入调试模式,F10是逐过程,F11是逐语句,打开堆栈

在这里插入图片描述
这时我们按住F10调试起来,右击鼠标转到反汇编就会呈现下图样式
在这里插入图片描述

_tmainCRTStartup函数(调用main函数)栈帧的创建

按F10,按到return 0 时再按一次,调用栈堆会出现以下内容
这时再看堆栈窗口发现 main 函数被 __tmainCRTStartup() 调用

在这里插入图片描述

main函数栈帧的创建

push        ebp  //在栈顶开辟ebp寄存器对应的空间
 mov         ebp,esp  //将esp的值传入ebp中(即将ebp指针移动到原本esp指向的位置)
 sub         esp,0E4h  //将esp的内容减去0E4h(将esp移动到原esp-0E4h的位置)
 push        ebx  //在栈顶放入ebx
 push        esi  //在栈顶放入esi
 push        edi  //在栈顶放入edi

此时进入main函数(也就是程序调试开始),首先要 push ebp 进行压栈,ebp 在 __tmainCRTStartup() 上面压栈
观察esp ebp 地址的变化,在调试的监视里面查看,push ebp 之后,esp 指向的位置也随之改变 (地址减小)

看下图
在这里插入图片描述
先看最开始的几个步骤
push:先在栈顶压一个ebp
move:把esp的值给了ebp(将地址传给ebp)
sub:给esp减去一个0E4h(八进制位数)这个值,减去一个0E4h的值后值变小了(地址变小了),那么此时ebp就指向了栈顶低地址
lea:load effective address(加载有效地址),ebp-0E4h就是再main函数的栈顶,为什么呢,这是因为我们前几个步骤在算的时候,esp在减去了一个0E4h之后就已经到达了栈顶,前面又把esp的值(地址)赋值给了ebp,所以理所当然的到达了栈顶

在这里插入图片描述
再将ebx esi edi分别压栈压在栈顶
大家可以边调式边看我给大家写的步骤,我如果一边写一边画图会很不方便,大家可以先跟着我的步骤先自己尝试着画一下,我在博客的最后给大家画了完整的图供大家参考
再看下面几个步骤,这里大家再最后几个步骤理解起来有点困难,我这里用通俗易懂的语句给大家解释一下,这里的eax存放的是0CCCCCCCCh这个值,从edi开始向下的ecx里面放的值全部放成CCCCCCCC这个值,这里再强调一遍word–2个字节,dword–4个字节,希望大家能够理解!!!
最上面是低地址,最下面是高地址

在这里插入图片描述
这就是为什么我们再VS编译器上面经常打印处烫烫烫烫,就是因为最开始没有在main函数里面定义变量的时候里面放的都是随机值,当然在不同的编译器上面效果不同
在这里插入图片描述

main函数内执行有效代码

mov         ecx,0ECC003h  
call        00EC131B  
	int a = 10;
mov         dword ptr [ebp-8],0Ah  
	int b = 20;
mov         dword ptr [ebp-14h],14h  
	int c = 0;
mov         dword ptr [ebp-20h],0  

mov这个意思我们上面已经讲到了,比如a,将14h(十六进制数也就是20,自己可以计算一下,116加上416的0次方),将20这个数字放到ebp-8这个地址里面,dword–4个字节,见下面我画的图大家就能理解
mov eax,dword ptr [ebp-14h] ,把 ebp-14h 的值0000 0014(十进制是20)放到 eax 里去
push eax ,压栈 eax(20),esp指向的位置也随之改变 (地址减小)
mov ecx,dword ptr [ebp-8] ,把 ebp-8 的值0000000a(十进制是10) 放到 ecx 里去
push ecx ,压栈 ecx(10),esp指向的位置也随之改变 (地址减小)

步骤演示图

在这里插入图片描述
接下来为call 指令,按下F11,此时就正式进入Add函数内部 并为其开辟栈帧

Add函数栈帧的创建

按 F11,进入到 Add 函数 ,该add 函数地址不一定与main 函数地址相连,但是add 函数的地址一定在main 函数地址上面

 call        00EC131B  

call 指令调用 Add 函数,这里逐语句(F11)执行,发现这里竟然存储着下一条指令的地址,事实上 call 指令把下一条指令的地址压栈了(为了 Add 函数结束后能找回来),esp 地址也跟着变化
在这里插入图片描述
在这里插入图片描述

进入 Add 函数前,会先为 Add 函数开辟函数栈帧,这这些操作跟先前main函数开辟函数栈帧操作一样,所以这里就不细谈了

push    ebp//将ebp上移
mov     ebp,esp//将esp内容放入ebp(移动ebp)
sub     esp,0CCh//esp-0CCh(为Add开辟空间)
push    ebx//在栈顶放入ebx
push    esi//在栈顶放入esi
push    edi//在栈顶放入edi
lea      edi,[ebp-0Ch]//ebp-0Ch的空间  
mov      ecx,3//3存入ecx  
mov      eax,0CCCCCCCCh//存入eax  
rep stos  dword ptr es:[edi]//esp往下0ch的空间进行赋值

首先,push ebp把ebp压栈到栈顶,再mov把esp赋给ebp,再sub,把esp-去0CCh,此步骤就是在为Add函数开辟空间,接着进行三次push,同main函数那样,同理,依旧是赋值为CCCCCCCC,详细过程不再赘述,跟上文main函数一样,如图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Add函数内执行有效代码

 mov         ecx,0ECC003h  
 call        00EC131B  
	int z = 0;
 mov         dword ptr [ebp-8],0  //把 0 初始化到 ebp-8 的位置

在这里插入图片描述

	z = x + y;
 mov         eax,dword ptr [ebp+8]  //把 ebp+8 的值 10 放到 eax 里
 add         eax,dword ptr [ebp+0Ch]  //把 ebp+0ch 的值 20 和 eax 的值 10 相加
 mov         dword ptr [ebp-8],eax  //把 eax 的值 30 放到 ebp-8(z) 里去
	return z;
 mov         eax,dword ptr [ebp-8]  //把 ebp-8 的值 30 放到 eax 里去

在这里插入图片描述

Add函数栈帧的销毁

mov eax,dword ptr [ebp-8] ,把ebp-8的值(30)放到eax里头去
pop edi ,出栈,释放为edi创建的栈区,地址开始增大
pop esi ,出栈,释放为esi创建的栈区,地址继续增大
pop ebx ,出栈,释放为exb创建的栈区,地址继续增大
执行一次pop,esp就会往高地址处移动一次
此时我的Add函数内部的CCCCCCCC这一些随机值就没有用了
mov将ebp的值赋值给esp,esp就指向高地址处了,ebp前面的Add函数的函数栈帧就销毁了,希望大家能够理解
然后我们再pop(出栈)一下,ebp就走了,就回到了main函数的低地址,此时我们的esp就来代替edp的位置,希望大家能够理解

在这里插入图片描述
最后我们看ret,我们此时回到了低地址处00C21450,这里面的逻辑其实是非常严谨的,我既要走出去也要走回来,我们接着从call指令处开始执行
然后执行add+8就是往高地址处走两步,dword是8个字节,地址往后+2个,这样就把我们形参中a,b给销毁了

在这里插入图片描述
在这里插入图片描述
mov esp,ebp ,ebp的值赋给esp,此时esp和ebp依旧相同
pop ebp ,弹出ebp,并将ebp所指向的main函数的起始地址赋值给了ebp指针,esp指针向高位移动,esp和ebp重新开始维护main函数的栈区空间
ret ,返回到main函数,在执行 ret 指令时,esp指针就指向了栈顶存放的call指令的下一条指令的地址

此时Add函数的栈帧算是真正销毁
在这里插入图片描述

main函数代码继续执行

在这里插入图片描述
mov dword ptr [ebp-20h],eax ,把eax的值放到ebp-20h上,而eax就是我们出Add函数时计算的和
接下来就是打印值和 main函数函数栈帧销毁,都与上面类似

所需反汇编代码总览

Add 函数

int Add(int x, int y)
{
 push        ebp  
 mov         ebp,esp  
 sub         esp,0CCh  
 push        ebx  
 push        esi  
 push        edi  
 lea         edi,[ebp-0Ch]  
 mov         ecx,3  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  
 mov         ecx,0ECC003h  
 call        00EC131B  
	int z = 0;
 mov         dword ptr [ebp-8],0  
	z = x + y;
 mov         eax,dword ptr [ebp+8]  
 add         eax,dword ptr [ebp+0Ch]  
 mov         dword ptr [ebp-8],eax  
	return z;
 mov         eax,dword ptr [ebp-8]  
}
 pop         edi  
 pop         esi  
 pop         ebx  
 add         esp,0CCh  
 cmp         ebp,esp  
 call        00EC1244  
 mov         esp,ebp  
 pop         ebp  
 ret  

main函数

int main()
{
 push        ebp  
 mov         ebp,esp  
 sub         esp,0E4h  
 push        ebx  
 push        esi  
 push        edi  
 lea         edi,[ebp-24h]  
 mov         ecx,9  
 mov         eax,0CCCCCCCCh  
 rep stos    dword ptr es:[edi]  
 mov         ecx,0ECC003h  
 call        00EC131B  
	int a = 10;
 mov         dword ptr [ebp-8],0Ah  
	int b = 20;
 mov         dword ptr [ebp-14h],14h  
	int c = 0;
 mov         dword ptr [ebp-20h],0  
	c = Add(a, b);
 mov         eax,dword ptr [ebp-14h]  
 push        eax  
 mov         ecx,dword ptr [ebp-8]  
 push        ecx  
 call        00EC10B4  
 add         esp,8  
 mov         dword ptr [ebp-20h],eax  
	printf("%d\n", c);
 mov         eax,dword ptr [ebp-20h]  
 push        eax  
 push        0EC7B30h  
 call        00EC10D2  
 add         esp,8  
	return 0;
 xor         eax,eax  
}
 pop         edi  
 pop         esi  
 pop         ebx  
 add         esp,0E4h  
 cmp         ebp,esp  
 call        00EC1244  
 mov         esp,ebp  
 pop         ebp  
 ret  

这次我们明显感觉难度大了起来,这正是我们修炼内功,变强的过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/673803.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MATLAB第42期】基于MATLAB的贝叶斯优化决策树分类算法与网格搜索、随机搜索对比,含对机器学习模型的评估度量介绍

【MATLAB第42期】基于MATLAB的贝叶斯优化决策树分类算法与网格搜索、随机搜索对比&#xff0c;含对机器学习模型的评估度量介绍 网格搜索、随机搜索和贝叶斯优化是寻找机器学习模型参数最佳组合、交叉验证每个参数并确定哪一个参数具有最佳性能的常用方法。 一、 评估指标 1、…

STM32F103基于HAL工程挂载FatFS驱动SD卡

STM32F103基于HAL工程挂载FatFS驱动SD卡 &#x1f4cc;基于标准库驱动《STM32挂载SD卡基于Fatfs文件系统读取文件信息》 &#x1f3ac;驱动实验效果&#xff1a; &#x1f528;通过STM32cubemx配置SPI1作为访问SD、TF卡通讯方式。 &#x1f527;在STM32cubemx配置中挂载中…

Scala入门【变量和数据类型】

目录 Scala基本认知 Hello Scala 方法的定义 伴生对象 Java&#xff1a; Scala&#xff1a; 2、变量和数据类型 2.1、注释 2.2、变量和常量 Java中的变量和常量 Scala基本语法 2.3、标识符 2.4、字符串 基本语法 2.5、标准输入输出 基本语法 2.6、文件的读写 …

6-8 二分查找

今天是端午节&#xff0c;祝大家端午节快乐~ 竟然这样&#xff0c;还不点点赞。 言归正传┏ (゜ω゜)☞ 目录 引入 二分查找算法思想 时间复杂度O&#xff08;logN&#xff09; 二分查找算法描述 二分查找算法的框架如下&#xff1a; 例题1&#xff1a; 例题2&#x…

秒懂SpringBoot之如何集成SpringDoc(全网目前最新最系统最全面的springdoc教程)

[版权申明] 非商业目的注明出处可自由转载 出自&#xff1a;shusheng007 文章目录 概述概念解释SpringDoc使用简单集成配置文档信息配置文档分组使用注解TagOperationSchemaParameterParametersApiResponses 和ApiResponse 认证授权无需认证需要认证 总结源码 概述 近来颇为懈…

13. WebGPU 正交投影

在上一篇文章中&#xff0c;讨论了矩阵的工作原理。讨论了如何通过 1 个矩阵和一些神奇的矩阵数学来完成平移、旋转、缩放&#xff0c;甚至从像素到裁剪空间的投影。实现 3D 操作 只需要再向前迈一小步。 在之前的 2D 示例中&#xff0c;将 2D 点 (x, y) 乘以 3x3 矩阵。要实现…

【机器学习】——续上:卷积神经网络(CNN)与参数训练

目录 引入 一、CNN基本结构 1、卷积层 2、下采样层 3、全连接层 二、CNN参数训练 总结 引入 卷积神经网络&#xff08;CNN&#xff09;是一种有监督深度模型框架&#xff0c;尤其适合处理二维数据问题&#xff0c;如行人检测、人脸识别、信号处理等领域&#xff0c;是带…

19c rac添加节点

在正常的节点 [rootdb1 ~]# xhost access control disabled, clients can connect from any host [rootdb1 ~]# su - grid ASM1:/home/griddb1>export DISPLAY:1.0 ASM1:/home/griddb1>$ORACLE_HOME/gridSetup.sh [rootdb2 ~]# /tmp/GridSetupActions2021-09-16_…

基于Nginx1.22+PHP8+MySQL8安装Discuz! X3.5

基于Nginx1.22PHP8MySQL8安装Discuz! X3.5 1. 安装PHP82. 安装MySQL83. 配置Nginx1.224. 安装Discuz! X3.5 1. 安装PHP8 更新系统&#xff1a; yum update安装EPEL存储库&#xff1a; yum install epel-release安装Remi存储库&#xff08;提供了最新的 PHP 版本&#xff09;&…

【十三】druid 原理解析

druid 原理解析 先前写了一篇博客关于druid集成相关的&#xff0c;这里来分析一下druid原理&#xff0c;结合这两篇文章希望读者能够把druid理解透彻。 一、druid介绍 Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生&#xff0c;内置强大的监控功能&…

【前端JS交互篇】ECMA核心语法 ——常量、变量、数据类型、各种弹框

一、javascript简介 1.1 Javascript简史 在WEB日益发展的同时&#xff0c;网页的大小和复杂性不断增加&#xff0c;受制于网速的限制&#xff0c;为完成简单的表单验证而频繁地与服务器交换数据只会加重用户的负担&#xff0c;当时走在技术革新最前沿的Netscape&#xff08;网…

阿里云服务器的弹性计算能力如何?是否支持按需扩展和缩减?

阿里云服务器的弹性计算能力如何&#xff1f;是否支持按需扩展和缩减&#xff1f;   【本文由阿里云代理商[聚搜云www.4526.cn]撰写】   阿里云服务器&#xff0c;作为业界领先的云计算服务提供商&#xff0c;其弹性计算能力是如何体现的&#xff1f;是否真的支持按需扩展和…

定制你的Blocks UI布局:Gradio的Block Layouts模块介绍

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

【python】如何在 Python 中创建相关矩阵

目录 一、说明 二、相关理论 2.1 何为相关 2.2 相关的前提 2.3 Correlation Matrix是个啥&#xff1f; 2.4 皮尔逊相关系数 三、Python演示如何创建相关矩阵 四、数据可视化观察 五、后记 一、说明 本教程介绍如何在 Python 中创建和解释相关矩阵。然而&#xff0c;创…

English Learning - L3 作业打卡 Lesson7 Day47 2023.6.20 周二

English Learning - L3 作业打卡 Lesson7 Day47 2023.6.20 周二 引言&#x1f349;句1: Growing up in a hot Las Vegas desert, all I wanted was to be free.成分划分弱读连读语调 &#x1f349;句2: I would daydream about traveling the world, living in a place where i…

有三个线程,分别只能打印A,B和C要求按顺序打印ABC,打印10次(多种方法,小白也懂)

目录 第一种方法:使用LockSupport的park和unpark功能(推荐) 第二种方式:synchronizedwaitnotify 第三种:暴力循环方法(不推荐) 第一种方法:使用LockSupport的park和unpark功能(推荐) 简单来说我们有一个名为LockSupport的方法 park就是阻塞当前进程 unpark就是取消阻塞让其…

DRIFTINGBLUES: 4实战演练

文章目录 DRIFTINGBLUES: 4实战演练一、前期准备1、相关信息 二、信息收集1、端口扫描2、访问网站3、查看源码4、解密5、访问网页6、解密7、访问网页8、微信扫一扫9、爆破FTP10、登录FTP11、下载文件并查看12、写入SSH密钥并上传13、SSH连接 三、后渗透1、查看第一个flag2、查找…

Golang | Web开发之Gin静态资源映射及HTML模板渲染

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; 专注 企业运维实践、网络安全、系统运维、应用开发、物联网实战、全栈文章 等知识分享 “ 花开堪折直须折&#xf…

[Eigen中文文档] 稀疏矩阵操作

文档总目录 本文目录 稀疏矩阵格式SparseMatrix 类 第一个示例SparseMatrix 类矩阵和向量属性迭代非零系数 填充稀疏矩阵支持的运算符和函数基本操作矩阵乘积块操作三角形视图和自共轭视图 英文原文(Sparse matrix manipulations) 处理和解决稀疏问题涉及各种模块&#xff0c…

【马蹄集】第十六周作业

第十六周作业 目录 MT2149 最长子段和MT2150 旅费MT2156 矩阵取数MT2157 迷宫MT2155 四柱河内塔 MT2149 最长子段和 难度&#xff1a;钻石    时间限制&#xff1a;1秒    占用内存&#xff1a;128M 题目描述 给出一个长度为 n n n 的序列 A A A&#xff0c;选出其中连续…