函数栈帧理解

news2024/12/26 3:38:46

  本文是从汇编角度来展示的函数调用,而且是在vs2013下根据调试展开的探究,其它平台在一些指令上会有点不同,指令不多,简单记忆一下即可,在我前些年的学习中,学的这几句汇编指令对我调试找错误起了不小的作用。那接来下就开始进入正题。 

 一 什么叫函数栈帧呢?

   栈帧也就是内存空间,函数栈帧也就是了解函数的内存空间,也就是我们来从内存来理解函数,个人感受,在理解完后,感觉看代码好像有一点看内存那感觉了。我们首先要了解今天要解决的六个问题:

1 局部变量是如何创建?

2 为什么局部变量内不初始化是随机值?

3 函数是如何传参的,传参顺序是什么?

4 形参和实参的关系?

5 如何进行的函数调用?

6 函数如何返回参数?

   我们大致知道数据都是放在栈区上的,而栈区内是先用高地址再用地址的,当我们调用一个函数就会在栈上开辟一块空间,这块空间就是该函数的栈帧,一块空间总得标志一下起始和终点把,也就有了两个寄存器来保存,esp存栈顶指针,ebp存栈底指针,这两个指针维护的就是当前调用函数的栈帧。

 那这里面存的1都是代码中的变量,而代码放在内存的其它位置?

二 main函数的调用者

我们总说main函数是个函数,那我们的这个main的返回值是给谁,又是被谁调用的呢?

   我们在调试模式下,F11,并且在窗口中查看调用堆栈就可以看到如下的图,而那个黄色箭头是当前调用的函数,所以调用顺序是mainCRTStartup()调用_ _tmainCRTStartup(),最后才调用的main函数,所以main函数返回值是给上述函数的,之前一直说不用管main函数的参数和返回值,现在我们就可以稍微理解main的返回值是给谁的了,至于参数则需要在其它环境才能引出来解释了。

 编译器调用main的准备工作

   我们先调试起来下面这个代码,然后转到反汇编

int Add(int x,  int y)
{
	return x + y;
}

int main()
{
	int x = 2;
	int y = 3;
	int ret=Add(x, y);
	return 0;
}

下面每一行都是一句指令,等会会一句一句说清楚,首先我们可以发现我们写代码第一句是int x=2,定义一个变量,但是对于编译器来说它要做很多的前期工作,不是直接就帮局部变量的定义,原因是:我们先前提到main函数首先也是个函数,也就是说被编译器调用的,那一开始ebp和esp维护的就不是main函数的栈帧空间,所以编译器要做的前期工作之一那就是将ebp和esp转为维护main函数栈帧。(这里已经大致解释了问题5,后面调用Add函数编译器做的工作和现在调用main函数做的准备工作一模一样)

 现在编译器要开始调用main函数了,我们来看看编译器做了什么。

   首先第一句指针push  ebp就是向栈顶存放一个ebp的变量,那这个时候esp就要上移了,因为存放了一个变量后,栈顶上移了,esp也要变。

 第二句指令是mov ebp esp也就是把esp的值赋值给ebp,那此时ebp和esp就指向同一个位置了,然后是第三句,sub  esp 0E4h是对esp减去一个值-0E4h,结果如下,esp就又上移了。

 此时esp和ebp已经开始维护新的空间了,此时ebp和esp就已经开始维护被调用的main函数栈帧了(后面会提如何在回去维护_ _tmainCRTStartup()函数的栈帧)

  紧接着的几句都是push三个寄存器元素,ebx和esi的作用先不用管,而edi是提供给后面的rep stos指令的,我们先看看编译器往edi放了什么,ebp-0E4h不就是我们执行了sub指令后esp的位置吗,当然此时esp的位置是如下图的。

   然后就是两个move指令和一个rep stos指令,这三句指令作用可以记忆一下,就是把edi地址向下的的ecx个空间全部初始化为eax中存的值,也就是说ecx存了初始化次数,eax存了初始化的值,而每个空间单位是dword指示的四个字节。论证:0E4h十进制是228,而39h(这个h好像是没啥用的)十进制是57,恰好分为了57个四个字节空间。

这就是为什么我们的局部变量未初始化的空间内存的是一个特别小的一个负数,就是因为我们一开始存的就是这么一个数,而且不同平台可能不一样,所以又称为随机数。(可以解释问题2为什么未初始化的局部变量内是随机值)。

四 执行实际写的代码转

 1 定义局部变量

   如果汇编不显示符号名,那其实创建一个局部变量,就是往一个空间内存一个值,当后面这块空间销毁了,局部变量也就没了。(题1解释)

虽然我们写代码是连续定义了两个变量,但是这两个变量在内存的位置不是紧挨的,是有空隙的,这个空隙的大小由编译器来定。

