函数栈帧的创建与销毁(保姆级讲解)

局部变量是怎么创建的?

在为main函数开辟栈帧空间时,在一定范围内初始化成0CCCCC,再把里面0CCCC的一些开辟空间给局部变量使用。

为什么局部变量的值是随机值?

因为我们在为main函数开辟栈帧空间时,会将一定范围内空间初始成0CCCCCC里面什么也没有,所以如果局部变量不给初始化,局部就会进入随意开辟栈帧空间,就是为随机值或者是烫烫烫。

函数是怎么传参的?传参的顺序是怎样的?

eax(b)和ecx(a)进行压栈,先传的b再传的a,从右向左。

形参和实参是什么关系?

形参是实参的一份临时拷贝,它们的值是相同的,但所使用的空间是不同的,所以形参的改变不影响实参,形参确实只是实参的一份临时拷贝。

函数调用是怎么做的?

下面我画图所解释的非常清楚了。(如果不明白的同学可以私信互相交流下)

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

由函数一步一步建立空间,再一步一步销毁空间返回

用保存call指令下一条指令地址与ebp-main函数的保存位置进行寄存器返回值

用寄存器eax返回最终的值。


知道和函数栈帧的创建和销毁就都会了,其实就是修炼了自己的内功,也能搞懂后期更多的知识。
进入正题
今天讲解使用的环境是VS2019
同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

首先我们要了解什么是函数栈帧

函数栈帧就是在函数调用过程中,程序为函数所开辟的栈空间,函数一般放在栈区。

而编译器为了方便动态内存管理,一般划分为了三个区域:栈区 堆区 静态区

而什么又是栈呢?

栈的概念及结构
栈:一种特殊的线性表,其只允许在 固定的一端 进行 插入和删除 元素操作。 进行数据插入和删除 操作的一端称为 栈顶 ,另一端称为 栈底 。栈中的数据元素遵守 后进先出 LIFO (Last in First Out) 的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈(Push), 入数据在栈顶
出栈: 栈的删除操作叫做出栈(Pop)。 出数据也在栈顶 。
特点:栈只能在栈顶进行插入和删除。

为了更加清楚了解函数栈帧,我们还需要了解下以下寄存器

eax :保留临时数据,常用于返回值

ebx:保留临时数据

ecx

edx

ebp:栈低指针

esp:栈顶指针

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

在调用main函数时ebp和esp会维护main函数的函数栈帧。

当我按F10进入调试,并且打开调用堆栈


我们可以看见函数的调用关系,调用堆栈是反应函数的调用关系的

由调试我们可以看见main函数是被invoke_main()函数调用的

而Add函数是被main()函数调用的

那么理所当然的invoke_main函数与Add函数也是有自己的函数栈帧空间的,并且由ebp和esp来维护函数栈帧空间。

F10进入调试点击右键转到反汇编

如下图所示是此次代码的反汇编指令 

但我们为了方便更加清晰的观察,再次点右键取消显示符号名。

 

main函数第一条反汇编指令是Push(压栈) ebp。

如果是ebp压栈那么ebp的值是会变小的 因为是由高地址到低地址

这是原来的值

当我push以后的值,果然变小了

 所以是真的压进去了吗?我们可以通过内存来查看

当我们查看esp内存的地址时,ebp确实压进去了,因为esp内存的值是ebp的地址

fc f9 f3 00  VS编译器一般是:小端字节存储 低位字节数据放低地址处 高位字节数据放高地址处 

mov的意思是把esp的值给ebp 我们依然可以通常调试来查看是不是这样的

sub是subtraction减法的缩写。就是给esp减去0E4h 

 用16进制显示就是228

 当我们给esp减去所对应的值时,那么esp不能再指向原来的位置,而是指向了上一块的某块区域

当我们查看内存ebp和esp

ebp

esp

这些内存空间都是为main函数所开辟的空间 

接下来是在栈顶压3个元素 

随着压栈esp也会随着压栈指向位置会发生变化 

 

VS2019栈区内存存放习惯:先放高地址,再放低地址

下面esp的值会随着压栈 esp位置也产生了变化

lea指令是=load effective address. 意思是加载有效地址

 这个指令有效果的其实是rep stos意思是把edi开始下面将内存空间改成OCCCCCCCCh 以双倍字节开辟 dword=double word  es:[edi]把edi开始下面所有空间以双字节开辟成0CCCCCCCCh。

edi到ebp开辟内存空间

esp-24h的值如下图

 

 将十进制数0Ah放进ebp-8 就是相当于把10放到了ebp-8里面

如果不给a初始值那么就是随机值,因此在为main函数开辟空间时使用的就是CCCCCC的值,所以会出现烫烫烫(字符串 字符)或者随机值(变量)。

 

