C语言杂谈:函数栈帧,函数调用时到底发生了什么

news2024/10/5 12:43:31

        我们都知道在调用函数时,要为函数在栈上开辟空间,函数后续内容都会在栈帧空间中保存,如非静态局部变量,返回值等。这段空间就叫栈帧

        当函数调用,就会开辟栈帧空间,函数返回时,栈帧空间就会被释放。这里的释放并非清空,而是让其无效化,可以后续的使用。

1,用到的寄存器和汇编指令

1,相关寄存器

eax:保存临时数据,返回值

ebx:保存临时数据

ebp:栈底寄存器

esp:栈顶寄存器

eip:指令寄存器,保存当前指令的下一条指令的地址

2,相关汇编指令

call:函数调用

mov:数据转移

push:出栈指令

pop:入栈指令

sub:减法指令

add:加法指令

jump:修改eip,转入目标函数调用

ret:恢复返回地址

2,虚拟内存地址

除了这些指令和寄存器,我们也需要了解虚拟内存地址是什么样子的

简单来说,高地址向低地址存储的内容分别是:内核,栈(向下增长),共享库的内存映射区域,

堆(向上增长),数据区(未初始化的数据,已初始化的数据),代码区,以及不分配使用的部分

区域。

值得注意的时,栈区是向下增长的,堆区是向上增长的。

3,代码预览

    简单总结调用函数时,发生的行为:

1,先形参实例化,按照参数列表从右向左

2,保护现场//将函数返回地址压入栈,转入目标函数

3,执行函数体

4,释放局部变量的栈帧空间

5,恢复现场//获得函数返回地址,释放栈帧空间

6,继续后续主函数语句
 

下面是演示所用的c语言代码

#include<stdio.h>
int add(int a,int b)
{
    return a+b;    
}
int main()
{
    int a=10;
    int b=10;
    int c=0;
    c=add(a,b);
    return 0;
}

4,调用过程

使用vs2022,点击调试,打开反汇编,打开寄存器

A:主函数栈帧建立

首先我们要知道,主函数也是函数,也需要建立函数栈帧,它被_tmainCRTStartup函数调用,而_tmainCRTStartup又被mainCRTStartup函数调用,mainCRTStartup函数又是被操作系统所调用的。

00007FF7AD0618D0  push        rbp  
00007FF7AD0618D2  push        rdi  
00007FF7AD0618D3  sub         rsp,148h  
00007FF7AD0618DA  lea         rbp,[rsp+20h]  
00007FF7AD0618DF  lea         rcx,[__B782E998_栈帧@c (07FF7AD071008h)]  
00007FF7AD0618E6  call        __CheckForDebuggerJustMyCode (07FF7AD061370h)  

这些是主函数栈帧建立的汇编代码

我们暂且不管这些代码,去关注寄存器的变化,重点关注espebp寄存器

这是主函数栈帧建立前栈底寄存器和栈顶寄存器的位置

这是主函数栈帧建立后栈底寄存器和栈顶寄存器的位置

可以看到栈顶寄存器的数值减少了(D20-C2C)个字节,这就说明了我们栈是由高地址向低地址增长

的,具体的建立过程我们在add函数时介绍。

B:变量初始化

单击F10我们观察寄存器

EIP指向下一条要指向的指令地址,值为00C71985,正是int a=10;这条指令。

此时,栈内空间应该是这样的:

点击F10,将变量a入栈,因为栈是从低地址到高地址增加的,所以我们将内存监视器调到010FF778,观察前后变化。

点击前

点击后

我们可以发现,变量a成功入栈了,距离栈底寄存器所在位置向上偏移八个字节单位

,同理我们将变量b和c入栈

观察到同样入栈成功,这里变量b也是0a的原因是因为数值相同,编译器进行了处理。

现在主函数栈帧就添加了三个变量。

接下来就是调用函数给c赋值了,一共有七条指令,我们一个个来看。

首先是调用Add()前(即call指令前)的4条指令,我们可以看出前两条指令的作用是先将变量b的值移

动到eax寄存器,然后以压栈push的方式压入栈中,栈顶寄存器更新,先下增长。

这里两个临时变量的产生,就是我们所说的形参实例化。我们需要注意两个点,一个是这是在调用

函数前生成的,其次就是压栈顺序是形参列表从右向左。

接下来,我们将执行函数调用指令,因为我们是通过跳转指令修改eip寄存器转入目标函数地址,

Add函数调用结束后还需返回main函数执行后续代码,所以我们需要将下一条指令的地址先保存起

来,然后进行跳转。

因此这个指令分为两步:1.将返回地址压入栈中 2.转入目标函数。

点击F11进入函数,我们可以发现函数返回后的指令地址被压入栈中(010FF67C ),然后修改eip进行跳转,转入add()函数:

C:转入add函数

下面三条是栈帧建立过程

 首先是第一条指令,单击F10,将栈底寄存器的内容压入栈中,即把main函数栈底的地址压入栈中:

