函数栈帧的创建和销毁,带动图详细解析,带你大致分析汇编代码

目录

1.什么是函数栈帧

2.理解函数栈帧有什么用?

3.函数栈帧的创建和销毁解析

3.1什么是栈?

3.2 认识相关寄存器和汇编指令

3.3函数栈帧的创建和销毁解析过程

3.4函数的调用

3.5汇编代码

3.5.1函数栈帧的创建

3.5.2main函数部分

3.5.3Add函数部分

3.5.4main函数剩下部分


1.什么是函数栈帧

  1. 在写C语言程序的时候,经常为了实现一个功能来封装一个函数,C语言是以函数为基础的基本单位
    1. 函数是怎么调用的?
    2. 函数是怎么传参的?
    3. 函数的返回值怎么带回?
  2. 上面这些问题都与函数栈帧有关系
  3. 函数栈帧(stack frame),在函数调用时,系统会调用栈(call stack)所开辟空间,这些空间用来存放:
    1. 函数参数和函数返回值
    2. 临时变量(函数静态局部变量和编译器自动产生的其他临时变量)
    3. 保存上下文信息(需要保持不变的寄存器)

2.理解函数栈帧有什么用?

  1. 只要理解了函数栈帧的创建和销毁,以下的问题可以很好的理解了
    1. 局部变量是如何创建的?
    2. 局部变量在不初始化内容的情况下为什么是随机的?
    3. 函数调用时参数如何传递的?
    4. 传参的先后顺序是怎样的?
    5. 函数的返回值如何返回?
  2. 以上问题学习完,下面的都可以得到答案了

3.函数栈帧的创建和销毁解析

3.1什么是栈?

  1. 现在计算机程序都会用到栈,有了栈才有了函数和局部变量,才会有现在的计算机语言
  2. 这个栈是内存中的栈和数据结构中的不一样的,要区分开;栈的入数据(Push),入数据只会在栈顶,栈的出数据(Pop),这只会在堆顶;遵循着后进先出的原则
  3. esp:是指向栈顶的,Pop数据或者Push数据,esp的指向会刷新
  4. 栈是由高到低增长的(高地址处向低地址处增长)

3.2 认识相关寄存器和汇编指令

  1. 相关寄存器
    1. eax:通用寄存器,保留临时数据,常用于返回值
    2. ebx:通用寄存器,保留临时数据
    3. ebp:栈底寄存器
    4. esp:栈顶寄存器
    5. eip:指令寄存器,保存当前指令的下一条指令的地址
  1. 相关汇编命令
    1. mov:数据转移指令
    2. push:数据入栈,同时esp栈顶寄存器也要发生改变
    3. pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
    4. sub:减法命令
    5. add:加法命令
    6. call:函数调用,1. 压入返回地址 2. 转入目标函数
    7. jump:通过修改eip,转入目标函数,进行调用
    8. ret:恢复返回地址,压入eip,类似pop eip命令

3.3函数栈帧的创建和销毁解析过程

  1. 每个编译器下,栈帧的创建是略有差异的
  2. 寄存器:eax、ebx、ecx、edx;重点是ebp和esp
    1. esp:栈顶寄存器
    2. ebp:栈底寄存器
  3. ebp、esp 这2个寄存器中存放的是地址使用这两个地址来维护函数栈帧
    1. 每个函数都有自己ebp和esp来维护函数
  4. 每一个函数调用,都要在栈区上开辟一块空间
  5. 寄存器是用来存储数据的,不管是什么,只用来存储数据

3.4函数的调用

  1. 其实main函数在最开始是被其他函数调用的,有另外一个函数在调用
  2. 图片中调试窗口调用堆栈,是有invoke_main()函数在调用我们的main函数,看第二张图
  3. 如果想打开,调用堆栈这个调试窗口可以这么做:调试 -->窗口 --> 调用堆栈

图二:从调试图中确实如此,的确是invoke_main函数在调用main函数;

图三:调用的invoke_main函数

3.5汇编代码   

  1. 汇编调试所用到的代码
  2. 我是用的环境是vs2022,x86