0a 00 00 00  就是10的十六进制存储 内存存储一般是十六进制存储

又是隔着两个字节存放的C的值0 (不同编译器存放位置不同,取决于编译器)

 

把20的值给eax再把eax压栈压进去

下一步指令把10给ecx然后再把ecx进行压栈

 按F11进入call令

内存中存放的是下一条add指令的地址00171987

 当我们再按一次F11会跳到Add函数的反汇编指令当中去

这和main函数的反汇编极其相似,这是在为Add函数开辟函数栈帧空间

 第一步Push压栈 第二步mov esp给ebp 第三步把0cch 给esp 相当于esp又往上走了

 

 

 

再进行压栈 

随着压栈esp的值也变化 esp的指向位置也随着变化

把ebp-0ch值给edi 把3给ecx 然后从edi开始下面所有位置改成0CCCCCCCH

 

 

 把0放到ebp-8 

 

 

 

 

ebp+0ch相当于+12 epb+12

通过调试过程看见传参是从右向左,先传的b再传的a

最后返回的时候把ebp-8的值也就是z的值给了寄存器eax

下面指令pop三次 esp也随着产生位置变化

当pop了三次esp的值 增加了3次

当pop三次要返回main函数 那么Add创建的函数栈帧就要销毁 这几个指令完成了把esp的值给ebp

再pop ebp 我们这个位置所保存main函数的ebp-main 就是为了函数返回时找到main函数的ebp

返回以后ebp和esp又开始维护了main函数的函数栈帧空间

这条指令就是为了执行call指令下一条add的功能 弹出了add指针地址

所以我们在开辟函数栈帧时保存了add指令地址

 当我们再按F10就回到了main函数Add的位置

main函数add指令 00171987与我们刚才在函数栈帧保存的call指令下一条指令的地址一模一样,也就是add指令函数的地址。就是为了方便回来,简直就是荣归故里!设计的太牛了!

给esp+8

随着esp+8形参的空间也销毁了。

把eax 30的值给ebp-20h 就是刚才c的位置

然后在程序结束时寄存器会返回所对应的值。

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

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

相关文章

Avalonia使一个弹窗弹到指定位置

1.项目下载地址&#xff1a;https://gitee.com/confusedkitten/avalonia-demo 2.UI库Semi.Avalonia&#xff0c;项目地址 https://github.com/irihitech/Semi.Avalonia 3.样式预览 4.PositionControl.axaml <UserControl xmlns"https://github.com/avaloniaui&quo…

华为数通方向HCIP-DataCom H12-831题库(单选题:201-220)

第201题 如图所示,路由器所有的接口开启OSPF,链路的Cost值如图中标识。若在R2的OSPF进程中通过命令import-route direct type 1引入直连路由,则R1到达10.0.2.2 /32的Cost值是以下哪一选项? A、150 B、151 C、200 D、201 答案:C 解析: Loopback0的cost值默认为0,R1-R2的…

AI 律助 Alpha GPT 线上实操发布会,重磅发布!

数字化时代,随着人工智能的迅猛发展,各行各业都在积极探索通过智能化工具实现工作效率翻升的可能性。“ ChatGPT 类产品”是未来办公应用软件发展的重要趋势之一,但如何将 ChatGPT 真正应用于法律人的工作,赋能效率提升?法律行业同样面临着新的挑战和机遇。 破局的关键是实现技…

DevExpress Reporting中文教程 - 如何在macOS等系统中生成导出报表文档

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 在本文中&#xff0c;我们将讨论如何在.NET MA…

基于Java的二手车交易管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

访问控制列表ACL讲解——想偷偷访问数据,我ACL可不同意

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 梦想从未散场&#xff0c;传奇永不落幕&#xff0c;博主会持续更新优质网络知识、Python知识、Linux知识以及各种小技巧&#xff0c;愿你我共同在CSDN进步 目录 一、ACL的基本概念 1. ACL是什么 2. 为什么需…

从零开始学习 Java:简单易懂的入门指南之线程同步(三十五)

线程同步 1.线程同步1.1卖票【应用】1.2卖票案例的问题1.3同步代码块解决数据安全问题【应用】1.4同步方法解决数据安全问题【应用】1.5Lock锁【应用】1.6死锁 2.生产者消费者2.1生产者和消费者模式概述【应用】2.2生产者和消费者案例【应用】2.3生产者和消费者案例优化【应用】…

C++程序加速方法

C程序加速方法 1. 将反复使用的数据存放在全局变量里面2. 使用多线程3. 用a和a&#xff0c;a–,--a4. 减少除法运算5. 尽量减少值传递&#xff0c;多用引用来传递参数。6. 循环引发的讨论1&#xff08;循环内定义&#xff0c;还是循环外定义对象&#xff09;7. 循环引发的讨论2…

