哈喽,大家好,我是情谊,今天我们来探讨一下函数栈帧的创建,下周我们进行函数栈帧的销毁讨论,话不多说,直接开始。
注意哦,我们使用的编译器是VS2013,在这个编译器中更方便我们了解函数栈帧的建立与销毁。而且我们还需要知道,在不同的编译器上函数栈帧的创立与销毁大体方向是一样的,但是还是有区别的。
开始了解函数栈帧之间,我们需要简单了解一下通用寄存器,通用寄存器是电脑CPU内部进行储存数据的小型存储区,用来存储参数,运算数据,运算结果等,通常包括:
- eax: 通常用来执行加法,函数调用的返回值一般也放在这里面
- ebx: 数据存取
- ecx: 通常用来作为计数器,比如for循环
- edx: 读写I/O端口时,edx用来存放端口号
- esp: 栈顶指针,指向栈的顶部
- ebp: 栈底指针,指向栈的底部,通常用 ebp+偏移量 的形式来定位函数存放在栈中的局部变量
- esi: 字符串操作时,用于存放数据源的地址
- edi: 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作
我们还需要了解一下压栈和出栈,压栈:给栈顶放一个元素,出栈:从栈顶删除一个元素
今天我们主要看的是ebp(栈顶指针)和esp(栈底指针),这两个寄存器存放的是地址,这两个地址是用来维护函数栈帧的。我们今天以下面这个简单的代码进行演示:
我们知道,每一个函数的调用都会在栈区上创建一块空间,,所以在上面的代码中,我们会在栈区创建两个空间,用来调用main函数和Add函数,这两块空间我们称为调用函数的函数栈帧,进行维护空间的是我们上面所说的两个寄存器ebp和esp,正在调用哪个函数,这两个寄存器就在维护哪块空间的栈帧也可以理解为,这两个寄存器正在维护哪片空间,那我们就正在调用哪个函数。例子:我们在调用Add函数时,ebp和esp就在维护Add的那片空间。
我们在使用栈区的地址时,一般是先使用高地址,再使用低地址,所以,栈帧大体形状如下:
其次,由上面的程序调试窗口中的调用堆栈中看出,main函数其实也是被调用的,继续调试结束,我们可以看出main函数是被一个函数__tmainCRTStartup()内部调用,而__tmainCRTStartup()这个函数又是被mainCRTStartup函数调用,所以总的关系是mainCRTStartup调用__tmainCRTStartup(),而__tmainCRTStartup()调用main函数,我们在调试状态下点击鼠标右键,选择转为反汇编语言
转化为反汇编语言后,我们就可以看到我们写的代码对应的汇编代码了 ,下图就是main函数的转化汇编语言
在main函数调用之前,已经有一个函数去调用它了,所以,前一个函数的栈帧已经创建好了
所以接下来,我们根据上面的汇编语言创建main栈帧,第一条语句是push ebp,这个语句的意思是将ebp进行压栈,就是将ebp移到函数上面进行新的函数栈帧的建立,接下来的move指令是将esp的值给ebp,那么ebp指向的就是esp。
第三句中的sub esp,0E4h就是将esp这个地址减去0E4h(相当于十进制的228),进而开辟的空间是main函数的栈帧。
接下来的三句push相当于在main函数上面压栈了三个元素,需要注意的是esp会随着压栈的增多而变化,就是压栈在往上面进行,那么esp始终将其包括在内,实现函数栈帧的维护。
第七句lea edi,[ebp+FFFFFF1Ch] (相当于lea edi,[ebp+0E4h])是将[ebp+0E4h]这个地址放入edi内,接下来进行的三句是一起执行的作用是从edi从下开始39h的数据全部改为0CCCCCCCCh(其实就是到ebp截至),至此main函数的栈帧的搭建完成,接下来的就是程序的进行。
mov dword ptr [ebp-8],0Ah 这一行就是给a变量创建空间,位置是ebp-8,接下来的b,c都是给变量创建空间,我们假设一个空格代表四个字节,所以a,b,c的值的位置分别如图
接下来就是调用Add函数的传参,mov eax,dword ptr [ebp-14h],这个表示的是将 ebp-14h这个地址的数据(就是b)放进eax,但是eax没有,所以我们也要进行压栈,创建一个地址来接受b的值,同理,a的传参也是这样,将ebp-8的值传到ecx中
接下来的call指令的执行,首先压栈一个call指令的下一条指令的地址001CA2FA ,这个的作用是让Add函数调用完成后返回这个压栈的位置,然后我们就真正的来到Add函数的调用了
我们看到,Add函数的建立和main函数的建立是一致的,所以我们便不在多说。请看下图。
自此,函数栈帧的建立就完成了,下周,我们讨论函数栈帧的销毁,那么看到这里的小伙伴请点一个赞呗,你们的点赞就是对我的最大的支持!
如果在文中发现些许错误的话,请多多包含哦,下周我会继续努力,带大家了解一下函数栈帧的销毁。谢谢大家!