函数栈帧的定义
函数栈帧(Function stack frame),也称为活动记录(Activation record)或调用栈帧(Call stack frame),是在程序执行过程中用来管理函数调用的一种数据结构。每当一个函数被调用时,都会创建一个对应的函数栈帧。
函数栈帧主要包含的内容
-
返回地址(Return Address):指示函数执行完毕后程序需要回到的下一条指令的地址。
-
基址指针(Base Pointer):指向当前函数栈帧的起始位置,在函数内部可以通过基址指针来访问局部变量和函数参数。
-
临时数据区域(Temporary Data Area):用于存储函数执行过程中的临时变量和中间结果。
-
参数区域(Argument Area):用于存储函数调用时传递的参数。
了解函数栈帧能解决什么问题
1、局部变量是怎么创建的?·
2、为什么局部变量的值是随机值?
3、函数是怎么传参的?
4、传参的顺序是怎样的?
5、形参和实参是什么关系?
6、函数调用是怎么做的?
7、函数调用是结束后怎么返回的?
知识介绍
介绍寄存器的作用
ebp:在创建栈帧中栈低的寄存器。
esp:在创建栈帧中栈顶的寄存器。
eip:是指令寄存器,起到保存下一个指令的作用。
eax:在函数栈帧中起到保存数据的作用,常常用来保存返回值。
ebx:在函数栈帧中起到保存数据的作用。
有关汇编指令的介绍
mov:指挥寄存器移动的指令
push:入栈,就是把寄存器放到栈的顶端。
pop:出栈,把在栈顶端的寄存器弹出去。
sub:执行减法命令。
add:执行加法命令。
call:执行函数的调用,在调用前存储一个返回值,再进入函数的内部
jump:修改eip,转进函数进行调用等。
ret:返回主函数,压入寄存器等指令。
函数栈帧的观察和学习
1准备好编译环境
要在DebugX86的环境下观察函数的栈帧。
本次的函数栈帧观察是在VS2022上面观察的
2准备函数
为了方便我们观察就写了一个简单的加法函数。
写完加法函数后我们进行调试(按F11进行)再点击右键就能查看
转到了反汇编我们就可以开始观察了
3观察主函数函数调用的堆栈
esp这个寄存器在函数栈帧的顶部,ebp这个寄存器在函数栈帧的底部。
两个寄存器之间的距离是0D8h,然后压栈(在esp这个位置)把这三个ebx,esi,edi压入栈顶,lea的指令就是把【ebp-18h】放入到edi这个空间,接下来的两个mov就是为函数内部的初始化准备,两个mov指令加上rep stos这个指令就是把从edi开始往下6(这里是X86环境下的6)的空间全都用0CCCCCCCCh来填满。(打印出来就是烫烫烫)。
1、把十六进制的0Ah放入ebp并向低地址移动8个字节。
2、把十六进制的14h放入ebp并向低地址移动20个字节。
先移动到【ebp-14h】上压了一个eax,再移动【ebp-8】上压了一个ecx,然后执行call指令转到了我们写的函数部分。
4、观察add函数的调用栈帧
大家看到这里会不会有点熟悉哈哈哈跟我们上面主函数的空间开辟一样。
ebp和esp开辟函数栈帧的空间空间的大小是0CCh,然后压栈压三个最后利用0CCCCCCCCh初始化3这个空间。
1、把【ebp+8】的值传给eax,在这个函数的空间ebp是函数空间的栈低指针,然后ebp+8就是传参时创建ecx的地址,里面存放的就是10这个数字。
2、在上面的基础上加上【ebp+0ch】中的值后,就是存放变量b的存放空间里面存放的值就是20。
3、将eax的存放的两个值加在一起求和结果存放到这个函数栈帧的【ebp-8】中。
4、当然退出了这个函数的栈帧里面的栈帧都会被销毁,但是里面存放的返回值并不会被销毁。当拿到了函数的返回值后,就可以出栈了。
函数栈帧的销毁
最后弹出edi,esi,ebx,然后让esp加0CCh(创建时是减去这个空间)个空间,把ebp赋给esp并弹出ebp,最后执行返回指令,返回到调用函数指令的下一个指令,当在执行ret指令时实际已经弹出After call,以执行add指令(esp,8),此时esp向高地址移动8个字节,esp和ebp将会重新维护main函数,eax中存放的返回值将会传递给地址(ebp-20),即ret的地址。此时add函数的调用彻底结束了。
最后main函数也是一样弹出edi,esi,edx后减少ebp和esp的空间把ebp赋给esp最后弹出ebp,最后返回main函数。此时main函数的调用也就结束了。
结束语
相信大家看完之后对函数栈帧的创建和销毁全部都了解了,提出来的问题答案也是全都知道了。作者的语言和知识水平有限有什么错的地方请给位大佬多多指教。