出游热潮再起,IPIDEA代理IP帮你应对旅游数据采集的挑战

随着互联网的快速发展&#xff0c;旅游行业也随之迅速发展。在线旅游预订已经成为人们出行前的必要步骤&#xff0c;然而&#xff0c;旅游信息的采集却是一项具有挑战性的任务。为了从酒店和航空公司网站、在线旅行社和其他类似来源收集数据&#xff0c;企业需要克服许多障碍。…

valarray 包含对象成员的类(cpp14章)

C代码重用 1.公有继承可以实现 2.包含、私有继承、保护继承用于实现has-a关系&#xff0c;即新的类将包含另一个类的对象。 &#xff08;使用这样类成员&#xff1a;本身是另外一个类对象称为包含 &#xff08;组合或层次化&#xff09;。&#xff09; 3.函数模板、类模…

GoLang连接mysql数据库

跟着文档走GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly. 1.使用命令拉取 go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite2.开始使用 package mainimport ("fmt""github.com/gin-gonic/gin"&…

虚幻阴影整理

虚拟阴影贴图&#xff08;VSM&#xff09;是一种全新的阴影贴图方法&#xff0c;可以提供稳定的高分辨率阴影。通过与虚幻引擎5的Nanite虚拟几何体、Lumen全局光照和反射以及世界分区功能结合使用&#xff0c;它能够实现电影级的品质效果&#xff0c;为大型开放场景提供光照。 …

mysql case when 不命中缓存

case when 在sql 中非常方便数据不同维度统计&#xff0c;但是也会出现mysql 索引不命中问题&#xff0c;当多个case 出现时&#xff0c;需要提取出来到where里面优化 优化后 SELECT date(RecordTime) AS date, count( DISTINCT CASE WHEN Param 1 …

Java之TCP和UDP进行网络编程

目录 一.网络编程 1.1网络编程的作用 1.2网络编程的基本概念 1.3网络编程的实现 二.UDP网络编程 2.1UDP数据报套的初步了解 2.2Java数据报套接字通信模型 2.3Java编程实现UDP通信 三.TCP网络编程 3.1TCP流套接字api 3.2TCP通信代码实现 3.2.1短连接实现代码 3.…

钉钉数字校园小程序开发:开启智慧教育新时代

随着信息技术的快速发展和校园管理的日益复杂化&#xff0c;数字校园已成为现代教育的重要趋势。钉钉数字校园小程序作为一种创新应用&#xff0c;以其专业性、思考深度和逻辑性&#xff0c;为学校提供了全新的管理、教学和沟方式。本文从需求分析、技术实现和应用思考三个方面…

websocket逆向-protobuf序列化与反序列化

系列文章目录 训练地址&#xff1a;https://www.qiulianmao.com 基础-websocket逆向基础-http拦截基础-websocket拦截基础-base64编码与解码基础-protobuf序列化与反序列化视频号直播弹幕采集实战一&#xff1a;Http轮询更新中 websocket逆向-protobuf序列化与反序列化基础 系…

Apipost连接数据库详解

Apipost提供了数据库连接功能&#xff0c;在接口调试时可以使用数据库获取入参或进行断言校验。目前的Apipost支持&#xff1a;Mysql、SQL Sever、Oracle、Clickhouse、达梦数据库、PostgreSQL、Redis、MongoDB 8种数据库的连接操作 新建数据库连接&#xff1a; 在「项目设置…

【Redis】使用Java客户端操作Redis

目录 引入jedis依赖连接Redis命令get/setexists/delkeysexpire/ttltype 引入jedis依赖 连接Redis 命令 get/set exists/del keys expire/ttl type

C++笔记之获取线程ID以及线程ID的用处

C笔记之获取线程ID以及线程ID的用处 code review! 文章目录 C笔记之获取线程ID以及线程ID的用处一.获取ID二.线程ID的用处2.1.线程池管理2.2.动态资源分配2.3.使用线程同步机制实现互斥访问共享资源2.4.使用线程 ID 辅助线程同步2.5.任务分发&#xff1a;线程ID可以用于将任务…

Qt 窗口与部署应用程序发布包 day6

Qt 窗口与部署应用程序发布包 day6 QWidget QWidget是所有可视控件的基类&#xff0c;每个控件都是矩形按照Z轴顺序排序如果控件没有父控件&#xff0c;则称为窗口 设置exe窗口图标 在项目文件中新建一个文件夹Resource&#xff0c;来存放图标文件 第一种方法 用绝对路…