你真的了解函数栈帧的创建与销毁吗?

一、相关的几个名词解释。

  1. ebp:栈底指针。
  2. esp:栈顶指针。(函数再次进行使用是在此上面加入使用的)
  3. call:用于保存当前指令的下一条指令并跳转到目标函数。
  4. push:入栈。(压栈)
  5. pop:出栈。
  6. mov:类似于赋值操作。
  7. add:加法操作。
  8. sub:减法操作。
  9. ecx : 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
  10. eax:是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
  11. esi/edi:分别叫做"源/目标索引寄存器"(source/destination index)。
  12. ret:使得出栈一次,并将出栈的内容当作地址。将程序执行跳转到该地址处。

寄存器:

eax ebx ecx edx ebp esp

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

二、函数调用实例

这里我们使用的环境是vs2013(在不同编译器下,函数调用过程中函数栈帧的创建是略有差异的

接下来用以下代码来分析:

#include <stdio.h>

int 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;
}

1、 从main函数的地方开始,要展开main函数的调用就得为main函数创建栈帧,那我们先来看main函数栈帧的创建。

【思考】

那么创建之后怎么维护的呢?

是由ebp和esp寄存器维护的。ebp和esp之间的空间就是调用mian函数时为其分配的空间(哪个正在被调用就去维护哪个)

【思考】

main函数是不是也被其他函数调用了呢?

我们接下来F10进入调试,可以在调用堆栈里面看到main函数被调用了:

那么被谁调用了呢?

继续按F10往下,我们发现在这调用了main函数:

往上找,我们发现这个函数名是:所以main函数是被_tmainCRTStartup函数调用的,再网上看,而_tmainCRTStartup函数是被mainCRTStartup()函数调用的。 

 所以,在main()函数调用之前,也为_tmainCRTStartup函数和mainCRTStartup()函数分配了空间。

 2、接下来在反汇编里面看具体创建过程:(查看时把显示符号名去掉,这样可以直接看到地址)

我们知道,在mian()也为_tmainCRTStartup函数和mainCRTStartup()函数分配了空间,

(压栈:给栈顶放一个元素 出栈:从栈顶分离一个元素)

  (1)首先先push进行压栈,那么此时esp指向的是压栈之后的顶端位置(此时esp的地址要减少,因为向上是低地址)

 

  (2)接着mov,将esp给到ebp,这时候ebp的指向不再是原位置,而是指向了esp所在位置

  (3)接着sub减码,给esp减去0E4h(16进制数字),那么esp指向的位置发生改变,此时esp和ebp之间的空间是为mian函数开辟的空间。 

 

  (4)接着三个push进行压栈,esp指向的是压栈之后的顶端位置

  (5)接着lea加载,把后面的有效地址加载到edi里面(地址不好之间观察我们可以右击显示符号名,可以看到是ebp-0E4h),意思就是找到了sub减码之后的esp指向的地址。

  (6)接着三步,mov把39h放到ecx里面去,接着下一步mov把0CCCCCCCCh放到eax里面

  (7)再接下来一步,rep stos意思是从edi开始向下的39h这么多个dword的数据改为0CCCCCCCCh(1个word 2个字节,dword 四个字节)我们在内存中可以看到,全部都被初始化为了cc cc cc。

到此,为main函数栈的开辟以及完成,现在就要走正式的代码了。

  (8)接下来将0Ah(10)放到ebp-8的位置,将14h 放到ebp-14h的位置,将0放到ebp-20h的位置。

   (9)接下来调用Add函数:

c=Add()调用函数:

  首先,将ebp-14h位置的元素放到eax里面,压栈,接着把ebp-8位置元素放到ecx里面,压栈,那么esp向上指向ecx位置。

 

 

  接着,call调用函数,(按F11进入函数)

会发现内存中放的是007B1450,是这条指令的下一条指令,我们可以看到进入call函数之后调用了Add函数,之后调用完会退出找到下一条指令,这样call执行完之后会找到这个地址,再接着执行。

进入Add函数里面,

 

  接着,再顶上压一个ebp(来自于main函数的),此时esp向上挪,再将esp给ebp,再sub给esp减去0CCh,此时esp指向减去之后的最顶端,这是ebp和esp之间的空间就是为Add函数分配空间。

  接着,ebx,esi,edi压栈。

  接下来,加载(过程和为创建main函数相似)

  再接着创建变量z:将0放到ebp-8的位置上去,

  接着,把ebp+8的值放到eax中,此时eax为10,再把ebp+0Ch加到eax中,此时eax为30。

 

  接着,再把eax放到ebp-8中去,z即为30.

  接着,往回返,把ebp-8的值放到eax寄存器中(不会销毁),即把30返回。

  接着这一部分:

pop是弹出,将edi,esi,ebx弹出,这时esp指向

 接着把ebp赋给esp,这时esp指向以下箭头处(Add函数栈帧销毁)

 

接着把ebp pop出去,即ebp找到(指向)main函数处的ebp,此时的esp位于: 

即回到main函数里面。

接着ret指令就是从栈顶弹出(POP)call指令的下一条地址,来到call指令的下一条地址。

之后:

 接着esp+8,则形参x,y的空间还给操作系统:

 

 接着把eax的值放到ebp-20h:

所以将结果赋值给c。

以上就是函数栈帧的创建与销毁过程。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值