int main()
{
009C25B0  push        ebp  
009C25B1  mov         ebp,esp  
009C25B3  sub         esp,0E4h  
009C25B9  push        ebx  
009C25BA  push        esi  
009C25BB  push        edi  
009C25BC  lea         edi,[ebp-24h]  
009C25BF  mov         ecx,9  
009C25C4  mov         eax,0CCCCCCCCh  
009C25C9  rep stos    dword ptr es:[edi]  
009C25CB  mov         ecx,9CC008h  
009C25D0  call        009C1320  
int a = 10;
009C25D5  mov         dword ptr [ebp-8],0Ah  
int b = 5;
009C25DC  mov         dword ptr [ebp-14h],5  
int ret = 0;
009C25E3  mov         dword ptr [ebp-20h],0  
ret = Add(a, b);
009C25EA  mov         eax,dword ptr [ebp-14h]  
009C25ED  push        eax  
009C25EE  mov         ecx,dword ptr [ebp-8]  
009C25F1  push        ecx  
009C25F2  call        009C13CA  
009C25F7  add         esp,8  
009C25FA  mov         dword ptr [ebp-20h],eax  
printf("%d\n", ret);
009C25FD  mov         eax,dword ptr [ebp-20h]  
009C2600  push        eax  
009C2601  push        9C7BCCh  
009C2606  call        009C13CF  
009C260B  add         esp,8  
return 0;
009C260E  xor         eax,eax  
}

所用到的代码

#include <stdio.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
 {
	int a = 10;
	int b = 5;
	int ret = 0;
	ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}
  1. invoke_main以上的函数就不考虑了,接下来转到反汇编,从main函数的第一行开始

3.5.1函数栈帧的创建

注意:每次调试的地址名字不一样,但是逻辑是一样的

int main()
{
002F17E0  push        ebp//把ebp寄存器中的值进行压栈,此时的ebp中存放的是
    //invoke_main函数栈帧的ebp,esp-4  
002F17E1  mov         ebp,esp //将esp的地址给ebp,ebp走到esp位置 
002F17E3  sub         esp,0E4h//esp - 0E4h esp向低地址走,为main函数预开辟空间 
002F17E9  push        ebx     //push ebx esi edi 三个寄存器到栈顶 这三个值随时有可能被修改
002F17EA  push        esi  
002F17EB  push        edi  
002F17EC  lea         edi,[ebp-24h]  // 刷新edi寄存器的位置,就是为main开辟的那块空间进行
002F17EF  mov         ecx,9          // 初始化操作
002F17F4  mov         eax,0CCCCCCCCh // 三行代码结合理解,将这块空间edi 到 ebp之前的9个值全部初始化成0CCCCCCCCh
002F17F9  rep stos    dword ptr es:[edi]  
002F17FB  mov         ecx,2FC008h    // 把对应地址的内容放到寄存器中,寄存器只用来存储数据
002F1800  call        002F1320       // 在执行对应地址指向的函数之前,会先存储下一个指令的地址
    //下一个指令的地址就是 002F1805
	int a = 10;
002F1805  mov         dword ptr [ebp-8],0Ah  
}
  1. 画图理解图,当然后面也有动图理解;编译器部分的调试,大家就多试试,结合着理解

  1. 寄存器是存储数据的,是获取地址中的内容保存到寄存器,或者把寄存器的值保存到内存的对应地址中

3.5.2main函数部分

  1. 经过上面操作,main函数的函数栈帧开辟好了
int main()
{
	int a = 10;
002F1805  mov         dword ptr [ebp-8],0Ah   //把10放到ebp - 8的位置上
	int b = 20;
002F180C  mov         dword ptr [ebp-14h],14h //把20放到ebp - 14h的位置上 

	int ret = add(a, b);                      //实参对形参的拷贝
002F1813  mov         eax,dword ptr [ebp-14h] //拷贝ebp - 14h位置的值,放到eax寄存器
002F1816  push        eax  //然后压栈
002F1817  mov         ecx,dword ptr [ebp-8]   //拷贝ebp - 8位置的值,放到ecx
002F181A  push        ecx  
002F181B  call        002F1023  //调用Add函数前,栈顶保存下一条指令的值,就是002F1820
002F1820  add         esp,8     // 调用函数回来后要销毁 形参拷贝的值
002F1823  mov         dword ptr [ebp-20h],eax   // 放到 eax寄存器里的值,给ebp - 20h,ret
	return 0;
002F1826  xor         eax,eax  
}
  1. 这里有个知识的分享,是不是有时候会打印出或报错出现中文 “烫烫烫烫----”这些字,为什么会这样?其实都是有原因的,
  2. 在内存中就是CCCCCC的初始化,翻译成中文就是“烫烫烫烫----”了,如果出现这样的报错,多半是使用了未初始化的空间
  3. 在执行call指令是要按F11逐行调试,最后会到Add函数中,下面也会讲到

