抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解(下)

简介: 抽丝剥茧C语言(中阶)函数栈帧的创建与销毁——图解

创建局部变量与初始化

现在初始化三个值:

我们看第一条指令,最后面的0Ah是十六进制,代表10的意思,把10放进[ebp-8]这个地址里面:

看,我们里面CCCCCCCC的值被改成, 十六进制a也就是十进制的10。

那么下面的两条汇编指令也就容易易理解了,最后变成这个样子:

这里顺便说一句,因为esp的上移,main函数的栈区已经变成了这样。

这里就是局部变量的创建与初始化。

调用Add函数

现在我们应该调用Add函数了。

首先分析第一条指令,我们要把[ebp-14h]的地址存进eax的寄存器里,然后往下看,再进行压栈。

其实也就是把我们的20存进eax,10存进了ecx而已。

我们又把eax和ecx进行压栈,其实也就是把20和10放再了上面。

这个动作其实就是传参

然后看下一行的指令call,这是准备调用Add函数,这是到了call这一行时,按F11进入这个函数内部,在进入内部之前我们发现这么一个问题:

红色是变化的一行,这里存入的地址是不是很眼熟?没错就是call下面add的地址。

这个位置是在我们传参上面的位置,也就是说再一次进行了压栈。

为什么要把地址放再这个地方呢?因为我们都知道,函数调用之后都是需要返回的,在这里记住地址就好从这里返回,然后继续执行指令。

Add函数的内部

这是进入Add函数里面的汇编指令:

是不是看着似曾相识?没错就是给Add函数分配空间并且维护。

在正式说这段代码我要说一句,现在维护代码的两个寄存器已经移动很多次了,也就是说现在main函数的栈帧已经这么大了:

我们再看现在需要的指令:让我们把ebp进行压栈,这里的ebp其实是main函数的ebp地址。

这些指令就和之前开辟main函数一样的逻辑:

这就是Add函数的栈帧。

下面进行局部变量的创建和计算加法还有返回值:

第一行指令先创建整型变量Z初始化为0:

然后看第二行指令,把[ebp+8]放进eax里面。

第三行指令,把[ebp+8]的值和[ebp+0Ch]加起来放在eax里面。

这时,eax等于30。

然后看第四行指令,意思是把eax的值放在[ebp-8]这个地址里面。

这里我们就明白了,是这样调用参数然后把他们放进了Z里面。

到了这里我们也明白了一件事,之前说的形参和实参问题,其实ecx和eax里面是10和20这个数值,并不像之前的ebp一样存的是地址,也就是说这里的ecx和eax有单独的空间,通过这个空间也只能找到10和20这个数值而已。

并且我们接收的值是int x和int y都没显示怎么运作。

Add的返回:

现在到返回了,我们也有一个疑惑,局部变量Z出了Add函数不就已经销毁了吗?其实是这样的,看最后一行,我们把[ebp-8]这个地址的值暂时存在了eax这个寄存器里,虽然变量Z销毁了,但是寄存器eax是不会销毁的,它是集成在CUP的硬件,所以说寄存器带着Z的值就走了。

返回与销毁

让我们看接下来的指令:

这里的pop是什么意思呢?是弹出的意思,连续三个弹出,把edi,esi,ebx都弹出去了:

变成了这个样子,esp因为这三个元素的弹出从而变化。

然后继续看第四行的指令,把ebp的地址赋给esp,也就是说esp拿到了现在的ebp的地址,和ebp同时指向了一个地方:

第五行的指令是弹出ebp(也就是main函数的ebp原来的地址),把弹出的结果弹到指向这里的ebp里面,就等于把原来再main函数的ebp地址赋给了现在指向这里的ebp,然后esp增加了一个4个字节的地址:

现在我们就发现,已经都回到了main函数的栈帧里面,esp和ebp又开始维护main函数了。

我们还有一个是ret指令,这个指令是返回的意思,弹出当前这个函数从栈顶返回。

因为之前我们存了00C21450这个地址,那么落脚点就是这个地址。

左边黄色箭头的地方就是落脚点。(当时存这个地址就是为了能让我们返回main函数里面的这一行)

这里注意,ret完事之后会pop一下,也就是说把这个main栈帧的最上面的元素给弹出了(也就是00C21450这个地址)。

add这一行的意思是给esp+8,就等于弹出了这两个元素(ecx和eax),形参也就销毁了:

这是图解。

我们继续往下看:

黄色箭头指向的地方指令是什么意思?把eax的值赋给[ebp-20h](这个地址就是局部变量C的地址)eax是个寄存器,之前我们把变量Z的值放了进来,也就是说我们最后把变量Z放进了变量C里面。

这样我们就把返回值给带回来了。

至于printf打印这些指令我们不做讲解了。

剩下的main函数的销毁和之前add的销毁一样。

结束

到这里函数栈帧与销毁就讲完了,我相信大家对于上面的疑惑都有了答案。

请路过的家人们点个赞,大佬们纠正错误和指点不足,谢谢!!!

相关文章
|
2月前
|
存储 安全 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2
|
2月前
|
存储 编译器 C语言
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1
|
2月前
|
存储 编译器 程序员
C语言之反汇编查看函数栈帧的创建与销毁(一)
C语言之反汇编查看函数栈帧的创建与销毁(一)
C语言之反汇编查看函数栈帧的创建与销毁(一)
|
2月前
|
存储 编译器 C语言
C 语言函数栈帧的概念讲解
C 语言函数栈帧的概念讲解
|
2月前
|
存储 编译器 C语言
C语言:底层剖析——函数栈帧的创建和销毁
C语言:底层剖析——函数栈帧的创建和销毁
|
2月前
|
存储 编译器 程序员
C语言之反汇编查看函数栈帧的创建与销毁(二)
C语言之反汇编查看函数栈帧的创建与销毁(二)
|
7月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
7天前
|
Java C语言 C++
定义C语言的int main()函数
定义C语言的int main()函数
|
11天前
|
C语言
C语言prinf函数
C语言prinf函数
11 4
|
9天前
|
存储 移动开发 C语言
技术心得记录:嵌入式开发中常用到的C语言库函数
技术心得记录:嵌入式开发中常用到的C语言库函数