我们往ebp-8位置开始向下使用四个字节来存放2,这说明栈区的内存是先用高地址,再用低地址,但是变量内部是先用低地址,再用高地址。

  2 调用Add函数 

  然后我们再来看看调用Add函数是如何传参的,首先是将ebp-14h中的值存入存到寄存器,再push到栈顶,而ebp-14h的值就是y的值这说明传参顺序是右到左(问题3的解释)。

 

   我们push了两个参数到栈上,而且只是把x,y的值拷贝了一份放到栈顶上。push寄存器到栈上的意思是把寄存器的值放到栈顶上,不是将寄存器放到上面,为了更好地展示栈上是什么,才没有用数据代替。

  然后我们就看到了一句call指令,这个时候会在栈顶上保存call指令下一条指令的地址,这个非常隐秘,我是从内存窗口才发现F11执行call指令时esp向上移动了四个字节。所以这个时候栈区的图应该为下图:

这个地址之后在Add函数销毁就会起作用。

 还有call不是直接找到函数,而找到一句jump指令,jump指令才能找到函数。

 此时调用Add函数又是一堆前置工作,就像调用main函数一样,都要改变ebp和esp。

 

我们可以看到最后我们把值都放到了eax中去,这就是Add函数栈帧都销毁了,还可以返回值的原因(问题6,后面有后续)

此时就又使得esp和ebp去维护新调用的函数栈帧。(问题5的再解释,函数调用就是通过ebp和esp的两个指针的配合)

 

   还有就是我们的实参和形参的关系从下面这张图应该可以理解吧,传值传参的时候,我们在Add函数找参数是ebp+8找的ecx,这就是x的值的拷贝,ebp+12找的就是y变量的拷贝。而且我们之前说ebp到esp之间是调用函数的栈帧,显而易见的是形参不在对应函数栈帧内。问题4,传值传参中形参是实参的临时拷贝。

3 Add函数栈帧的销毁

当我们调用完了Add就要使得esp和ebp返回去维护main函数栈帧,而如何返回的,请看下图。

 pop就是从栈顶拿走一个元素,push的时候esp要上移,那pop的时候esp就要下移。

 然后就是move 指令,那esp就直接和ebp一样指向了。

还有就是pop ebp还会把ebp(而这里的ebp存的是原先main的栈底指针存的地址)弹出并且存到ebp。那ebp不就直接去维护main函数的栈底了吗。

 

   此时我们的ebp和esp基本回到维护main函数栈帧的状态,但是我们一开始是跳转到函数内去执行的指令,执行完后要回到main函数的,那怎么回来呢?就是这个ret指令的作用了。而且使用完后也会被pop掉

 最后一步:esp+8就完成了对Add函数栈帧的销毁,也对形参进行了销毁。

而我们再回到main函数中,可以发现,ret[ebp-20h]接收的就是eax中的值(问题6后续)

 这就是个人对函数栈帧的全部理解,之前学了觉得理解了,但是时间久了反而有点忘了,所以就狠下心来写了这篇博客,因为画很多图来结合分析。

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

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

相关文章

Python爬虫实战案例——第一例

X卢小说登录(包括验证码处理) 地址:aHR0cHM6Ly91LmZhbG9vLmNvbS9yZWdpc3QvbG9naW4uYXNweA 打开页面直接进行分析 任意输入用户名密码及验证码之后可以看到抓到的包中传输的数据明显需要的是txtPwd进行加密分析。按ctrlshiftf进行搜索。 定位来到源代码中断点进行调…

Qt应用开发(基础篇)——纯文本编辑窗口 QPlainTextEdit

一、前言 QPlainTextEdit类继承于QAbstractScrollArea,QAbstractScrollArea继承于QFrame,是Qt用来显示和编辑纯文本的窗口。 滚屏区域基类https://blog.csdn.net/u014491932/article/details/132245486?spm1001.2014.3001.5501框架类QFramehttps://blo…

iOS17 widget Content margin

iOS17小组件有4个新的地方可以放置分别是:Mac桌面、iPad锁屏界面、 iPhone Standby模式、watch的smart stack Transition to content margins iOS17中苹果为widget新增了Content margin, 使widget的内容能够距离边缘有一定的间隙,确保内容显示完整。这…

ARM--day6(实现字符、字符串收发的代码和现象,分析RCC、GPIO、UART章节)

uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_uart.h"//RCC/GPIO/UART4章节初始化 void hal_uart4_init();//发送一个字符函数 void hal_put_char(const c…

Roxy-Wi 命令执行漏洞复现

