函数栈帧(1)

四个特点

  • 一个限定表尾进行删除(出栈)和插入(入栈)操作的线性表,其过程类似与压子弹与退子弹(后进先出)。
  • 一个由系统自动分配的内存空间,譬如调用函数、创建临时变量时内存空间的创建与销毁。
  • 用于存储函数内部的局部变量、方法调用、函数传参数值等。
  • 由高地址向低地址生长。

 常用寄存器及简单汇编指令

栈帧

  • 栈帧是一块因函数运行而临时开辟的空间。
  • 每调用一次函数便会创建一个独立栈帧。
  • 栈帧中存放的是函数中的必要信息,如局部变量、函数传参、返回值等。
  • 当函数运行完毕栈帧将会销毁。

main函数栈帧创建

main函数的调用链条

main函数在被调用时,创建了栈帧。在调试过程中将转到反汇编,便能直观的看到main函数栈帧创建的过程。首先需明确的是,函数栈帧由寄存器esp,ebp维护。

008B1410  push        ebp  
008B1411  mov         ebp,esp  
008B1413  sub         esp,0E4h  
008B1419  push        ebx  
008B141A  push        esi  
008B141B  push        edi  
008B141C  lea         edi,[ebp-0E4h]  
008B1422  mov         ecx,39h  
008B1427  mov         eax,0CCCCCCCCh  
008B142C  rep stos    dword ptr es:[edi] //dword 为 4个字节

  1. 在__tmainCRTStartup()函数顶部压入ebp,如图所示esp指向ebp,ebp成功压入栈中。

2.esp值传递给ebp

3.esp减去0E4h:由于栈先使用高地址后使用低地址,减去一个值意味着esp指针向低地址移动了0E4h个地址,此处便开辟了main函数的栈帧。

     4.压入ebx,esp指向ebx顶部。

     5.压入esi,esp指向esi顶部。

     6.压入edi,esp指向edi顶部。

     7.将edi向下39h个空间全部改为0xCCCCCCCC。

 

动态演示

局部变量创建

int a = 10;
00AA142E  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00AA1435  mov         dword ptr [ebp-14h],14h  
	int ret = 0;
00AA143C  mov         dword ptr [ebp-20h],0  
  1. 将十六进制整数:0Ah(DEC 10)放入ebp 向低地址移动8个字节。
  2. 将十六进制整数:14h(DEC 20)放入ebp 向低地址移动20个字节。
  3. 将十六进制整数:0(DEC 0)放入ebp 向低地址移动32个字节。

动态演示 

 函数传参与调用
ret = Add(a, b);
00AA1443  mov         eax,dword ptr [ebp-14h]  
00AA1446  push        eax  
00AA1447  mov         ecx,dword ptr [ebp-8]  
00AA144A  push        ecx  
00AA144B  call        00AA10E1  
00AA1450  add         esp,8  
00AA1453  mov         dword ptr [ebp-20h],eax  

从以上汇编代码可知函数是先传参后调用,函数传参顺序是从右往左。

函数传参

  1. ebp - 14h 的地址传给eax,即eax中实际存放了20。
  2. eax 压栈。
  3. ebp - 8 的地址传给ecx,即ecx中实际存放了10。
  4. ecx 压栈。

函数调用

在执行call指令后,栈中压入call指令的下一条地址。 

进入Add()函数,可以看出这与此前main函数开辟栈帧的过程类似,说明Add()函数调用又开辟了一块独立的栈帧。 

在函数栈帧、局部变量创建完毕后,进行Add()函数运算过程:

c = a + b;
00AA13E5  mov         eax,dword ptr [ebp+8]  
00AA13E8  add         eax,dword ptr [ebp+0Ch]  
00AA13EB  mov         dword ptr [ebp-8],eax  

 将(ebp + 8)的值传递给eax,此时的ebp存放Add函数的栈底指针,(ebp + 8) 的位置即函数传参时创建的ecx的地址,其内部存放的正是10。
eax寄存器中执行求和指令,加上(ebp + 0ch) 中的值,同理可以得知(ebp + 0ch)中的值是20。
将eax的经过求和的结果,传递到(ebp - 8)的位置 。 

通过上述过程可以得知函数内部并未给形参开辟空间,而是直接查找了实参传递时的地址,由此解释了形参其实是实参的一份临时拷贝。 

函数返回 
return c;
00AA13EE  mov         eax,dword ptr [ebp-8]  

 将返回值传递至寄存器eax中,因此在函数调用结束函数栈帧被销毁时,返回值并不会销毁。在函数拿到返回值后,开始出栈:

00AA13F1  pop         edi  
00AA13F2  pop         esi  
00AA13F3  pop         ebx  
00AA13F4  mov         esp,ebp  
00AA13F6  pop         ebp  
00AA13F7  ret  

从低位置到高位置依次弹出edi,esi,ebx,随后将ebp赋给esp并弹出ebp,最后执行ret指令返回到调用Add函数的call指令的下一地址,在执行ret指令时实际已弹出After call,以执行指令 add  esp,8,此时esp向高地址移动8字节,esp,ebp重新维护main函数,eax中存放的返回值将被传递给地址(ebp - 20h)即ret的地址。至此,Add函数返回完毕。main函数栈帧销毁过程与前述过程类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值