函数栈帧理解
本文是从汇编角度来展示的函数调用,而且是在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后续)
这就是个人对函数栈帧的全部理解,之前学了觉得理解了,但是时间久了反而有点忘了,所以就狠下心来写了这篇博客,因为画很多图来结合分析。
相关文章:
函数栈帧理解
本文是从汇编角度来展示的函数调用,而且是在vs2013下根据调试展开的探究,其它平台在一些指令上会有点不同,指令不多,简单记忆一下即可,在我前些年的学习中,学的这几句汇编指令对我调试找错误起了不小的作用…...
【SA8295P 源码分析】70 - QAM8295P 原理图参考设计 之 DP、eDP 接口硬件原理分析
【SA8295P 源码分析】70 - QAM8295P 原理图参考设计 之 DP、eDP 接口硬件原理分析 一、DP 接口(Display Port)介绍二、高通参考硬件原理图分析2.1 高通 Display 接口框图介绍2.2 DP接口 Pin 定义介绍2.3 高通参考设计:DP2、DP3 硬件原理图2.4 高通参考设计:eDP0、eDP1 硬件…...
【CSS动画02--卡片旋转3D】
CSS动画02--卡片旋转3D 介绍代码HTMLCSS css动画02--旋转卡片3D 介绍 当鼠标移动到中间的卡片上会有随着中间的Y轴进行360的旋转,以下是几张图片的介绍,上面是鄙人自己录得一个供大家参考的小视频🤭 代码 HTML <!DOCTYPE html>…...
数据结构<树和二叉树>顺序表存储二叉树实现堆排
✨Blog:🥰不会敲代码的小张:)🥰 🉑推荐专栏:C语言🤪、Cpp😶🌫️、数据结构初阶💀 💽座右铭:“記住,每一天都是一個新的開始…...
理解docker命令
基础命令 帮助命令 docker --help(帮助命令) 用于获取某个命令的帮助信息 #命令帮助 docker 命令 --help 小技巧 换行符 \ 使用命令换符,可以让繁杂命令变得有条理 #命令换行,使用换行符 \ docker ... \... \ 镜像命令 d…...
【SA8295P 源码分析】16 - QNX侧 TouchScreen Panel (TP)线程函数 tp_recv_thread 源码分析
【SA8295P 源码分析】16 - QNX侧 TouchScreen Panel (TP)线程函数 tp_recv_thread 源码分析 一、TP 线程函数:tp_recv_thread()二、处理&上报 坐标数据 cypress_read_touch_data()系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接:《【SA8295P…...
第九章MyBatis的技巧
${}和#{}的区别 #{}给sql语句的占位符传值${}直接将值拼接到sql语句上,存在sql注入的现象 什么时候用${} 需要先对sql语句拼接,然后再编译。 字符串排序字段向SQL语句中拼接表名。比如根据日期生成日志表 批量删除 delete from car where in(${ids}…...
计算机技术与软件专业技术资格(水平)考试----系统架构设计师
【原文链接】计算机技术与软件专业技术资格(水平)考试----系统架构设计师 考试简介 计算机软件资格考试是由国家人力资源和社会保障部、工业和信息化部领导下的国家级考试。计算机软件资格考试既是职业资格考试,又是职称资格考试。考试合格…...
使用nrm快速切换npm源以及解决Method Not Implemented
文章目录 什么是nrm如何使用nrm查看本机目前使用的npm 源安装nrm查看可选源查看当前使用源切换源添加源删除源测试源的响应时间 如果你遇到这个报错,就可以采用这种方案解决哦解决方案:1. 切换为官方源2. 查看漏洞3. 修复漏洞4. 下面命令慎重使用&#x…...
NVIDIA Jetson 项目:机器人足球比赛
推荐:使用 NSDT场景编辑器 助你快速搭建可二次编辑器的3D应用场景 事实上,整个比赛都致力于这个想法。RoboCup小型联盟(SSL)视觉停电技术挑战赛鼓励团队“探索本地传感和处理,而不是非车载计算机和全球摄像机感知环境的…...
【论文解读】Hybrid-SORT: Weak Cues Matter for Online Multi-Object Tracking
因为Hybrid-SORT的baseline是基于OCSORT进行改进的,在这之前建议先了解byteTrack和【】的相关知识 1.介绍 1.1 基本框架 多目标跟踪(MOT)将问题分为两个子任务。第一个任务是检测每个帧中的对象。第二个任务是将它们在不同的框架中联系起来。关联任务主要通过显式…...
Microsoft 图像BERT,基于大规模图文数据的跨模态预训练
视觉语言任务是当今自然语言处理(NLP)和计算机视觉领域的热门话题。大多数现有方法都基于预训练模型,这些模型使用后期融合方法融合下游任务的多模态输入。然而,这种方法通常需要在训练期间进行特定的数据注释,并且对于…...
vue3+elementUI-plus实现select下拉框的虚拟滚动
网上查了几个方案,要不就是不兼容,要不就是不支持vue3, 最终找到一个合适的,并且已上线使用,需要修改一下样式: 代码如下: main.js里引用 import vue3-virtual-scroller/dist/vue3-virtual-scroller.css; …...
学C的第三十四天【程序环境和预处理】
相关代码gitee自取: C语言学习日记: 加油努力 (gitee.com) 接上期: 学C的第三十三天【C语言文件操作】_高高的胖子的博客-CSDN博客 1 . 程序的翻译环境和执行环境 在ANSI C(C语言标准)的任何一种实现中,存在两个不同的环境。 ࿰…...
微服务中间件--Ribbon负载均衡
Ribbon负载均衡 a.Ribbon负载均衡原理b.Ribbon负载均衡策略 (IRule)c.Ribbon的饥饿加载 a.Ribbon负载均衡原理 1.发起请求http://userservice/user/1,Ribbon拦截该请求 2.Ribbon通过EurekaServer拉取userservice 3.EurekaServer返回服务列表给Ribbon做负载均衡 …...
字符设备驱动实例(ADC驱动)
四、ADC驱动 ADC是将模拟信号转换为数字信号的转换器,在 Exynos4412 上有一个ADC,其主要的特性如下。 (1)量程为0~1.8V。 (2)精度有 10bit 和 12bit 可选。 (3)采样时钟最高为5MHz,转换速率最高为1MSPS (4)具有四路模拟输入,同一时…...
python基础5——正则、数据库操作
文章目录 一、数据库编程1.1 connect()函数1.2 命令参数1.3 常用语句 二、正则表达式2.1 匹配方式2.2 字符匹配2.3 数量匹配2.4 边界匹配2.5 分组匹配2.6 贪婪模式&非贪婪模式2.7 标志位 一、数据库编程 可以使用python脚本对数据库进行操作,比如获取数据库数据…...
SpringAOP原理:手写动态代理实现
0、基础知识 AOP我们知道,是在不修改源代码的情况下,为代码添加一些新功能的技术。通过动态代理,可以在不修改原始类代码的前提下,对方法进行拦截和增强。 动态代理常用于在不改变原有业务逻辑的情况下,对方法…...
【旅游度假】Axure酒店在线预订APP原型图 旅游度假子模块原型模板
作品概况 页面数量:共 10 页 兼容软件:Axure RP 9/10,不支持低版本 应用领域:旅游度假,生活服务 作品申明:页面内容仅用于功能演示,无实际功能 作品特色 本作品为「酒店在线预订」的移动端…...
Android JNI系列详解之CMake和ndk-build编译工具介绍
一、前提 CMake和ndk-build只是编译工具,本次主要介绍ndk-build和CMake的区别,下节课介绍他们的使用。 二、CMake工具介绍 CMake:cross platform make,是跨平台的编译工具 CMake是在AndroidStudio2.2之后引入(目前默认…...
Ubuntu 20.04黑屏救星:手把手教你用tty2命令行重装NVIDIA驱动(附内核更新关闭指南)
Ubuntu 20.04黑屏救援实战:从tty2命令行到图形界面恢复全指南 当你满心欢喜地启动Ubuntu 20.04,准备开始一天的工作时,迎接你的却是一片漆黑——这是许多Linux用户都曾遭遇过的噩梦场景。NVIDIA驱动问题导致的系统黑屏不仅令人沮丧࿰…...
Python 爬虫进阶技巧:多线程异步爬取大幅提升数据采集速度
前言 常规单线程爬虫采用串行阻塞式请求模式,严格按照 “请求页面 — 解析数据 — 保存入库 — 下一页请求” 的线性流程执行,每一次网络请求都需要等待服务器响应、网络传输延时完成后,才能发起下一次任务。在大批量站点列表、分页数据、多…...
告别MATLAB命令行里的‘天书’:手把手教你用symdisp优雅展示LaTeX公式
MATLAB符号计算可视化革命:用symdisp实现LaTeX级公式渲染 在科研和工程计算领域,MATLAB的符号计算工具箱一直是数学推导的利器,但长期以来,命令行输出的公式展示方式让许多研究者头疼——密密麻麻的文本表达式不仅难以直观理解&am…...
17 LCD1602模块——显示屏
一、51单片机模块二、LCD1602模块三、模块间的连接单片机P2端口:P2_5~P2_7单片机P0端口:P0_0~P0_7四、LCD1602芯片1、参数和引脚这里只需要了解单片机的引脚功能,也可以大致看一眼,后面在编码显示功能的时候,也会做详细…...
数据结构(哈希函数)
#pragma once //之前已经学完的,顺序表,链表等 他们总是有一个共有的特征,数据和其存储之间是没有任何关系的 //现在的需求 让查找函数的时间复杂度达到O(1); //让数据和其存储位置之间产生某种函数(映射)关系 这就是哈…...
MCP Analytics Suite:用自然语言驱动AI数据分析,零代码生成专业报告
1. 项目概述:当AI助手遇上专业数据分析如果你和我一样,日常工作中需要处理大量的业务数据——可能是Shopify的订单报表、Stripe的支付流水,或者是一堆从各个渠道导出的CSV文件——那你一定体会过那种“数据在手,却无从下手”的焦虑…...
[具身智能-694]:万物皆智能,万物皆 ROS2:未来所有带感知、能运动、可交互的硬件终端,都能用 ROS2 做底座,智能普惠全域设备。万物接入 ROS2,就是接入标准化、开源化、互联化的智能时代。
一、为什么说「万物皆智能」从传统机电设备 → 感知 决策 执行一体化:普通家电、工业设备、移动载体、穿戴设备、楼宇设施,都在加传感器、算力、通信、自主决策,不再是被动受控,而是具备自主感知、逻辑判断、联动协作的智能属性…...
PostgreSQL 与 MySQL InnoDB 隔离级别 MVCC 核心对比
一、数据库标准四大隔离级别 SQL 标准定义 4 个隔离级别,从低到高: 读未提交(Read Uncommitted)读已提交(Read Committed,RC)可重复读(Repeatable Read,RR)串…...
构建离线文档ETL管道:用Python实现PDF/Word智能转Markdown优化LLM输入
1. 项目概述:为什么我们需要一个离线的文档转换工具?如果你和我一样,经常需要把一堆PDF、Word文档甚至扫描件喂给本地的大语言模型(比如Ollama、LM Studio),那你肯定遇到过这个痛点:模型宝贵的上…...
Cadence IC617虚拟机导入后,Calibre DRC报License错误的保姆级修复指南
Cadence IC617虚拟机导入后Calibre DRC报License错误的终极解决方案 当你兴冲冲地打开从同事那里拷贝的Cadence IC617虚拟机镜像,准备开始芯片设计工作时,突然跳出的Calibre DRC license错误提示就像一盆冷水浇下来。这种"拿来即用"的环境本应…...
