超详细函数栈帧(利用反汇编窥探底层原理)+建议收藏(代码片段)

IT莫扎特 IT莫扎特     2022-12-18     784

关键词:

前言

学习函数栈帧之前我们得了解一下什么是寄存器,因为关于函数栈帧的知识是需要了解寄存器的知识做一个内容铺垫的,每次测试采用的环境是在VS2017下

寄存器

寄存器与函数栈帧的关系
ebp,esp这两个寄存器存放的是地址,这两个地址是用来维护函数栈帧的,简单了解一下寄存器呢有六个

本次测试代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Add(int x,int y) 

	int z = x + y;
	return z;


int main()

	int a = 10;
	int b = 20;
	int ret = 0;

	ret = Add(a,b);
	printf("%d", ret);
	return 0;

首先我们得明白每一次调用函数都会为该函数创建一个函数栈帧的,那么这块空间由谁来维护呢,前面我们讲到函数栈帧是两个寄存器esp 和 ebp
来维护的,下面看内存图

通常我们把ebp 呢我们称之为栈底指针,esp
呢我们又称之为栈顶指针,为什么有栈顶指针和栈底指针的说法呢?我们都知道栈的使用习惯是先使用高地址空间,再使用低地址空间,每次函数调用压栈的时候,我的两个指针都会指向这块空间,
每次开辟空间的时候,在栈中是不是总是向上使用的,那么栈顶指针是不是就总指向这块空间的头,而栈底指针就是指向这块空间的底,

主函数是被谁调用的?调用逻辑是什么

接下来先从main函数开始开刀,我们都知道写好的代码都要放到主函数中去运行,然道主函数就这么牛?总是得经过他得同意?答案并不是,其实main函数的上头也有老大调用main,接下来我们通过调试观察他的调用逻辑,有猎奇心理的小伙伴请跟着来

这里我们等待主函数返回到被调用处,去寻找他的源头

在这里我们把调用堆栈打开就行

在这个地方我们和直观的就看到了主函数被调用处,另外要说明的是在VS2017上好像不能观察到主函数的调用逻辑,博主目前采用的是VC2010学习版,回到主题

不知道大家观察到没有,这里的__tmainCRTStartup()其实就是我的mainret函数,其实很明显在我们的调用堆栈中就可以看出

在这里我们选中这一行双击跳转到mainret函数的被调用处

我们再往上翻

原来是这个函数调用了我们的__tmainCRTStartup(),从而函数的调用逻辑我们就搞明白了,

我们再梳理一遍调用逻辑

主函数栈帧的创建

既然我们知道了这一点,来么就可以再完善一下刚刚的内存图

直到这里大家把对函数栈帧的理解开始建立一个大致的轮廓就行,接下来我们通过反汇编观察里面的秘密

下面就是整个程序的部分汇编代码

之前说过每调用一次函数都会为这个函数分配一个栈帧,main函数是由__tmainCRTStartup()函数调用的,那么__tmainCRTStartup这个函数的栈帧就已经创建好了,

回到反汇编,看这一行的 push 一个叫 ebp 的寄存器进来

栈顶指针,栈底指针

push进来一个元素那么我的esp是不是就得指向这个元素,因为esp是我的栈顶指针嘛

接下来,来到调试窗口,这行指令还没执行前esp的地址是0x00b5f964

这一行一执行来到了下一行会发现此时的esp的地址已经发生了变化由原来的地址(0x00b5f964)变化到了(0x00b5f960)差了4字节,这说明了其实就是压入了一个元素进去,因为此时的esp指向的是压入栈中的那一块空间,因为栈的使用习惯是先使用高地址再使用低地址,由原来的地址(0x00b5f964)减去4字节得到的就是(0x00b5f960),此时的esp指向的就是(0x00b5f960)这还不足以证明吗?是不是就对应了上面的这张内存图

函数栈帧创建的预备工作

接下来再看mov这一条指令,这条指令的作用是将esp赋值给我的ebp

很明显我的esp和ebp此时指向的是同一块空间,因为是同一个地址嘛

如果要用内存图来描述的话,这张就最通俗了

