函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)

函数栈帧的创建和销毁[以C语言代码为例,汇编代码的角度分析]

  • 一.前言
    • 1.几个问题
    • 2.几个说明
  • 二.相关寄存器和汇编命令的简要说明
  • 三.从汇编代码调试的角度逐步分析函数栈帧的创建于销毁
    • 1.函数栈区的知识:
    • 2.逐步调试分析
      • 1.保存__tmainCRTStartup这个函数栈帧的栈底地址
      • 2.正式进入main函数
      • 3.开辟main函数栈帧
      • 4.将main函数栈帧中的数据置为随机值
      • 5.函数传参的准备
        • 1.创建形参
        • 2.将call指令的下一条指令的地址压栈
      • 6.正式进入MyAdd函数并且开辟栈帧,置为随机值
      • 7.取出形参的值
      • 8.销毁MyAdd函数栈帧
      • 9.返回
  • 四.总结

一.前言

1.几个问题

在C语言学习阶段,我们可能会遇到下面几个问题,
在学习完函数栈帧的创建和销毁之后,我们就能更加深刻地理解下面几个问题了
在这里插入图片描述

2.几个说明

其次,我们要说明的是:不同编译器下汇编指令的样子是有所差异的
下面给大家看一下同样的代码在VS2013中的样子
在这里插入图片描述
同样的代码在Linux中的样子
在这里插入图片描述
而且在观察汇编代码学习函数栈帧的创建和销毁的过程中.
不要使用太高级的编译器,越高级的编译器越不容易学习和观察

同时在不同的编译器下,函数的调用过程是略有差异的,具体细节取决于编译器的实现

我们这一篇博客以VS2013为例,学习函数栈帧的创建和销毁的过程

二.相关寄存器和汇编命令的简要说明

在这里插入图片描述

三.从汇编代码调试的角度逐步分析函数栈帧的创建于销毁

我们以这份代码为例:

#include <stdio.h>
int MyAdd(int a, int b)
{int c = a + b;return c;
}
int main()
{int x = 0xA;int y = 0xB;int z = MyAdd(x, y);return 0;
}

1.函数栈区的知识:

首先我们要说明两点:

1.函数是开辟在栈区的,栈区空间的使用习惯是
先使用高地址,后使用低地址

也就是说函数栈区是从高到低去开辟的

2.main函数是也是被调用的
具体调用流程如下
在这里插入图片描述

2.逐步调试分析

在这里插入图片描述
在这里插入图片描述
初始情况:
esp(栈顶指针)
ebp(栈底指针)
esp和ebp之间有一块空间
这块空间其实就是__tmainCRTStartup的栈帧
在这里插入图片描述

1.保存__tmainCRTStartup这个函数栈帧的栈底地址

执行第一条指令:

push ebp
把ebp的值入栈,并且esp减小

在这里插入图片描述

2.正式进入main函数

执行第二条指令:

mov ebp esp
就是把esp的值给ebp

在这里插入图片描述

3.开辟main函数栈帧

执行第3条指令

sub esp 0E4hesp = esp-0E4h
也就是esp减少0E4h大的空间

这就是为main函数开辟栈帧
也就是说在汇编中开辟栈帧的方式就是栈顶指针减少

其中
函数的栈帧大小是由编译器决定的
根据什么决定的呢?
编译器可以通过sizeof求出该函数栈帧中的所有变量的具体大小
并进行合理分配栈帧的大小

在这里插入图片描述

4.将main函数栈帧中的数据置为随机值

接下来是3条push指令

push ebx
push esi
push edi

在这里插入图片描述
请注意:esp每一次push之后的值都减4
为什么呢?
因为push是压栈操作.我们可以理解为push进去的数据在内存空间上是紧密相邻的

接下来是:

lea edi,[ebp+FFFFFF1Ch]
就是把ebp+FFFFFF1Ch这个地址加载到edi中其实这个ebp+FFFFFF1Ch就是ebp-0E4h
而这个0E4h就是第三条指令中
sub esp 0E4h
这个esp减去的大小
也就是这个main函数的栈帧大小

在这里插入图片描述
下面两条指令:

mov ecx,39h
mov eax,0CCCCCCCCh
把16进制数字:39h给ecx
把0CCCCCCCCh给eax

在这里插入图片描述
下面这个指令:

rep stos    dword ptr es:[edi]

在这里插入图片描述
因此我们就可以回答

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

因为函数栈帧创建之后,会对栈帧中的数据进行初始化,
而局部变量是开辟在栈帧中的,

如果没有对该局部变量进行初始化
那么该局部变量的值就是随机值

而VS2013中的随机值就是0CCCCCCCCh
这也就解释了为什么我们经常会见到烫烫烫烫烫烫这样的字符
在这里插入图片描述
这就是我们初始化后的栈帧空间
在这里插入图片描述
执行完刚才那条指令之后ecx被清0
edi会指向ebp

5.函数传参的准备

1.创建形参

下面两条指令在main函数的栈帧中创建了x和y这两个局部变量

mov   dword ptr [ebp-8],0Ah
mov   dword ptr [ebp-14h],0Bh把0Ah(就是10进制的10)赋值给ebp-8内存空间的值
把0Bh(就是10进制的11)赋值给ebp-14h内存空间的值

这里我们就可以回答第一个问题了:局部变量是如何创建的?

局部变量是通过栈底指针的偏移定位到空间
将对应空间分配给局部变量
然后将该空间内的值修改为对应初始化的值
在这里插入图片描述
下面四条指令是:

mov     eax,dword ptr [ebp-14h]
push    eax
mov     ecx,dword ptr [ebp-8]
push    ecx
本质是形成形参进行函数传参
其中这个eax中放的是y的值
ecx中放的是x的值

因此这也就解释了第三条问题:

函数是如何传参的?

通过栈底指针的偏移找到实参的值,
把对应实参的值传递给寄存器eax和ecx
然后把eax和ecx的值入栈
就形成了形参

也就是说形参是实参的一份拷贝,改变形参不会影响实参

传参的顺序是怎样的?

顺序是从右向左传参
在这里插入图片描述
eax变为11
ecx变为10

2.将call指令的下一条指令的地址压栈

下面就要进行call指令调用MyAdd函数了
首先编译器会先把call指令的下一条指令保存下来
也就是说这里实际上是把add指令的地址压入了栈帧当中
也就是执行了
push add的地址
在这里插入图片描述
在这里插入图片描述
下一条是jmp命令
这个命令就很简单
执行完jmp命令后就正式进入了MyAdd函数

jmp  00bb13c0

这样我们就能回答第5个问题了:

函数调用是怎么做的?

函数调用时,先形成形参,
然后把main函数的栈底指针的地址保存下来
然后把对应函数调用指令的下一条指令的地址压入栈中

然后正式进入MyAdd函数

6.正式进入MyAdd函数并且开辟栈帧,置为随机值

然后我们进入MyAdd函数
在这里插入图片描述
下面这10条指令跟创建main函数栈帧的指令如出一辙
就是先把main函数的栈底地址入栈
然后把esp的值赋给ebp:其实就是正式进入MyAdd这个函数的栈帧
然后sub esp,0CCh就是为MyAdd函数创建栈帧
push ebx,esi,edi 为后续初始化MyAdd函数栈帧做准备

rep stos dword ptr es:[edi]: 就是把MyAdd函数栈帧中的空间初始化为随机值0CCCCCCCCh

push        ebp  
mov         ebp,esp  
sub         esp,0CCh  
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp+FFFFFF34h]  
mov         ecx,33h  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi] 

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

7.取出形参的值

然后下面这三条指令就是:

mov         eax,dword ptr [ebp+8]  
add         eax,dword ptr [ebp+0Ch]  
mov         dword ptr [ebp-8],eax  其实就是通过ebp栈底指针的偏移找到创建的形参,取出形参的值
赋值,相加给eax
然后通过eax的赋值和ebp栈底指针的偏移创建MyAdd函数栈帧中的局部变量c

在这里插入图片描述

8.销毁MyAdd函数栈帧

下面这5条指令就是

mov eax,dword ptr [ebp-8]
pop edi
pop dsi
pop ebx
mov esp,ebp

保存返回值并且销毁MyAdd这个函数栈帧
在这里插入图片描述

9.返回

其中:

返回的本质是:
1.返回到main函数的栈帧
2.返回到call指令的下一条指令的地址处
3.如果有返回值,把返回值进行返回

下面两条指令就会
分别将ebp和esp返回到main函数中的对应的位置

