函数栈帧(栈区)

news2024/11/19 17:29:27

函数栈帧(栈区)

  • 一.前言
  • 二.main函数空间的开辟(函数调用是如何做到的)
  • 三.main函数内部的变量初始化(局部变量是如何创建的以及为什么是随机值)
  • 四.main函数内部的函数创建
    • 1.函数是如何传参的
    • 2.传参的顺序以及实参和形参的关系
    • 3.函数调用结束后是怎么返回的

在这里插入图片描述

一.前言

在这里插入图片描述

前言:在不同类型的编译器里,函数栈帧的大体是没有区别的,但在细节上可能会有一些区别。这里我们使用vs2013来查看,如果对调试还不太了解的话可以看看这篇博客vs调试

二.main函数空间的开辟(函数调用是如何做到的)

首先要看一看寄存器,寄存器有很多种,这次只看两种esp和edp,因为它们两个是用来维护栈帧的。esp是栈顶指针,edp是栈底指针。

栈区是优先使用高地址的,由上图也可以看出,栈区是向下延伸的。接下来单独画出栈区的图,为了更直观,我直接将高地址放在下面,低地址放在上面

ps:main函数也是函数需要在栈区开辟空间

在这里插入图片描述

为了验证上述结论,我们写一个很简单的函数并使用调试功能的调用堆栈监视来查看具体的过程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们从调用堆栈里可以看出main函数被调用了,那么它究竟是被谁调用了呢?我们接下来让代码继续往下走。

在这里插入图片描述

我们可以很清楚的看到main函数其实是被__tmainCRTStartup()这个函数调用。而__tmainCRTStartup()又被mainCRTStartup所调用。可以看出来main函数的调用是比较复杂的,我们每次写main函数都用return 0结尾,其实这个0就被放入了mainret里。那么实际上我们也需要给上述两个函数也开辟空间,在调用main函数之前。

结论:在VS2013中,main函数也是被其他函数所调用的

既然我们在调用main函数的时候要开辟空间,那么我们在使用Add函数的时候也需要开辟空间并且肯定也需要esp和edp去维护,那具体的细节是怎样的呢?这里我们需要简单的阅读一下汇编代码

在这里插入图片描述
在这里插入图片描述

通过上文我们知道在调用main函数之前会先调用__tmainCRTStartup这个函数。既然我们现在已经进入到了main函数内部,那说明前面那个函数的栈帧肯定已经创建完成了,那么在刚准备调用main函数时,应当是如下图。

在这里插入图片描述

第一步push,压栈。将ebp的值放在__tmainCRTStartup这个函数的顶部,同时由于esp是栈顶,esp也会向上移。注意ebp本身所指的位置是不变的,顶部只是放入ebp的值

在这里插入图片描述

第二步mov,把后面的值赋给前面的。这里就是将esp的值给ebp,那不就相当于ebp不再指向当前位置而指向esp所指向的位置了吗。

在这里插入图片描述

第三步sub,减法。这里是把esp的值减去0E4h,这里的0E4h是16进制数(ps:0h是16进制的象征)。那么减去一个值后,esp肯定就变小了,那么它就会向上移动,指向上面的某一块区域。

在这里插入图片描述

从上可以看出ebp和esp准备开始维护下一个函数(main函数),上述的0E4h就是为main函数预开辟的空间(这个大小是由编译器决定的)。紧接着又是三个push,在顶部压三个元素,esp也就随着向上移(每次push后esp的值都会变),具体这三个值是什么目前不需要理解,之后它们会自动弹出。

在这里插入图片描述

下一步是lea,load effective address加载有效地址。就是把后面的地址放到前面来。这里ebp-0E4h有什么特殊含义吗?我们再梳理一下脉络,首先我们mov将esp的值给了ebp,再sub,esp的值减去了0E4h。那么可以看出,ebp-0E4h这个值就是esp在进行三次push之前的值。

从lea到rep stos可以完整的看作一个步骤。从edi这个位置开始向下每次初始化4个字节(dword双字,就是4个字节),总共初始ecx(39h)次,初始内容是eax(0CCCCCCCCh)。也就是把main函数内的空间全部初始化为0CCCCCCCCh。