漏洞描述 Roxy-WI是开源的一款用于管理 Haproxy、Nginx 和 Keepalived 服务器的 Web 界面 Roxy-WI 6.1.1.0 之前的版本存在安全漏洞,该漏洞源于系统命令可以通过 subprocess_execute 函数远程运行,远程攻击者利用该漏洞可以执行远程代码。 免责声明 技术文章仅供参考,任…

Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)

默认Excel/PowerPoint折线图是这个样子的: 左右两侧都留了大块空白,很难看 解决方案 点击横坐标,双击,然后按下图顺序点击 效果

开发调试更便捷!火山引擎 DataLeap 提供 Notebook 交互式开发体验

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 Notebook 是一种支持 REPL 模式的开发环境。 所谓「REPL」,即「读取-求值-输出」循环:输入一段代码,立刻得到相应的结果&#x…

OpenCV图片校正

OpenCV图片校正 背景几种校正方法1.傅里叶变换 霍夫变换 直线 角度 旋转3.四点透视 角度 旋转4.检测矩形轮廓 角度 旋转参考 背景 遇到偏的图片想要校正成水平或者垂直的。 几种校正方法 对于倾斜的图片通过矫正可以得到水平的图片。一般有如下几种基于opencv的组合方…

机场项目:解决飞行物空间大小/纵横比、速度、遮挡等问题引起的实时目标检测问题...

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID|计算机视觉研究院 学习群|扫码在主页获取加入方式 计算机视觉研究院专栏 Column of Computer Vision Institute 最近在arXiv中翻阅到一遍基于Yolov8的飞机实时目标检测论文&#xff…

回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现GAM广义加性模型多输入单输出回归预测(多指标,多图)效果一览基本介绍程序设计参考资料 效果一览 基本…

MySQL | JDBC连接数据库

一、什么是JDBC 概念: JDBC,即Java Database Connectivity,java数据库连接。是一种用于执行SQL语句的Java API,它是Java中的数据库连接规范。这个API由 java.sql.*,javax.sql.* 包中的一些类和接口组成,它为Java开发…

八种架构演进

日升时奋斗,日落时自省 目录 1、单机架构 2、应用数据分离架构 3、应用服务集群架构 4、读写分离/主从分离架构 5、冷热分离架构 6、垂直分库架构 7、微服务架构 8、容器编排架构 9、小结 1、单机架构 特征:应用服务和数据库服务器公用一台服务…

HRS--人力资源系统(Springboot+vue)--打基础升级--(四)新增员工信息

今天的更新:在员工信息界面,点新增按钮,弹出一个请填写新增员工信息的窗口,然后保存数据 1. 先来看下窗口设计样式 嗯,感觉这窗口又细又长,果然排版不是我的强项,这里的新增属于管理员的权限界面…

SQL助你面大厂(N日留存率)

在一些游戏中比较关注的就是留存率,随着王者的更新,画质在不断的优化,可是匹配机制确实一成不变,导致很多人逐渐退游,那么我们的王者的官方是怎么知道有这么多人退游么?退游了是相当于删除玩家对应的那条数…

第 7 章 排序算法(4)(插入排序)

7.7插入排序 7.7.1插入排序法介绍: 插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。 7.7.2插入排序法思想: 插入排序(Insertion Sorting)的基本思想是:把n个待排…

【令牌桶算法与漏桶算法】

💧 令牌桶算法与漏桶算法 \color{#FF1493}{令牌桶算法与漏桶算法} 令牌桶算法与漏桶算法💧 🌷 仰望天空,妳我亦是行人.✨ 🦄 个人主页——微风撞见云的博客🎐 🐳 《数据结构与算法》专…

机器学习深度学习——NLP实战(情感分析模型——RNN实现)

👨‍🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习&&深度学习——NLP实战(情感分析模型——数据集) 📚订阅专栏:机器学习&…

玩转红黑树:手把手教你实现和理解红黑树

玩转红黑树:手把手教你实现和理解红黑树 引言一、红黑树的定义1.1、理论知识1.2、代码实现1.3、代码优化 二、红黑树的旋转2.1、理论知识2.2、代码实现 三、红黑树插入节点3.1、理论知识3.2、代码实现 四、红黑树删除节点4.1、理论知识4.2、代码实现 五、红黑树的查…

04_17页回收问题和水位线和swap交换空间和oom,内存性能微调

前言 应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。 当应用程序读写了这块虚拟内存,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会…

基于STM32CubeMX和keil采用SPI通信实现轮询方式读写W25Q128获取设备ID

文章目录 前言1. SPI通信1.1 SPI硬件接口与连线1.2 SPI传输协议1.3 SPI的数据传输 2. W25Q1282.1 片选2.2 控制指令2.3 时序图分析 3. STM32CubeMX配置3.1 引脚配置3.2 时钟配置3.3 SPI配置3.4 工程配置 4. 代码编写4.1读ID函数 总结 前言 最近使用通信比较多,包含…