目录
如果你能熟练的掌握函数的栈帧与销毁在面试中是及其亮眼的加分项,所以我们来以实例来将解函数是如何实现栈帧与销毁的。
第二步:通过将edp压栈,将edp赋值给esp,再将esp的值减减。这样esp和edp之间的差值就是main函数的栈帧空间。
call跳add()函数的地址之后,在添加call指令的下一条指令的地址。
z出了add()栈帧被销毁,但是值被临时放到eax寄存器里。
如果你能熟练的掌握函数的栈帧与销毁在面试中是及其亮眼的加分项,所以我们来以实例来将解函数是如何实现栈帧与销毁的。
一. 函数栈帧
edp,esp这两个寄存器存放的是地址,这两个地址是用来维护函数栈帧的。
每一个函数调用都要在栈区中创建一个空间。
二.寄存器
1.1 edp:栈底指针。
1.2 esp:栈顶指针,每次push,它都会向上(低地址)挪动,push就会--,pop就会++。
esp和edp中间的 内容才是当前维护的内容,esp上方的内容已经销毁的内容。
寄存器:寄存器是集成到CPU 上的是独立的存储空间。与硬盘,内存都是独立存在的。
三. 用例题讲解创建栈帧的过程
3.1 main 函数的反汇编代码。
main函数也是被调用,main函数是被t_main CRTStartup调用。
第一步:给调用main函数的函数分配栈帧。
第二步:通过将edp压栈,将edp赋值给esp,再将esp的值减减。这样esp和edp之间的差值就是main函数的栈帧空间。
第三步: 将edi,esi,ebx压栈。
lea:是load effective address 是加载有效地址的意思。即esp-014H。
第四步:初始化
从edi开始向下ecx次即39h次,将ecx里dword(一个word两个字节,doubleword是四个字节)个内容初始化为0cccccccch,初始化39h(39h是十六进制,换成十进制次)次。即将main函数里的内容全部初始化为0cccccccch。
所以如果变量不初始化,那么就默认0cccccccch。
第五步:给实参分配空间
将ebp-14h(即b的值)存放到eax里,然后将eax压栈 。
第六步:将实参拷贝到新地址
不用给形参分配新空间,在函数调用之前,就将实参拷贝到新空间。
第七步: call指令传参
call的地址是
然后call指令,call指令跳到add()函数的地址
call跳add()函数的地址之后,在添加call指令的下一条指令的地址。
所以call()指令之后,将call指令的下一条指令的地址压栈。
3.2 add函数的栈帧
然后才是真正的进入add()函数。
第一步:为add创建栈帧
所以我们并没有创建形参,而是在调用函数的时候就把实参传递过去了。 将他们当成X,Y。
因此可以证明形参只是实参的拷贝。所以改变形参不会改变实参。
第二步:将形参相加:
3.3 return 的栈帧
形参是实参的临时拷贝,不是在add栈帧里创建的只是调用一个压栈的空间。
第一步
z出了add()栈帧被销毁,但是值被临时放到eax寄存器里。
所以为了防止z出了作用域被销毁,所以将z放到eax寄存器里临时保存,寄存器是不会因为程序销毁而销毁的。
第二步
pop会导致esp++ ,esp上面的栈会被释放被回收了S。
第三步:回收空间
将ebp赋值给esp的意思就是将esp挪动到edp的位置,上面的空间被回收。
第四步:pop edp
pop edp,将edp出栈,这里栈顶元素edp是main函数的edp,所以将他pop之后他会回到原main函数的edp位置。
然后esp++;这是esp和edp两个指针彻底回到main函数的作用域里。esp指向00C21450即回到了call指令的下一条指令的地址,所以要执行call指令的下一条指令。
第五步:回来之后执行call指令的下一条指令
将esp+8:这里加8的目的值将两个形参的空间给销毁。
第六步
将eax的值赋值给edp -20h,刚刚我们为了防止z的值丢失所以我们将它的值存放在寄存器eax里,所以函数的任务完成了,现在需要将操作完的值还给main函数。
正好edp-20h就是main函数的C。这样返回值就被传回来了。
创建的完整过程(没带销毁)
四.面试问题
4.1 局部变量时怎么创建的?
答:首先,我们为这个局部变量分配栈帧空间,然后在为这个空间初始化空间,然后在为局部变量分配空间。
4.2 为什么局部变量的值是随机值?
答:因为如果局部变量不初始化的话,函数栈帧空间的初始化是随机值,所以如果局部变量不初始化的话,那么它的值就是它分配的那段空间的随机值。如果你将局部变量初始化了,那么你初始化的值就会覆盖掉随机值。
4.3 函数是怎么传参的?传参的顺序是什么?
我们并没有创建形参,而是在调用函数的时候就把实参传递过去了。在还没有调用函数的时候,就以及将实参的拷贝push压栈到空间里,到调用函数的时候,并没有为形参分配空间,而是使用esp+偏移量,找到实参的拷贝。
传参的顺序:参数从右向左压栈。
4.4 形参和实参的关系是什么?
答:形参是实参的拷贝,改变形参不会影响实参。因为他们的空间是独立的。
4.5 函数的调用是怎么做的?
5.6 函数调用是结束后怎么返回的?
在函数调用之前就将call指令的下一条指令的地址就压栈了,然后再将edp(此时的edp是调用函数的上一条函数如main函数的edp)压栈,这样在pop掉edp时,edp会返回到原edp的位置,由于pop操作,会使esp++,这样esp就会调用call指令的下一条指令的地址,这样就会时函数调用返回,然后返回的值,事先已被寄存器eax临时保存,这样值也会被寄存器带回。