因为是压栈,所以栈顶寄存器向上偏移四个字节。

然后是第二条指令,单击F10,将栈顶寄存器的内容移动到栈底寄存器,使得栈顶寄存器和栈底寄存器指向同一个地址空间:

 最后是第三条指令,单击F10,将esp栈顶寄存器的内容减去0CCH,使其向低地址偏移0C0h个字节,如下:

至此,add函数栈帧建立完成。

建立的栈帧空间

之后的内容我们在之前都有过类似的,我们需要知道几点

1,retnru语句计算时,函数参数是从之前的临时变量处取得数值进行计算

2,计算结果存储在eax寄存器中

至此,ADD函数调用完毕,进入最后一步,栈帧释放。

D:add函数销毁

栈帧的销毁我们重点来谈后三条语句,前几条语句对应着前面栈帧创建时的初始化操作,进行设置,我们不去管。

首先是第一条mov命令,我们单击F10运行,ebp栈底寄存器的值赋给esp栈顶寄存器,此时ebp与esp指向同一个地址空间: 

在这时,理论来说我们就已经释放完成了,因为add函数的内存空间已经被覆盖了。

接下来就是恢复main函数栈帧的操作了。

        我们单击F10,执行下一条pop指令,将栈顶内容弹出并放入ebp栈底寄存器中,还记得我们

刚才栈顶放的是什么了吗,是main函数栈底地址,所以此时ebp重新指向main函数栈底。

同时esp栈顶寄存器的指向发生改变。

        之后执行ret指令,ret作用是恢复返回地址,压入eip,即把栈顶元素弹出到eip指令寄存器

中,改变下一条执行的指令。我们单击F10,发现返回到了main函数,此时eip的内容就是我们之

前保存的下一条main函数指令地址,esp栈顶寄存器发生改变:

之后执行main函数中的下一条add指令,将esp栈顶寄存器的值加8并存回esp栈顶寄存器,此时esp向下偏移8个字节,指向原main函数栈顶,释放临时变量的栈帧空间。

最后使用mov将值赋给c,打印,函数结束。

后面的printf函数也会建立栈帧,但类似,不再讨论。

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

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

相关文章

读《淘宝技术这10年》:从进化中感受技术的美与挑战

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 大家好,我是小米,一个29岁的程序员,喜欢分享技术干货。今天,我想和大家聊一聊我最近读的一本书——《淘宝技术这10年》。这本书让我深刻领悟…

解决IDEA拉取或提交代码时SSL certificate problem: unable to get local issuer certificate问题

在使用IDEA拉取或提交GitHub上的代码时提示如下错误&#xff1a; 解决方法如下&#xff0c;依次执行命令&#xff1a; 在项目根目录下&#xff0c;打开Git Bash 1、echo | openssl s_client -servername github.com -connect github.com:443 | sed -ne /-BEGIN CERTIFICATE-/…

23 - 每位教师所教授的科目种类的数量(高频 SQL 50 题基础版)

23 - 每位教师所教授的科目种类的数量 考点&#xff1a; 排序和分组 selectteacher_id,count(distinct subject_id) cnt fromTeacher group byteacher_id;

SL3160 替换PN6008 耐压150V 降5V输出降压恒压芯片 脚位一样

在电子工程领域&#xff0c;芯片替换是一项常见且至关重要的任务。今天&#xff0c;我们将详细探讨SL3160芯片如何替换PN6008芯片&#xff0c;重点关注两者在耐压150V和降5V输出降压恒压方面的性能对比&#xff0c;以及它们脚位一致性的优势。 我们来了解一下SL3160和PN6008这两…

AE/PR插件中文汉化-StyleX V1.0.1动漫卡通绘画半色调漫画风格化特效

插件StyleX是一款采用人工智能AI技术的高级视频风格化工具&#xff0c;它不仅可以将素材转换为高质量的卡通片&#xff0c;还可以生成给定图像或视频风格的任意风格化视频。使用此工具&#xff0c;用户可以轻松创建创意风格化效果&#xff0c;例如卡通、绘画、绘画、半色调等等…

【大数据】计算引擎:Spark核心概念

目录 前言 1.什么是Spark 2.核心概念 2.1.Spark如何拉高计算性能 2.2.RDD 2.3.Stage 3.运行流程 前言 本文是作者大数据系列中的一文&#xff0c;专栏地址&#xff1a; https://blog.csdn.net/joker_zjn/category_12631789.html?spm1001.2014.3001.5482 该系列会成体…

翻译《The Old New Thing》- What’s with this MSH_MOUSEWHEEL message?

Whats with this MSH_MOUSEWHEEL message? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080806-00/?p21353 Raymond Chen 2008年06月06日 MSH_MOUSEWHEEL 消息是怎么回事&#xff1f; 硬件团队正在研发一种鼠标滚轮设备&#xff0c;并…

LlamaIndex 一 简单文档查询