在这里插入图片描述

三.main函数内部的变量初始化(局部变量是如何创建的以及为什么是随机值)

好了,现在main函数的空间开辟完成,由于符号名不能让我们直观的看到具体的过程,我们将符号名关掉,只显示地址。

在这里插入图片描述

上面的a,b,c的名称就分别用ebp-8,ebp-14h,ebp-20h来替代。那么通过阅读不难发现就是把0Ah(10)这个值放入ebp-8这个位置,而这个10其实就是我们给a赋的值,如果我们没有给a赋值,那么它这里本身应该是CCCCCCCC,这也就是为什么我们经常打随机值时会出现烫烫烫的字样。同理下面的将14h(20)放入ebp-14h这个地址中。将0放入ebp-20h这个地址中。这样我们就将a,b,c三个变量全部赋值啦。其实这里我们仔细观察还能发现ebp-8,ebp-14h,ebp-20h,它们相邻之间差了12也就是差了两个整形,我们可以通过内存监视来查看。

在这里插入图片描述

由此我们完成了变量的初始化,接下来是Add函数的创建。

四.main函数内部的函数创建

在这里插入图片描述

1.函数是如何传参的

我们都知道函数调用是需要传参的,那它究竟是如何传参的呢?

第一步把ebp-14h里的值放进eax。ebp-14h的地址就是b所对应的地址,也就是将20放进eax里。

在这里插入图片描述

第二步是push,将eax放到顶部。

在这里插入图片描述

第三步ebp-8里的值放到ecx。也就是将a的值放入ecx。第四步再push,将ecx放到顶部。

在这里插入图片描述

第五步call,调用。这条指令就是进入函数内部需要按F11,在看这条指令时我们需要观察他地址

在这里插入图片描述

接下来按F11进入函数并且打开我们的内存监视。

在这里插入图片描述

我们可以看到它在0x008FFAA8这个位置存了我们call指令的下一条指令的地址并且在它下面的一个地址0x008FFAAC里存的是10。也就是说这个地址是在变量a的上方的。也就是把call指令的下一条指令的地址压在a的上面了。

在这里插入图片描述

我们都知道函数调用完毕后是需要返回的,而这个地址就是来帮助编译器进行定位。

回归正题,进入函数内部后我们可以看到下面的汇编代码。

在这里插入图片描述

我们可以看到它的前面一半其实跟我们的main函数的创立是一样的,是在为它分配栈帧,这里简单梳理一下。首先,将ebp进行压栈操作;接着将esp的值赋给edp;然后esp-0CCh就是开辟0CCh这么大的空间;之后又是三个push;紧接着就是将里面的空间全部初始化为0CCCCCCCCh。

在这里插入图片描述

接下来就是创建z,将0放入ebp-8的地址里。

在这里插入图片描述

然后是计算x+y并把值放入z中。首先是把ebp+8所对应的值放入eax,ebp+8就是把ebp向下移两个整形,就是10,然后再将ebp+0Ch所对应的值与eax相加;也就是10+20,再存入eax。之后再将eax里的值放入ebp-8(z)中。我们也可以看到x+y=z这个操作并不是在Add函数内部进行的实际上是直接通过寄存器实现的。

在这里插入图片描述

2.传参的顺序以及实参和形参的关系

通过上面的演示,我们可以理解到我们常说的传参其实是一份临时拷贝的意思了。本质上就是由寄存器复制参数的值然后再利用压栈,最后通过向下回访来的到参数,并不是在Add函数内部创造的。并且也可以看到我们传参是先传的b再传的a,所以传参是从右向左传的。

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

之后是函数的返回。

在这里插入图片描述

把ebp-8里的值放入eax里。就是把z的值拷贝到eax里。这也就解释了为什么在调用函数后,函数被销毁还能有返回值。eax是寄存器是不会随着函数的销毁而销毁的。

返回完毕后紧接着是三句pop,pop就是弹出栈顶(也就是把该地址所对应的内容给后面的寄存器),每弹出一次esp就会向下走一步。由于edi,esi,ebx所对应的地址的内容就是它们本身,所以实际上就只是让esp向下移动3次。