这里可以看看调试图确实是和说的一样,注意:ebp-14h,-减的16进制的,14h == 20

在执行Add函数前到底会不会存储下一条指令的地址,看下面图片解析

这里为什么是倒着存储的,可以去看看这篇博客  整形数据与浮点型的数据在内存中存储的形式,以及大小端字节序(笔记版)

3.5.3Add函数部分

  1. 最开始就可以观察到,Add函数前面部分和main函数逻辑一样的函数栈帧的创建
  2. 最重要的是自己去调试!!!
int Add(int x, int y)
{
009C1790  push        ebp  
009C1791  mov         ebp,esp  
009C1793  sub         esp,0CCh  
009C1799  push        ebx  
009C179A  push        esi  
009C179B  push        edi  
009C179C  lea         edi,[ebp-0Ch]  
009C179F  mov         ecx,3  
009C17A4  mov         eax,0CCCCCCCCh  
009C17A9  rep stos    dword ptr es:[edi]  
009C17AB  mov         ecx,9CC008h  
009C17B0  call        009C1320       //前面函数栈帧的创建就略过了
	int z = 0;
009C17B5  mov         dword ptr [ebp-8],0   //创建变量Z,把值放到ebp - 8的位置上
	z = x + y;
009C17BC  mov         eax,dword ptr [ebp+8] //先取到ebp+8,也就是变量a的拷贝,也就是现在的x,值放到eax寄存器
009C17BF  add         eax,dword ptr [ebp+0Ch] //再取到变量b的拷贝,然后相加,结果放到eax
009C17C2  mov         dword ptr [ebp-8],eax   // 把最终的结果放到ebp - 8,创建的Z变量的位置
	return z;
009C17C5  mov         eax,dword ptr [ebp-8]  //出函数就会销毁变量,所以暂时放到eax寄存器中
}
009C17C8  pop         edi  //Pop,三次,因为变量销毁,空间回收
009C17C9  pop         esi  
009C17CA  pop         ebx  
009C17CB  add         esp,0CCh  //回收之前预开辟的空间
009C17D1  cmp         ebp,esp   //比较ebp和esp位置的值
009C17D3  call        009C1244  //这里调用了其他函数,我也不太清楚
009C17D8  mov         esp,ebp   //把ebp的地址给esp,esp走到ebp的位置
009C17DA  pop         ebp       //Pop ebp,也就是压栈的main函数的ebp
009C17DB  ret                   //ret,回到call下一条指令的位置,可以发现这个程序很严谨
  1. 板书解释
  2. 到了这个,再回看到之前提出的问题,现在应该都可以得到解释了
  3. 前面的Add函数栈帧部分大概的说一下

3.5.4main函数剩下部分

  1. 剩下的部分就粗略说明一下了,函数栈帧的销毁上面的很详细
	ret = Add(a, b);
009C25EA  mov         eax,dword ptr [ebp-14h]  
009C25ED  push        eax  
009C25EE  mov         ecx,dword ptr [ebp-8]  
009C25F1  push        ecx  
009C25F2  call        009C13CA  
009C25F7  add         esp,8  
009C25FA  mov         dword ptr [ebp-20h],eax  
	printf("%d\n", ret);
009C25FD  mov         eax,dword ptr [ebp-20h]  
009C2600  push        eax  
009C2601  push        9C7BCCh  
009C2606  call        009C13CF  
009C260B  add         esp,8  
	return 0;