pop     ebp
ret

在这里插入图片描述
最后两条指令

add esp,8
mov    dword ptr [ebp-20h] eax

在这里插入图片描述
这样就能回答第6个问题了

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

首先先把返回值进行临时拷贝,在MyAdd这个函数中是用寄存器eax来保存这个返回值
然后通过弹出main函数的栈底地址,将栈底指针ebp恢复为main函数的栈底指针

通过弹出call指令的下一条指令的地址并将这个地址存入eip中
保证call指令结束后能够回到call指令的下一条指令的位置

我们同时也可以看出:
形参先创建,然后再调用函数
函数先销毁,然后再销毁形参

函数的销毁和形参的销毁都是通过栈顶指针的增大实现的

函数栈帧的创建是通过栈顶指针的减小实现的

在函数中创建局部变量是通过栈底指针的偏移定位到要创建的位置
通过eax,ecx等等寄存器来完成变量值的传递

以上就是以MyAdd为例,
整个函数栈帧的创建和销毁过程的分析

四.总结

综上,我们可以得出
在这里插入图片描述

以上就是函数栈帧的创建和销毁的全部内容,希望能对大家有所帮助!

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

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

相关文章

合肥中科深谷嵌入式项目实战——人工智能与机械臂(三)

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 作者&#xff1a;爱吃饼干的小白鼠。Python领域优质创作者&#xff0c;2022年度博客新星top100入围&#xff0c;荣获多家平台专家称号。…

kafka3.X集群安装(不使用zookeeper)

一、kafka集群实例角色规划 在本专栏的之前的一篇文章《kafka3种zk的替代方案》已经为大家介绍过在kafka3.0种已经可以将zookeeper去掉。 上图中黑色代表broker&#xff08;消息代理服务&#xff09;&#xff0c;褐色/蓝色代表Controller(集群控制器服务) 左图&#xff08;kafk…

[BUUCTF NewStarCTF 2023 公开赛道] week4 crypto/pwn

