<C>.函数栈帧的创建和销毁

<在不同的编译器下有所差异,可能会有细小区别>

1.寄存器

一般寄存器:AX、BX、CX、DX
AX:累积暂存器,BX:基底暂存器,CX:计数暂存器,DX:资料暂存器,
EAX、ECX、EDX、EBX:为ax,bx,cx,dx的延申。

堆叠、基底暂存器:BP、SP
BP:基底指标暂存器,SP:堆叠指标暂存器
EBP,ESP为BP,SP的延申。

ebp和esp这两个寄存器,存放的是地址。这两个地址是用来维护函数栈帧的


ebp和esp是如何维护函数栈帧的呢?

每一个函数调用,都要创建一块空间,而且都在栈区上。

假若是这么一段代码

#include<stdio.h>
Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c=Add(a, b);
	printf("%d\n", c);
	return 0;
}

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16我们把这一块空间,称作main函数的函数栈帧。
esp叫栈顶指针,ebp叫栈底指针。现在esp和ebp就是维护main函数的这块空间。当我要调用哪个函数,esp和ebp就会马上去维护。栈区的习惯是先使用高地址,再使用低地址。放数据也是从顶上,往下放数据。


我们一开始调试就发现,main函数被调用了。但是main函数,是被谁调用的呢?

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16 
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16
我们可以得知,main函数也是被调用的,他的返回值,就回到mainret里面去了。从中也能看出,在vs2013中,main函数也是被其他函数调用的。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16调用的main函数。他又是被watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_15,color_FFFFFF,t_70,g_se,x_16调用的。
简单来说就是,mainCRTstartup调用了__tmainCRTstartup,__tmainCRTstartup调用了main函数。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_19,color_FFFFFF,t_70,g_se,x_16


 所以说一开始,就分配了mainCRTstartup和__tmainCRTstartup的空间,然后main函数的空间,然后是Add函数的空间,大体的思路就是这样,接下来详解是如何做到的

1.
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16程序一开始时,内存的布局是这样的情景。栈区的使用是从高地址向低地址使用的
压栈是从栈顶放一个元素,出栈是从栈顶删除一个元素(push and top)
第一步,压栈,push ebp:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_12,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16我就相当于放了个ebp进去,那么esp也会发生变化
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16esp就放到这里了。我们按F10可以观察到
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_13,color_FFFFFF,t_70,g_se,x_16变成了
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16就意味着,esp变小了,esp走到上面去了。

第二步:move,esp的值给ebp

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_13,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 ebp就跑到esp的位置上去了。

第三步:sub 给esp减去一个0E4H的八进制数,也就是228
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16
可见,esp跑当上面来了。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16其实这块区域,就是main函数的空间。


紧跟着,压栈了三个元素 
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_18,color_FFFFFF,t_70,g_se,x_16

 →esp也同时发生了变化:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 第二步,lea:load effective address(把地址加载给edi里面去)
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_17,color_FFFFFF,t_70,g_se,x_16
接下来这三步是干什么呢
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16把39h存到ecx(计数暂存器)里面去,然后eax存入0ccccc,接下来edi就会执行39次

从edi开始,向下39h次双字节double word的数据全部初始化成0cccccch这样的数字:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_12,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16


watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 接下来轮到我的语句了。

0Ah就是10,把10这个数字,放到ebp-8这个位置上去
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_19,color_FFFFFF,t_70,g_se,x_16这块空间,就是我放的a
如果我创建变量时,不给你默认初始化值,那么就放的就是cccccc这样的东西。这就是为什么要初始化的原因。

然后把14h这样的值,放到ebp-14h的位置上去。空了两个整形的位置
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_17,color_FFFFFF,t_70,g_se,x_16
然后把0放到ebp-20h的位置上去。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16这样一来,abc就创建完成


接下来调用add函数:

怎么传参?

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16 他第一步是把ebp-14h,就是我们的b,放到eax里面去了,eax就是20

然后push eax,就是压栈,栈上又压了eax,就是把20放进去了:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_17,color_FFFFFF,t_70,g_se,x_16

 然后重复刚刚的步骤,把ebp-8的值,也就是a,放给ecx,再压栈ecx
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16
这两个动作就是在传参


watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16接下来是调用函数,这个地址非常关键。
当我按F11跳转进入函数时就能发现:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_12,color_FFFFFF,t_70,g_se,x_16

 我这个地方却放了这个我要执行的下一个地址,放到这里去了。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_16,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16
为什么要记住这个地址呢?

因为我执行完后我要回来,从这儿往下执行。


 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16接下来才执行的我函数部分。

第一步:我把main函数的ebp压到上面去了,
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16

 第二步,把esp给ebp
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_18,color_FFFFFF,t_70,g_se,x_16ebp就应该代替了esp的位置了

第三步,给esp减了一个0CCH

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16其实这就是在为add函数开辟栈帧。

然后压栈了ebx,esi,edi,然后rep并把edi往下的所有内容,都初始化为cccccccccccc
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16


 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 然后终于开始我的计算了。

x和y从哪里来呢?

把ebp+8的值放到eax里去。可知ebp+8实际上就是我刚刚压栈压过来的值。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_17,color_FFFFFF,t_70,g_se,x_16

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 10加上ebp+0Ch这个值,eax实际上就是30
然后再把eax的值放到ebp-8里去,ebp-8就是我们创建的z。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_13,color_FFFFFF,t_70,g_se,x_16

 整个过程我有创建参数吗?没有。是我在刚刚调用函数的时候,我传参的时候,就把形参压栈就压下去了,而且先传的b,再传的a。所以参数是从右向左传得到。我最后是找到我压栈传来的值。

 watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_9,color_FFFFFF,t_70,g_se,x_16他就会被认为我要的x和y。

所以说形参是实参的临时拷贝。改变形参,不会影响实参。


接下来是返回:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 return z的意思就是把ebp-8的值放到eax里面去,寄存器可是不会因为z没有了而销毁的。

然后把edi esi ebx全部出栈,这三个东西的空间就被操作系统回收了。
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_13,color_FFFFFF,t_70,g_se,x_16
然后把ebp,赋给esp,esp就应该指到ebp这个位置了。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 然后pop ebp,就把ebp弹出去了,让ebp就回到了他最开始的位置了

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_14,color_FFFFFF,t_70,g_se,x_16
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_15,color_FFFFFF,t_70,g_se,x_16esp也走到栈顶去了。这样就找到了我的空间。

最后一步ret。我们还有call指令的下一个指令的地址还存着的。我存他,就是为了回来,从下一个指令继续走下去
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16

 现在我就回来了,esp也走到这儿了:
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_18,color_FFFFFF,t_70,g_se,x_16
然后形参也就没有用了

 然后接下来add esp,8

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_13,color_FFFFFF,t_70,g_se,x_16就说明我回来了,形参也还给操作系统了。

然后再把eax放到ebp-20h,就是刚刚创建的c里面
watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAWXV1ZWtpIFhZMzk=,size_20,color_FFFFFF,t_70,g_se,x_16


为什么局部变量是随机数,函数是怎么传参等等,就迎刃而解了。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值