在这里插入图片描述

弹出完成后就该销毁空间了,mov把ebp的值赋给esp,也就意味着ebp和esp指向同一位置。

在这里插入图片描述

之后又是pop,ebp。我们原本这块空格里存的是main函数的ebp,而此时弹出(将mian函数里曾经edp的地址赋给现在的edp),ebp寄存器也就是直接返回原本main函数ebp的位置;与此同时,esp向下走一步。

在这里插入图片描述

最后是一个ret,就是返回。返回到所对应的地址位置,也就是返回到call指令的下一条指令,看我们监视显示。

在这里插入图片描述

第六步,add。esp+8,也就是直接到esp+c的位置,释放掉我们所创建的形参。(一旦esp向下走,上面的部分就自动销毁了)

在这里插入图片描述

第七步,把eax的值放到ebp-20h里去。就是把30放到c里。这样我们的函数调用才算完毕。同理,main函数的销毁也是这个过程,就不再累述了。

还可以看到我们之后还有printf的汇编,因为printf实际上也是一个函数实现的大体与Add函数的实现相同,但由于printf很复杂,这里就不再解析了,如果大家有兴趣的话,可以根据上面的步骤自己调试一下代码。但需要注意的是编译器的不同会导致结果的不同。

在这里插入图片描述

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

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

相关文章

物联网通信技术原理第2章 无线通信技术(期末重点)

目录 2.1 无线通信基本知识 2.1.2 无线通信的特点 2.2 无线信道 2.2.1 恒参无线信道举例 2.2.2 恒参无线信道特性及其对信号传输的影响 1. 幅度—频率畸变 2. 相位—频率畸变​编辑 2.2.3 随参无线信道距离 1、短波电离层反射 2、对流层散射信道 2.2.4 随参信道特性及…

漏洞深度分析|Apache Karaf 4.2.16 存在JNDI 注入漏洞

项目地址 GitHub - apache/karaf: Mirror of Apache Karaf 项目介绍 Apache Karaf是一个模块化运行时,支持多种框架和编程模型(REST/API、web、spring boot 等)。它提供了统包功能,您可以毫不费力地直接利用这些功能&#xff0…

戴维南定理