再补完这个就基本上完了. crypto RSA Variation II Schmidt-Samoa密码系统看上去很像RSA,其中Npqq, 给的eN给了d from secret import flag from Crypto.Util.number import *p getPrime(1024) q getPrime(1024)N p*p*qd inverse(N, (p-1)*(q-1)//GCD(p-1, q-1))m bytes…

计算机视觉的相机选型

#你一般什么时候会用到GPT?# 目前市面上的工业相机大多是基于CCD&#xff08;ChargeCoupled Device&#xff09;或CMOS&#xff08;Complementary Metal Oxide Semiconductor&#xff09;芯片的相机。一般CCD制造工艺更加复杂&#xff0c;也会更贵一点&#xff01; 1、CCD工…

40基于MATLAB,使用模板匹配法实现车牌的识别。

基于MATLAB&#xff0c;使用模板匹配法实现车牌的识别。具体包括将原图灰度化&#xff0c;边缘检测&#xff0c;腐蚀操作&#xff0c;车牌区域定位&#xff0c;车牌区域矫正&#xff0c;二值化&#xff0c;均值滤波&#xff0c;切割&#xff0c;字符匹配&#xff0c;最终显示车…

mycat2.X读写分离

一、数据库中间件介绍 二、下载安装包 2.1下载地址: 下载两个一个是mycat程序,一个是mycat的驱动 http://dl.mycat.org.cn/2.0/install-template/mycat2-install-template-1.20.zip http://dl.mycat.org.cn/2.0/1.21-release/mycat2-1.21-release-jar-with-dependencies-2…

git本地搭建服务器[Vmware虚拟机访问window的git服务器]

先按照https://zhuanlan.zhihu.com/p/494988089说明下载好Gitblit然后复制到tomcat的webapps目录下,如下: 双击"startup.bat"启动tomcat: 然后访问"http://127.0.0.1:8080/gitblit/"即可看到git的界面: 说明git服务器已经能够成功运行了! Vmware虚拟机…

vue源码分析(七)—— createComponent

文章目录 前言一、createComponent 参数说明二、createComponent 源码详解1.baseCtor的实际指向2.extend 方法3.判断Ctor是否是函数的判断4.installComponentHooks方法5.返回一个带标识的组件 vnode 前言 createComponent文件的路径&#xff1a; src\core\vdom\create-componen…

windows下使用FFmpeg开源库进行视频编解码完整步聚

最终解码效果: 1.UI设计 2.在控件属性窗口中输入默认值 3.复制已编译FFmpeg库到工程同级目录下 4.在工程引用FFmpeg库及头文件 5.链接指定FFmpeg库 6.使用FFmpeg库 引用头文件 extern "C" { #include "libswscale/swscale.h" #include "libavdevic…

Failed to prepare the device for development

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

IDEA 使用技巧

文章目录 语言支持简化编写 有问题&#xff0c;可暂时跳过 个人常用快捷键插件主题插件功能插件 碰到过的问题 除了一些在Linux上用vim开发的大佬&#xff0c;idea算是很友好的集成开发工具了&#xff0c;功能全面&#xff0c;使用也很广泛。 记录一下我的 IDEA 使用技巧&#…

【数据分析】上市公司半年报数据分析

前言 前文介绍过使用网络技术获取上市公司半年报数据的方法&#xff0c;本文将对获取到的数据进行简要的数据分析。 获取数据的代码介绍在下面的两篇文章中 【java爬虫】使用selenium获取某交易所公司半年报数据-CSDN博客 【java爬虫】公司半年报数据展示-CSDN博客 全量数…

Java工具库——commons-lang3的50个常用方法

未来的你&#xff0c;我亲爱的女孩&#xff0c;愿此刻无忧无虑&#xff0c;开心&#xff0c;快乐… 工具库介绍 Apache Commons Lang 3&#xff08;通常简称为Commons Lang 3&#xff09;是Apache Commons项目中的一个Java工具库&#xff0c;它提供了一系列实用的工具类和方法…

骨传导耳机到底好用吗,2023年骨传导耳机该怎么选

骨传导耳机到底好用吗&#xff0c;骨传导耳机是一种完全颠覆你听音体验的黑科技&#xff01;不仅能够让你享受音乐的同时保护你的听力&#xff0c;还能让你感受到一种前所未有的新鲜感。很显然&#xff0c;骨传导耳机是真的好用&#xff0c;现在市面上有许多品牌的骨传导耳机&a…

24 行为型模式-访问者模式

1 访问者模式介绍 访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式。 2 访问者模式原理 3 访问者模式实现 我们以超市购物为例,假设超市中的三类商品: 水果,糖…

.NET、VUE利用RSA加密完成登录并且发放JWT令牌设置权限访问

后端生成公钥私钥 使用RSA.ToXmlString(Boolean) 方法生成公钥以及私钥。 RSACryptoServiceProvider rSA new(); string pubKey rSA.ToXmlString(false);//公钥 string priKey rSA.ToXmlString(true);//私钥 后端将生成的公钥发送给前端 创建一个get请求&#xff0c;将…

【Linux】centOS7安装配置及Linux的常用命令---超详细

一&#xff0c;centOS 1.1 centOS的概念 CentOS&#xff08;Community Enterprise Operating System&#xff09;是一个由社区支持的企业级操作系统&#xff0c;它是以Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码为基础构建的。CentOS提供了一个稳定、可靠且…

macOS M1安装wxPython报错‘tiff.h‘ file not found的解决方法

macOS12.6.6 M1安装wxPython失败&#xff1a; 报错如下&#xff1a; imagtiff.cpp:37:14: fatal error: tiff.h file not found解决办法&#xff1a; 下载源文件重新编译&#xff08;很快&#xff0c;5分钟全部搞定&#xff09;&#xff0c;分三步走&#xff1a; 第一步&…

完整的电商平台后端API开发总结

对于开发一个Web项目来说&#xff0c;无论是电商还是其他品类的项目&#xff0c;注册与登录模块都是必不可少的&#xff1b;注册登录功能也是我们在日常生活中最长接触的&#xff0c;对于这个业务场景的需求与逻辑大概是没有什么需要详细介绍的&#xff0c;市面上常见的邮箱注册…

如何将word格式的文档转换成markdown格式的文档

如何将word格式的文档转换成markdown格式的文档 前言 A. 介绍Markdown和Word格式文档 什么是Markdown&#xff1f; Markdown是一种轻量级标记语言&#xff0c;旨在简化文本格式化和排版的过程。它以纯文本形式编写&#xff0c;通过使用简单的标记语法&#xff0c;使文档更具…