回到汇编代码,接下来程序该执行sub这条指令了,这条指令的作用就是让esp 减去 0E4h
这样子esp就会指向新的空间了,再啰嗦一句就是栈的使用习惯是先使用高地址再使用低地址,很明显esp
指向一个比他还小的地址,那么肯定是又开辟了一块空间

很明显此时的esp 又指向了一块新的空间,因为他的地址发生了变化

此时的内存布局是这样子的

所以主函数空间的大小就是ebp指针减去esp指针的大小,因为在一块连续的空间里地址 - 地址得到的结果就是这块空间的大小

接着回到汇编代码,可以看到程序又压进去3个值,这个我们先不用关心,知道内存图就可以

此时的栈顶指针发生变化,直到这里大家对栈顶指针对应每一次压栈都会指向这一块新空间这里已经很熟悉了吧

当我们程序来到这一行看到了一个 lea,那么这个lea是个什么意思呢?lea的全称是(load effective
address)意思就是加载有效地址,相当于给edi这个寄存器里面放入一个地址

为了便于大家观察这博主调整一下编译器,显示符号名一勾上便于后我们观察

这行汇编代码 edi,[ebp-0E4h] 意思是什么呢?就是用ebp寄存器里此时保存的地址减去0E4h得到的新的地址赋值给我的edi,

这三行指令一执行完,到底干了什么事,真正能够产生效果的是这句指令 rep stos dword ptr
es:[edi],这句话的意思就是要把从edi这个位置开始向下走的39h次(ecx中的内容),每次将dword的空间初始化为0CCCCCCCCh(eax中的内容)
注意:(一个word占2字节 d就是双倍的意思,也就是4个字节)

这三行代码将主函数的空间全都初始化为0CCCCCCCCh

Add函数是怎么被调用的

以上汇编指令执行完,初步任务就已经完成了,main函数的栈帧的开辟就已经准备完了,接下来就可以执行有效代码了

程序走到这一步,开始执行我们的C语言代码 int a = 10;

这一行汇编代码的意思是将0Ah(十进制的10) 这个值,放到 [ebp-8]这个地址指向的这块空间处初始化这 dword(四字节空间)
黄色小块ebp-8这个地址指向的这块空间就是变量a所在的内存单元,在这个时候已经被初始化为10,额外插一句如果没有将10赋值给a
这块空间,那么这块空间不就还是(ccccc)了?答案:是的,每次开辟栈帧的时候系统都会给这块内存空间给随机值,随机值是什么值?就是这个(ccccc),每次如果程序员只定义局部变量但是没有给局部变量赋值,那么局部变量的随机值不是(烫烫烫烫)吗?只是针对局部变量,因为只有局部变量才会使用栈空间,那如果是全局变量呢?是不是就不会啊?因为全局变量没有用到栈的内存空间,

断点停在这一行表示还没执行这条语句,此时a的内容就是0xcccccc

c,那么执行这条语句出现的结果又是什么呢呢,让我们接着观察

变红的部分表示变量a已经初始化了,不再是随机值了,那么这条汇编代码是不是就验证了上面的解释,由于编译器采用的是小端存储(小端在左大端在右),所以在内存中是倒着存放的实际上应该是
0x 00 00 00 0a,对应的就是10

那当我们有了对上面这个代码的理解,在理解这条汇编代码是不是就简单多了啊,这里就不再啰嗦,下面我们直接看内存图

当我的 [ebp-14h] 一执行,其实就已经跳到
0x00B5F94C这个地址处了,也就是对应的变量b的地址处,在这个时候可以看到变量b也已经有初始值了,不再是随机值了


绿色箭头标识的是变量b,这种做法希望能便于大家理解,在这个地方呢我们可以观察到两个局部变量是差了2个整形的,下面是他们对应的地址,当然这个跟编译器有关,有的编译器是差1个整形,有的编译器是差2个整形,在liunx中差的是1个整形,而在VS2017中差两个整形,关于这个内容在我的这篇博客中有过解释,https://blog.csdn.net/m0_53421868/article/details/118726690?spm=1001.2014.3001.5501

接着往下看,这就不用多说了

这行语句一执行完ret在内存中也被初始化好了

并且我们也能观察到这3个局部变量都是差了2个整形

此时的内存布局,橙色箭头所指向的就是变量c所在的内存块

接下来就轮到函数调用了 mov一执行 eax的存放的就是变量b的地址,接着执行完push再压栈eax

此时此刻esp就指向了我的eax,明显esp发生了变化

将变量a的值存放到ecx寄存器中

ecx此时此刻存放的是10

push指令一执行

esp指向了新的地址

内存分布再发生变化

F11一按进入到了我的Add函数中,这是Add函数对应的汇编指令

push一执行完esp又指向了新压栈的一块空间

对应的内存图

mov一执行完,esp指针和ebp指针又指向同一个地方去了


当sub一执行完esp又指向另外一个地址处去了,那么这样子以来的目的是不是就又为了Add函数分配内存呢


再接着又是3次push压入3个寄存器


而以上这一块代码的执行又是为了Add函数栈帧的创建做准备

紧接着再把x 和 y变量存放到寄存器eax中,执行完add指令后,再将x + y的结果存放到eax中,最后将eax去初始化变量z

其实x变量和y变量并不是在函数栈帧中创建的而是在传参的时候就已经记录了他们的值

在函数调用完了之后,return z 会将变量z的值放入到寄存器中,返回的是寄存器的值,所以在变量z会销毁了之后并不影响函数的返回值

这3行代码会将edi、esi、ebx给弹栈并将esp逐渐往高地址去指向,

当再执行mov指令的时候,两个指针又重叠了

回看内存图,此时此刻这个Add函数栈帧就被销毁了

而当pop esp pop
ebp这两条指令一执行后,我的esp又回到最开始指向主函数的edi处去了,而我的ebp又指向到最开始的ebp处去了,请看下面的内存图

而函数一调用完就会回到call指令的下一条指令,回到主函数程序继续执行

当执行当add指令后esp + 8,这个过程会将原来的两个临时变量x和y给释放掉

最后将原本存放在eax寄存器中的值带回给我的ret

计算的结果就是30

以上的所有流程博主就交代完了,如果有什么不足的请指教,感谢

c语言☀️函数超详讲解☀️(详细讲解+代码演示+图解)建议收藏(代码片段)

  自己整理的长篇详细学习笔记分享给大家,如有错误,欢迎评论区指正目录一、函数是什么?C语言中函数的分类二、库函数为什么会有库函数库函数的优点如何学习库函数 C语言常用的库函数三、自定义函数为什... 查看详情

java虚拟机原理线程栈|栈帧|局部变量表|反汇编字节码文件|java虚拟机指令手册|程序计数器(代码片段)

文章目录一、线程栈二、栈帧三、栈帧-局部变量表四、反汇编字节码文件五、Java虚拟机指令手册六、程序计数器一、线程栈装载HelloWorld.class字节码文件到Java虚拟机内存中,会将该字节码文件中的数据进行分解,放到不同的内存区... 查看详情

内功修炼《函数栈帧的创建和销毁》建议收藏(代码片段)

...️⃣为什么未初始化的局部变量的值是随机值?3️⃣函数是如何传参的?以及传参的顺序是怎样的?4️⃣形参和实参是什么关系?5️⃣ 查看详情

linux——超超讲解ssh的原理与ssh的实现!建议收藏❤(代码片段)

...结用户与组的权限那些事儿!建议收藏!目录SSH原理SSH基本概念SSH算法ssh配置文件(传递参数)ssh_config与sshd_configSSH的使用查看SSH是否运行查看哪些用户远程登录上本机ub 查看详情

pat乙级全套超详细题解建议收藏

PATOJ地址代码上面专栏就是PAT乙级共95道题的所有代码,在专栏里已经按照顺序排好序了。专栏地址讲解PAT代码那里有的个别有题解,但是文字很难把一道题讲懂。于是,为了方便更好的理解。在家闲着没事录制了PAT... 查看详情

函数栈帧的创建与销毁,带你了解代码底层原理(代码片段)