定理内容:任意二端口直流网络都可以被一个等效电路替换,这个等效电路只包含一个电压源和一个串联电阻,如图1所示。 图1 戴维南定理的作用 1、分析含有非串联或非并联电源的网络 2、用最少数量的元件实现复杂网络的端口特性(端口前…

【深入浅出Spring原理及实战】「开发实战系列」SpringSecurity原理以及实战认证分析开发指南

前提介绍 承接上一篇文章,相信大家应该已经对SpringSecurity的原理有了一定的认识,而本篇文章给大家带来的则是在实际业务开发中的技术指南,希望对你有所帮助。所谓知彼知己方能百战百胜,用Spring Security来满足我们的需求最好了…

我们国家都有哪些信息安全标准

写在前面 早年刚参加信息安全工作更多的学点皮毛技术,到处找安全工具,跟踪poc,拿到一个就全网扫一遍,从来没有想过,系统化的安全工作应该怎样搞?我做的工作在安全体系中处于哪个阶段? 后来有机会做企业安全建设&…

503.下一个更大元素II,42. 接雨水

503. 下一个更大元素 II 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数…

C++:vector操作笔记

本文记录了对C中的vector容器的简单操作,包括给vector添加元素、按指定列排序、取值。 文章目录一、vector 和普通数组之间的转化二、根据vector数组的某一列数据排序三、删除二维 vector 数组最后一列数据一、vector 和普通数组之间的转化 1,数组转vec…

Mysql日志体系

3、Mysql日志体系 bing log :在server记录mysql所有的DDL和DML语句,主要用来做主从复制和数据恢复。内容追加,没有固定大小限制。redo log :undo log : 3.1、bin log 3.1.1、binlog日志作用 binlog记录所有的DDL和…

Unity CEO:玩家不在乎AR还是VR,他们只想要优质内容

近期,Unity CEO John Riccitiello在接受英国金融时报采访时,透露了自己对于游戏的前生今世,以及AR/VR和未来的看法。他认为,打造一个新平台需要大量优质内容,创意对于新平台很重要,不管AR和VR技术有什么区别…

java医院挂号系统挂号预约网站挂号网站预约网站源码

主要实现门诊挂号和用户网站预约挂号的功能 演示视频: https://www.bilibili.com/video/BV1nN411d7HT/?share_sourcecopy_web&vd_sourceed0f04fbb713154db5cc611225d92156 环境:jdk7/8mysql5.5tomcat7/8 技术:ssm(springspringmvcmyi…

读书笔记---Bioinformatics Data Skills

最近阅读了《Bioinformatics Data Skills》,是不错的生物信息学入门书,本文作者简介如下: 全书以下面三个方向展开,为生物信息学的继续学习指明了道路: (1)生物信息学数据技巧(第一章…

动态DNS与DPDK高性能DNS -DPDK环境搭建

工具准备: vmware Ubuntu 16.04 Server 版本 1.vmware 添加两块网卡 桥接网卡作为 DPDK 运行的网卡 NAT 网卡作为 ssh 连接的网卡 2.修改网卡配置信息 将 ethernet0.virtualDev 由 e1000 修改 vmxnet3,因为 vmware 的 vmxnet3 支持多队 列网卡 3.修…

Qt对象树

对象模型(对象树) 类似于c中虚析构实现的功能一样,在释放父类对象的同时调用子类的析构函数释放子类对象 也c调用析构函数的区别是:会先调用父类的析构函数,然后一层层往下调用子类析构,直到调用到底层子…

10.1、Django框架简介、创建第一个应用

文章目录预备知识MVC模式和MTV模式MVC模式MTV 模式Django框架Django框架简介Django框架的应用启动后台admin站点管理数据库迁移创建管理员用户管理界面本地化创建并使用一个应用bookapp项目的数据库模型创建数据库模型生成数据库表数据库上的基本操作启用后台admin站点管理自定…

Vue框架--Ruoyi解析(前端)

路由router注册 router目录下的 index.js 配合 permission.js 是整套vue前端项目的权限判断核心. index.js 里面的path 配置都是一些不会与权限挂钩的路由,例如: 404、登录页面路径等. permission.js 中的 router.beforeEach 是路由拦截,在访问某个链接之前会进行权限判断,但是…

Mob教你汇总常见的用户注册和登录方式

目前市面上APP的常见的用户注册和登录方式有三种,第一种是最早期的手机号码密码登录,第二种是常见的 手机号码短信验证码,第三者是最新推出,且正被各大应用APP接入采用的" 一键登录"秒验方式。本文从运行方式、优势、劣…

【Leetcode】965. 单值二叉树、100. 相同的树、572. 另一棵树的子树

作者:一个喜欢猫咪的的程序员 专栏:《Leetcode》 喜欢的话:世间因为少年的挺身而出,而更加瑰丽。 ——《人民日报》 目录 965. 单值二叉树 100. 相同的树 572. 另一棵树的子树 965. 单值二叉树…

蓝桥杯,我劝你不要参加的8个完美理由

蓝桥杯,是一个全国高校的IT技术比拼,如果你参加了,可能不止是刷题数量的剧增,还有你的软件人生 我劝你不要参加,因为如果你参加了,可能会有以下烦恼: 目录 1、会让你变得上进 2、会提前感受码…

OpenCV inRange 函数使用详解

本文是 OpenCV图像视觉入门之路的第6篇文章,本人详细的解决了RGB转HSV,HSV通过AI来进行HSV转 inRange() 函数的范围值操作,简单全面的解决了OpenCV对于图像中某个颜色的分析工作,本文通过识别红色区域和蓝色区域来编写示例程序和博…

从交互设计师的角度,聊聊设计工具的那些事

工欲善其事,必先利其器,这句话是出自论语,讲的是要做好一件事,工具是非常重要的,作为一个设计师,设计工具对于我们的重要性毋庸置疑,每天都在接触,也有很多感悟和心得。 我从事设计…