009C260E  xor         eax,eax  
}
009C2610  pop         edi  
009C2611  pop         esi  
009C2612  pop         ebx  
009C2613  add         esp,0E4h  
009C2619  cmp         ebp,esp  
009C261B  call        009C1244  
009C2620  mov         esp,ebp  
009C2622  pop         ebp  
009C2623  ret

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

策略模式编程

接口定义&#xff1a; public interface ProcessParserStrategy { List<ProcessInfo> parser(String osType, String processInfo); String getApp(); } public interface ConfigParserStrategy { List<ConfigInfo> parser(String configInfo); String getConfigT…

谷歌Chrome浏览器排查js内存溢出

1. 打开谷歌浏览器检查台 2. 点击memory 3. 点击开始快照录制&#xff0c;时隔一会儿录一次&#xff0c;多录几次 4. 进行快照对比

vue+element-plus完美实现跨境电商商城网站

目录 一、项目介绍 二、项目截图 1.项目结构图 2.首页 3.中英文样式切换 4.金钱类型切换 5.商品详情 6.购物车 7.登录 ​编辑 8.注册 9.个人中心 三、源码实现 1.项目依赖package.json 2.项目启动 3.购物车页面 四、总结 一、项目介绍 本项目在线预览&am…

[网络安全产品]---EDR

写在前面 前端时间看抖音&#xff0c;刷到周鸿祎介绍360为什么这么厉害&#xff0c;他提到一点就是360是全球第一个提出云查杀概念的公司&#xff0c;相比较传统的基于病毒特征库终端杀毒&#xff0c;360依托积累的庞大的信息数据能有效应对APT攻击。 然后又特意找了一下云查…

世界是软件定义的 - 正如硬件公司所证明的那样

很难相信&#xff0c;马克安德森&#xff08;Marc Andressen&#xff09;在13年前写下了他著名的博客&#xff0c;题为“软件正在吞噬世界”。在这篇文章中&#xff0c;他谈到了现代软件组织对传统企业造成的破坏。 十三年后&#xff0c;即使面对英伟达的平流层估值&#xff0…

openGauss开发者大会、华为云HDC大会举行; PostgreSQL中国技术大会7月杭州开启

重要更新 1. openGauss Developer Day本周五于北京举行&#xff0c;大会聚集了相关行业专家、用户、伙伴和开发者&#xff0c;分享给予openGauss的联合创新成果和实践案例。([2] ) &#xff1b;华为云 HDC 2024本周五于东莞松山湖举行&#xff0c;主题演讲主要覆盖鸿蒙、AI ([3…

IntelliJ IDEA 2024 mac/win版:编程利器,智慧之选

IntelliJ IDEA 2024是一款由JetBrains精心打造的集成开发环境(IDE)&#xff0c;专为Java等编程语言量身打造&#xff0c;同时支持多种其他语言&#xff0c;为开发者提供了卓越的开发体验。 IntelliJ IDEA 2024 mac/win版获取 这款IDE凭借其出色的智能化和高效性&#xff0c;赢…

【Python高级编程】新手小白必须得学会的文本文件操作,资料资源均可分享!

文件读取处理 使用 read()&#xff1a; # 使用 read 方法读取文件的所有内容 with open(resources/training_log.txt, r) as file:content file.read()print(content)# 报错处理版本 # 使用 read 方法读取文件的所有内容 # 使用 utf-8 编码方式打开文件 with open(resources…

车载模块负载基础认识

车载模块负载是指车辆上的各种电子设备和系统&#xff0c;如导航系统、音响系统、空调系统、安全气囊等。这些设备和系统在车辆运行过程中需要消耗一定的电能&#xff0c;以保证其正常工作。车载模块负载的基础认识主要包括以下几个方面&#xff1a; 1. 负载类型&#xff1a;车…

GaussDB关键技术原理:高性能(一)

引言 对数据库性能进行优化是令人激动的&#xff0c;无论是对其进行性能需求分析、性能需求设计、性能问题定个位都是富于变化又充满挑战的工作&#xff0c;本章围绕“数据库性能”进行全面系统化的介绍&#xff0c;首先从数据库在现代软件栈中所处的位置出发&#xff0c;介绍…