...么创建的?(2)为什么局部变量的值是随机的?(3)函数是怎么传参的?传参的顺序如何?(4)形参和实参是什么关系?(5)函数调用是怎么做的?(6)函数调用结束后是怎么返回的?这些疑问其实都和函数栈帧... 查看详情

史上最全的ideadebug调试技巧(超详细!建议收藏!)(代码片段)

来源:https://www.cnblogs.com/chiangchouDebug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。通常我们也可以启用Debug模式来跟踪代... 查看详情

史上最全的ideadebug调试技巧(超详细!建议收藏!)(代码片段)

来源:https://www.cnblogs.com/chiangchouDebug用来追踪代码的运行流程,通常在程序运行过程中出现异常,启用Debug模式可以分析定位异常发生的位置,以及在运行过程中参数的变化。通常我们也可以启用Debug模式来跟踪代... 查看详情

图解c/c++语言底层:函数调用过程之函数栈帧的创建和销毁(上)(代码片段)

**文章目录函数栈帧的创建和销毁什么是寄存器?寄存器分类寄存器用途什么是"栈"?函数栈帧的概念函数压栈的过程示例代码和主函数汇编指令(部分)汇编指令:构建函数栈帧准备(一)汇编指令:构建函数栈... 查看详情

实用工具tcpingping&tcping的区别,使用命令,超全超详细使用手册(建议收藏)

目录​​ping​​​​简介​​​​使用​​​​tcping​​​​简介​​​​下载​​​​使用​​​​总结​​ping简介ping(PacketInternetGroper)是一种英特网包探索器,用于测试网络连接量的程序。Ping是工作在TCP/IP网络体系结构... 查看详情

超详细的前端自动化测试教学!建议收藏(代码片段)

为什么需要写前端自动化大部分企业为了追求开发效率,所以并没有去强制要求员工写前端自动化测试的代码。另一部分企业则会要求前端开发额外写前端自动化测试。那么写和不写到底有哪些区别呢?不写前端自动化... 查看详情

超详细的前端自动化测试教学!建议收藏(代码片段)

为什么需要写前端自动化大部分企业为了追求开发效率,所以并没有去强制要求员工写前端自动化测试的代码。另一部分企业则会要求前端开发额外写前端自动化测试。那么写和不写到底有哪些区别呢?不写前端自动化... 查看详情

门禁卡怎么弄到手机上,手机变成门禁卡,手把手超详细(建议收藏)

言简意赅,图文并茂,直截了当,只聊干货。加密卡的复制请看这篇文章:【一学就会,加密门禁卡怎么弄到手机上,手机变门禁卡(建议分享)-今日头条】https://m.toutiao.com/is/dJxatpM/1、基本条件手... 查看详情

超详细梳理hbase核心知识点(上)建议收藏(代码片段)

之前我的公众号名字叫做:’‘Java不睡觉’’,原因就是当时看了一本书,名字是《HBase不睡觉书》。这本书正如其名字一样,是一本让人读起来根本不会发困的书,very奈斯。本文就是整理了这本书上的知识... 查看详情

clickhouse超底层原理+高可用实操(史上最全)(代码片段)

文章很长,建议收藏起来慢慢读!总目录博客园版为大家准备了更多的好文章!!!!推荐:尼恩Java面试宝典(持续更新+史上最全+面试必备)具体详情,请点击此链接尼恩Java面试宝... 查看详情

clickhouse超底层原理+高可用实操(史上最全)(代码片段)

文章很长,建议收藏起来慢慢读!总目录博客园版为大家准备了更多的好文章!!!!推荐:尼恩Java面试宝典(持续更新+史上最全+面试必备)具体详情,请点击此链接尼恩Java面试宝... 查看详情

建议收藏超详细的canal入门,看这篇就够了!!!(代码片段)

概述canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了MySQL(也支持mariaDB)。背景早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步... 查看详情

(超多图)基于androidstudio开发的一个简单入门小应用(超级详细!!)(建议收藏)(代码片段)

基于Androidstudio开发的一个简单入门小应用一、前言二、前期准备三、开发一个小应用五、运行应用一、前言在暑假期间,我学习JAVA基础,为了能早日实现自己用代码写出一个app的“梦想”,因此,现在开始对Andro... 查看详情