前言 在学习LangChain的时候&#xff0c;我接触到了LlamaIndex。它犹如我在开发vue时用到的axios&#xff0c;主要负责数据打理。别问我为什么打这个比方&#xff0c;前端老狗&#xff0c;重走AI路&#xff0c;闭关一年能否学的妥当&#xff1f; LlamaIndex 是一个用于 LLM 应…

【Python】教你彻底认识Python中的Web开发

​​​​ 文章目录 一、Web开发的基本概念1. Web服务器2. 客户端-服务器模型3. HTTP协议4. 前端与后端 二、常用的Web开发框架1. Django1.1 安装Django1.2 创建Django项目1.3 定义模型1.4 定义视图1.5 定义URL路由1.6 模板 2. Flask2.1 安装Flask2.2 创建Flask应用2.3 模板2.4…

Day 41 NGINX详解

Nginx详解 一、HTTP协议 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;的缩写,是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 1、HTTP 工作原理 HTTP协议通信流程 WEB Server&…

队列的实现及其应用

队列的概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作&#xff0c;具有先进先出的效果。入数据的一段称为队尾&#xff0c;出数据的一段称为队头。队列的应用是&#xff1a;1.维持公平性&#xff08;抽号&#xff09;、2.广度优先遍历…

JVM垃圾收集器和性能调优

目标&#xff1a; 1.JVM垃圾收集器有哪几种&#xff1f; 2.CMS垃圾收集器回收步骤。 一、JVM常见的垃圾回收器 为什么垃圾回收的时候需要STW? 标记垃圾的时候&#xff0c;如果不STW&#xff0c;可能用户线程就会不停的产生垃圾。 1.1 单线程收集 Serial和SerialOld使用单…

大语言模型应用与传统程序的不同

大语言模型&#xff08;LLM&#xff09; 被描述的神乎其神&#xff0c;无所不能&#xff0c;其实&#xff0c;大语言模型只是一个模型&#xff0c;它能够理解和生成自然语言&#xff0c;唯有依靠应用程序才能够发挥作用。例如&#xff0c;基于大模型可以构建一个最简单的会话机…

黑马python-JavaScript

1.JavaScript的定义&#xff1a; JavaScript是运行在浏览器端的脚步语言&#xff0c;是由浏览器解释执行的、简称js。它能够让网页和用户有交互功能&#xff0c;增加良好的用户体验效果 2.使用方式&#xff1a; 1.行内式&#xff08;主要用于事件&#xff09; <input type&q…

24-unittest简介

一、unittest简介 unittest是Python中常用的单元测试框架&#xff0c;与Java中的Junit单元测试框架类似。 二、示例程序 1&#xff09;导入unittest模块 import unittest 2&#xff09;使用help()函数查看源码中的示例程序 help(unittest) Simple usage:import unittestc…

Unity DOTS技术(二)ECS

文章目录 一.ECS简介二.基本操作实例三.实体查看组件四.位置实体修改五.旋转实体六.了解原有属性七.禁止自动创建八.动态实例化 一.ECS简介 ECS全称 (Entity Component System).即(E:实体 C:组件 S:系统). 实际上即是数据方法分离.数据放在组件里,具体实现放在系统里.组件挂载…

【excel】设置二级可变联动菜单

文章目录 【需求】在一级菜单选定后&#xff0c;二级菜单联动显示一级菜单下的可选项【步骤】step1 制作辅助列1.列转行2.在辅助列中匹配班级成员 step2 名称管理器step3 制作二级下拉菜单step4 消除二级菜单中的空白 【总结】 之前做完了 【excel】设置可变下拉菜单&#xff…

NPDP|智造业产品经理的战略智慧与行动之道

在智能制造风起云涌的时代&#xff0c;智造业产品经理的角色愈发重要。他们不仅需要具备深厚的行业知识&#xff0c;更要拥有前瞻的战略眼光和高效的行动能力。那么&#xff0c;智造业产品经理如何进行战略思考与行动呢&#xff1f;本文将为您揭示其中的奥秘。 洞察市场趋势&am…

01_初识微服务

文章目录 一、微服务概述1.1 什么是微服务1.2 对比微服务架构与单体架构1.3 微服务设计原则1.4 微服务开发框架1.5 简单理解分布式部署与集群部署 二、微服务的核心概念2.1 服务注册与发现2.2 微服调用&#xff08;通信&#xff09;2.3 服务网关2.4 服务容错2.5 链路追踪参考链…

用友NC pagesServlet SQL注入致RCE漏洞复现(XVE-2024-13067)

0x01 产品简介 用友NC是由用友公司开发的一套面向大型企业和集团型企业的管理软件产品系列。这一系列产品基于全球最新的互联网技术、云计算技术和移动应用技术,旨在帮助企业创新管理模式、引领商业变革。 0x02 漏洞概述 用友NC /portal/pt/servlet/pagesServlet/doPost接口…