vue+echarts ----中国地图 下拉选择省份地图中的省份区域高亮显示以及飞线图的效果

vueecharts ----中国地图 下拉选择省份地图中的省份区域高亮显示以及飞线图的效果 1、父组件核心代码&#xff1a;【/utils/area的详细数据】、【/utils/china详细数据】 <template><div class"center"><div class"digital"><el-se…

canvas实现画布拖拽效果 适配Uniapp和Vue (开箱即用)

需求:我司是做AIGC项目最近和地铁项目有关需要实现海报效果图&#xff0c;并且需要使用画布拖拽和修改上传删除等等功能 当时连续加班花了10个工作日搓出来 实现挺简单的但是Canvas数据处理还是挺麻烦的 大概功能如图下 首先我们需要引入Fabric.js 这个库封装好了原生的Canva…

Apifox 快速入门教程

访问示例项目​ 可访问Apifox官网&#xff0c;下载并打开 Apifox 后&#xff0c;你将会看到由系统自动创建的“示例团队”&#xff0c;其中内含一个“示例项目”。 项目中自动生成了与宠物商店有关的数条接口。 手动新建接口​ 新建接口是开发者们最常用的功能之一。Apifox 能…

硫化物固态电解质在全固态锂电池制造领域发展潜力大

硫化物固态电解质在全固态锂电池制造领域发展潜力大 固态电解质主要包括氧化物、硫化物、聚合物等类型。氧化物固态电解质由于研发难度相对较低&#xff0c;是目前主流技术路线。硫化物固态电解质研发难度较高&#xff0c;但性能优异&#xff0c;特别适合制造全固态锂电池&…

小程序wx.uploadFile异步问题

问题&#xff1a;小程序上传文件后我需要后端返回的一个值&#xff0c;但这个值总是在最后面导致需要这个值的方法总是报错&#xff0c;打印测试后发现这它是异步的。但直接使用 await来等待也不行。 uploadImg.wxml <view class"upload-wrap"><view clas…

黑神话悟空-吉吉国王版本【抢先版】

在中国的游戏市场中&#xff0c;一款名为“黑神话悟空”的游戏引起了广泛的关注。这款游戏以中国传统的神话故事“西游记”为背景&#xff0c;创造了一个令人震撼的虚拟世界。今天&#xff0c;我们要来介绍的是这款游戏的一种特殊版本&#xff0c;那就是吉吉国王版本。 在吉吉国…

邮件推送服务商有哪些核心功能?怎么选择?

邮件推送服务商支持哪些营销工具&#xff1f;推送性能如何评估&#xff1f; 邮件推送服务商的核心功能可以帮助企业更高效地管理和优化其电子邮件营销活动&#xff0c;从而提升客户参与度和转化率。AokSend将详细介绍邮件推送服务商的一些核心功能。 邮件推送服务商&#xff…

鸿蒙仓颉编程语音来了,ArkTs语言危矣?

鸿蒙仓颉编程语言来了&#xff0c;请允许我哭会~~呜呜呜~~我的arkts啊 仓颉编程语言文档地址文档中心 鸿蒙直播大会开始一个小时了&#xff0c;地址华为开发者大会&#xff08;HDC 2024&#xff09;主题演讲 同时api12 不再是秘密了 各位&#xff01;公开啦 api12 公开地址…

如何开启Claude 3的Artifacts功能以及如何注册Claude3

就很突然&#xff0c;Claude 3.5&#xff0c;它来了&#xff01; Anthropic发布3.5系列第一个版本Claude 3.5 Sonnet。在多个关键指标中&#xff0c;GPT-4o几乎被吊打&#xff01; 另外Claude 3.5 Sonnet是免费的&#xff0c;提供了跟gpt-4o一样的次数。更高的速度和次数&…

maxwell源码编译安装部署

目录 1、组件环境 2、maxwell安装前提 3、maxwell安装 3.1、maxwell下载 3.1.1、最新版本下载 ​编辑 3.1.2、历史版本下载 3.2、maxwell安装 3.3、maxwell配置 3.2.1、mysql开启binlog 3.3.2、maxwell元数据配置 3.3.3、maxwell配置任务 4、maxwell部署问题 4.1、utf…