函数调用过程栈帧变化

        某个函数运行时,机器需要分配一定内存去进行函数内的各种操作,这个过程包括将数据和控制从代码的一部分传递到另一部分,分配的这部分就是栈帧,栈帧其实就是是一段有界限的内存空间,它为函数局部变量分配空间,退出该函数时释放这些空间,回到调用它的地方执行下一条指令。

栈的作用:传递参数、局部变量分配、保存调用的返回地址、保存寄存器以供恢复上一栈帧

了解程序在内存中分布的都知道,栈是从高地址向低地址延伸的,每个函数的调用,都有它自己独立的一个栈帧

栈帧主要是有下面2个指针工作

ebp:帧指针(保存了最高地址)

esp:栈指针(保存了最低地址)

另外列举一些用到了的寄存器及定义

寄存器
eax累加(Accumulator)寄存器,常用于函数返回值
ecx计数器(Counter)寄存器,常用作字符串和循环操作中的计数器
edi目的变址寄存器
ebx基址(Base)寄存器,以它为基址访问内存

当程序执行时,栈指针(栈顶)可以移动,因此大多数信息的访问都是相对于帧指针的。

栈帧空间如下,这里顶部为高地址

下面我们根据一个实际的例子使用vs2013执行来说明栈帧在函数执行过程中是如何变化的……

#include <stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

首先我们反汇编一下,得到下面的汇编代码

1、main函数栈帧的创建

int main()
{
01361410  push        ebp        #1 旧ebp压入栈
01361411  mov         ebp,esp    #  新函数esp指向ebp,栈顶变栈底(形成新栈)ebp = 0x0073fd94
01361413  sub         esp,0E4h   #2 esp往低地址移动0xe4,开辟空间
01361419  push        ebx        #  压栈
0136141A  push        esi        #  压栈
0136141B  push        edi        #3 压栈 此时 esp = 0x0073fca4
0136141C  lea         edi,[ebp-0E4h]  #4 下4句为把ebp开始的长度为ox39大小内存初始化0xCCCCCCCC
01361422  mov         ecx,39h  
01361427  mov         eax,0CCCCCCCCh  
0136142C  rep stos    dword ptr es:[edi]  # 重复拷贝ecx次,为0x39
	int a = 1;
0136142E  mov         dword ptr [a],1  # 赋值1到ebp前16个字节
	int b = 2;
01361435  mov         dword ptr [b],2  # 赋值1到ebp前16个字节
	int c = Add(a, b);
0136143C  mov         eax,dword ptr [b]  # b放到exa中
0136143F  push        eax                # 压栈
01361440  mov         ecx,dword ptr [a]  #5 a放到exa中
01361443  push        ecx                # 压栈
01361444  call        _Add (013610E1h)   # call指令调用,先要压栈call下一句指令,即01361449
01361449  add         esp,8  

对应堆栈如下图(底部为高地址):

 

2、add函数栈帧的创建

int Add(int x, int y)
{
013913C0  push        ebp  # main ebp压栈 此时esp=0x0073fc94
013913C1  mov         ebp,esp  
013913C3  sub         esp,0CCh  
013913C9  push        ebx  
013913CA  push        esi  
013913CB  push        edi  #此时esp=0x0073fbbc,ebp=0x0073fc94
013913CC  lea         edi,[ebp-0CCh]  
013913D2  mov         ecx,33h  
013913D7  mov         eax,0CCCCCCCCh  
013913DC  rep stos    dword ptr es:[edi]    # 都和main差不多的操作
	int z = 0;
013913DE  mov         dword ptr [z],0    #0赋值到z中,即为ebp前16个字节中低8位赋值0
	z = x + y;
013913E5  mov         eax,dword ptr [x]  
013913E8  add         eax,dword ptr [y]  # 获取形参a、b的值,相加
013913EB  mov         dword ptr [z],eax  # 保存在c中 
	return z;
013913EE  mov         eax,dword ptr [z]  #结果保存在eax中,通过寄存器带回
}
013913F1  pop         edi  #出栈
013913F2  pop         esi  #出栈
013913F3  pop         ebx   #出栈
013913F4  mov         esp,ebp  #ebp赋值给esp
013913F6  pop         ebp  # 出栈,回到main栈帧
013913F7  ret  

对应栈帧变化如下,其实和main差不多(底部为高地址):

 

 3、add函数返回main

01391444  call        _Add (013910E1h)  
01391449  add         esp,8  #add返回后到这句指令,esp向下移动2位,就到push edi那句指令的栈帧了
0139144C  mov         dword ptr [c],eax  # 下面调用其他也和add类似了

	printf("%d\n", c);
0139144F  mov         esi,esp  
01391451  mov         eax,dword ptr [c]  

	printf("%d\n", c);
01391454  push        eax  
01391455  push        1395858h  
0139145A  call        dword ptr ds:[1399114h]  
01391460  add         esp,8  
01391463  cmp         esi,esp  
01391465  call        __RTC_CheckEsp (0139113Bh)  

	return 0;
0139146A  xor         eax,eax  
}

这样,main函数调用子函数栈帧的生命周期就算结束了,函数的调用就算来来回回操作。

整体栈如下:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值