函数栈帧理解

  本文是从汇编角度来展示的函数调用,而且是在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.rhkb.cn/news/97941.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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

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

【前端|JS实战第1篇】使用JS来实现属于自己的贪吃蛇游戏!

前言 贪吃蛇游戏是经典的小游戏,也是学习前端JS的一个很好的练习项目。在本教程中,我们将使用 JavaScript 来逐步构建一个贪吃蛇游戏。我们会从创建游戏区域开始,逐步添加蛇的移动、食物的生成以及游戏逻辑等功能。 🚀 作者简介&a…

韦东山-电子量产工具项目:业务系统

代码结构 所有代码都已通过测试跑通,其中代码结构如下: 一、include文件夹 1.1 common.h #ifndef _COMMON_H #define _COMMON_Htypedef struct Region {int iLeftUpX; //区域左上方的坐标int iLeftUpY; //区域左下方的坐标int iWidth; //区域宽…

java八股文面试[java基础]——String StringBuilder StringBuffer

String类型定义: final String 不可以继承 final char [] 不可以修改 String不可变的好处: hash值只需要算一次,当String作为map的key时, 不需要考虑hash改变 天然的线程安全 知识来源: 【基础】String、StringB…

Python web实战之细说 Django 的单元测试

关键词: Python Web 开发、Django、单元测试、测试驱动开发、TDD、测试框架、持续集成、自动化测试 大家好,今天,我将带领大家进入 Python Web 开发的新世界,深入探讨 Django 的单元测试。通过本文的实战案例和详细讲解&#xff…

ubuntu安装Microsoft Edge并设置为中文

1、下载 edge.deb 版本并安装 sudo dpkg -i microsoft-edg.deb 2. 设置默认中文显示 如果是通过.deb方式安装的: 打开默认安装路径下的microsoft-edge-dev文件,在文件最开头加上: export LANGUAGEZH-CN.UTF-8 ,保存退出。 cd /opt/micr…

PHP8的字符串操作3-PHP8知识详解

今天继续分享字符串的操作,前面说到了字符串的去除空格和特殊字符,获取字符串的长度,截取字符串、检索字符串。 今天继续分享字符串的其他操作。如:替换字符串、分割和合成字符串。 5、替换字符串 替换字符串就是对指定字符串中…

【算法系列篇】滑动窗口

文章目录 前言什么是滑动窗口1.长度最小的子数组1.1 题目要求1.2 做题思路 1.3 Java代码实现2.无重复字符的最长子串2.1 题目要求2.2 做题思路2.3 Java代码实现 3.最大连续1的个数 III3.1 题目要求3.2 做题思路3.3 Java代码实现 4.将x减到0的最小操作数4.1 题目要求4.2 做题思路…

【仿写框架之仿写Tomact】四、封装HttpRequest对象(属性映射http请求报文)、HttpResponse对象(属性映射http响应报文)

文章目录 1、创建HttpRequest对象2、创建HttpResponse对象 1、创建HttpRequest对象 HttpRequest对象中的属性与HTTP协议中的内容对应,用于后序servlet从request中获取请求中的参数。 参照http请求报文: import java.io.BufferedReader; import java…

配置使用Gitee账号认证登录Grafana

三方社会化身份源 集成gitee第三方登录 第三方登录的原理 所谓第三方登录,实质就是 OAuth 授权。用户想要登录 A 网站,A 网站让用户提供第三方网站的数据,证明自己的身份。获取第三方网站的身份数据,就需要 OAuth 授权。 举例来…

ubuntu上使用osg3.2+osgearth2.9

一、介绍 在ubuntu上使用osgearth加载三维数字地球,首先要有osg和osgearth的库,这些可以直接使用apt-get下载安装,但是版本有些老,如果需要新版本的就需要自己编译。 #查看现有版本 sudo apt-cache madison openscenegraph #安装…

Python写一个创意五子棋游戏

前言 在本教程中,我们将使用Python写一个创意五子棋游戏 📝个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列: ☄️爬虫JS逆向系列专栏 - 爬虫逆向教学 ☄️python系列专栏 - 从零开始学python 首先 GomokuGame 类的构造函数 __ini…

小程序 CSS-in-JS 和原子化的另一种选择

小程序 CSS-in-JS 和原子化的另一种选择 小程序 CSS-in-JS 和原子化的另一种选择 介绍快速开始 pandacss 安装和配置 0. 安装和初始化 pandacss1. 配置 postcss2. 检查你的 panda.config.ts3. 修改 package.json 脚本4. 全局 css 注册 pandacss5. 配置的优化与别名 weapp-pand…

LeetCode 周赛上分之旅 #40 结合特征压缩的数位 DP 问题

⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度…

07_缓存预热缓存雪崩缓存击穿缓存穿透

缓存预热&缓存雪崩&缓存击穿&缓存穿透 一、缓存预热 提前将数据从数据库同步到redis。 在程序启动的时候,直接将数据刷新到redis懒加载,用户访问的时候,第一次查询数据库,然后将数据写入redis 二、缓存雪崩 发生情…

Roxy-Wi 命令执行漏洞复现

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

小程序前台Boot后台校园卡资金管理系统java web学校进销存食堂挂失jsp源代码

本项目为前几天收费帮学妹做的一个项目,Java EE JSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。 一、项目描述 小程序前台Boot后台校园卡资金管理系统 系统有2权限&…

Nuxt3_1_路由+页面+组件+资源+样式 使用及实例

1、 简介 1.1 开发必备 node版本 v16.10.0 我使用的是16.14.0编辑器推荐使用Volar Extension 的VS code插件Terminal 运行nuxt指令 1.2 环境搭建 安装项目: npx nuxilatest init [first_nuxt3]进入项目目录: cd [first_nuxt3]安装依赖:n…

【蓝桥杯】[递归]母牛的故事

原题链接:https://www.dotcpp.com/oj/problem1004.html 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 我们列一个年份和母牛数量的表格: 通过观察,找规律,我们发现: 当年份小于等于4时&…