| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C语言の底层神功——图解函数栈帧的创建和销毁 -> 正文阅读 |
|
[C++知识库]C语言の底层神功——图解函数栈帧的创建和销毁 |
目录 一、寄存器介绍寄存器是是集成到CPU上的,在电脑中的存储有:硬盘、内存和寄存器,它们是各自独立的。要理解函数栈帧,必须知道ebp和esp这两个寄存器是用来存放地址的,通过ebp和esp中存放的地址维护函数栈帧。 常见的寄存器及其用途如下:
二、栈帧我们来看一下百度给出的解释:
三、汇编指令为了能在编译器中清楚观察到函数栈帧创建和销毁的全过程,需要通过反汇编操作转到汇编语言。为了能看懂汇编语言,我们需要了解一些常见汇编指令及其含义。 常见的汇编指令及其对应的含义如下:
四、准备工作要先了解一点,在不同的编译器里函数栈帧的创建和销毁存在一定的差异,但总体逻辑是一致的。由于越高级的编译器越难以观察函数栈帧创建和销毁的过程,这里我们演示用的编译器vs2013。 首先我们先写一段代码,其中通过函数调用进行加法运算。代码如下:
五、观察函数栈帧的创建和销毁1.转到汇编语言首先我们开始调试,之后进行的操作如下: 这样我们就可以清楚地观察函数栈帧创建和销毁的全过程啦~ 2.main函数栈帧的创建?进行上述操作后我们可以看到main函数的反汇编代码如下:
我们在main函数里一直往下调试直到main函数结束后发现堆栈里发生了变化,如图所示 于是我们知道,在vs2013中main函数是被其它函数调用的。 ?在结束之后的页面我们可以观察到函数的调用关系如下: 在栈区分配的空间大致如下 : 因此我们可以知道,在mian函数被调用之前,已经为__tmainCRTStartup开辟好了栈帧 ?我们回到最初main函数开始被调用的断点,逐条分析反汇编指令 ??a.首先 进行push操作,即把ebp压栈 ? 压栈放的元素是ebp的地址,进行压栈后,esp指向的位置也会上移,如图所示(部分不必要的细节图中有所省略) b.接着进行mov指令: ? ?即把esp的值赋给ebp,此时esp和ebp指向的是同一个地址 ? 未mov时我们可以看到esp和ebp指向的地址如下: ? 执行mov执行后: ?? 可以看出esp和ebp指向的地址完全一致 ?c.下一条指令是sub,即把esp指向的地址减去0E4h(0E4h是八进制数字) ? ? ?减去之后esp就指向了大概这个位置 ,图中紫色部分就是为main函数申请的栈帧 可以看到esp指向的地址确实减小? d.下一步执行的是压栈操作,esp也会随之上移 ? e.再下一步仍是压栈操作,即把esi压入栈里 ? ? f.再接下来还是压栈,压进edi ? ? g.下面执行的操作如下图,即把后边有效的地址放入前面 ? ?h.再接下来,把39h放入ecx中 ?i.然后执行的指令是把0CCCCCCCCh放入eax里 ?j.接下来的指令就很有趣了 ?rep stops意思是把从edi下边的39h次(ecx中的值)dword(double word)(一个word两个字节,double word即为双字,四个字节)全部改成0CCCCCCCCh ?执行完这步指令我们去看看内存的情况 此时esp的地址为0x0115FC84,从0x0115FC90往下直到ebp全部初始化为CCCCCCCC ?此时为main函数开辟的栈帧已经OK k.接下来的指令是把0Ah(0Ah为十六进制数字,换算为十进制是10)放入ebp-8里(为了观察得更清楚,我们显示符号名) ? ? ?所以创建的随机变量如果没有初始化,打印出来的结果是烫烫烫…就是系统初始化结果CCCCCCCC的缘故 ?l.再接下来为局部变量b指定房间 即把14h(十进制对应数字为10)赋给ebp-14h的空间? ? ?m.再接下来,把0赋给ebp-20h的空间 ? 所以我们看出,局部变量是在函数栈帧里找一些空间放进去 我们可以看到内存情况如下 ? 局部变量之间的距离大小取决于编译器 ?n.接下来的操作是把ebp-14h的值氟给传给eax o.再接下来的操作是压栈,把eax压入栈里,其实里边放的是b的值,这就是形参y ? ? p.再接下来,把ebp-8h的值赋给ecx q.然后把ecx也压入栈里,里边放的是a的值,这就是形参x ? ? 上边的动作就是在执行传参 ?从上边我们可以看出,传参是先行的,并且传参顺序是从右向左。形参是实参的一份临时拷贝。 r.到了下边,就要调用函数Add了(call指令是调用函数) 但此时我们发现call指令下一条指令的地址也被压入栈里 ?接下来我们按F11进入 Add函数内部 2.Add函数栈帧的创建和销毁进入Add函数后我们可以看到Add函数的汇编代码如下:
a.首先,仍是执行压栈指令 ? ?b.接下来的指令和之前遇到的情况一致,把esp的值赋给ebp ? ?这样ebp和esp指向的又是同一块空间 c.接下来的指令是为Add函数的栈帧申请一块空间 ? 黄色空间就是给Add函数开辟的栈帧 d.接下来的指令仍是压栈 ? e.再接下来的指令仍是压栈 ? f.还是压栈,这三次压栈和之前调用main函数的情况完全一致 ? g.然后把ebp+FFFFFF34h的地址给ebi ?h.下一步指令是把33h赋给ecx i.把0CCCCCCCCh放入eax中 ?j.再下一步指令和之前遇到的情况类似,也是把从edi往下直到ebp的空间初始化? ? k.下面的操作是为局部变量z指定一块空间,即把0赋给ebp-8的空间里 ? l.接下来就要执行计算任务了,首先把ebp+8的值(其实是形参x)赋给eax ?m.然后又把ebp+0Ch的值(其实是形参y)加到eax里 ?未执行该指令是寄存器eax的情况: 执行该指令后: ? eax中的值就变为30 n.下边的指令就是把eax中的值赋给ebp-8(即局部变量z) ? o. 接下来的指令是把z的值放到寄存器eax存起来 ?p.之后的指令就是出栈操作了,首先出栈的是最顶部的edi ? 每次出栈后esp都会下移 q.下边出栈的是esi ? ?r.然后出栈的是ebx ? s.接下来的指令是把ebp的值赋给esp,至此黄色空间就释放了,即还给内存 ? t.接下来出栈的是ebp,由于这里存的是之前压在main函数底部的ebp的地址,所以出栈之后ebp直接指向了main栈帧底部 ? ?u.接下来Add函数就返回了,当ret返回的时候,就是从栈顶,弹出了call指令下一条指令的地址,就来到了call指令下一条指令 不仅要走出去,我还要回得来
? w.然后就把eax中的值(30)赋给ebp-20h(局部变量c)中?x.之后的指令就显而易见了,main函数的出栈逻辑和Add函数一致 ?六、小结?至此,我们就搞懂了函数栈帧创建和销毁的整体逻辑。最后我们通过问答形式来梳理一下。
? ? ? ? ? ? ? ? ? ? ? ? ? |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 17:11:48- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |