浅谈函数栈帧之图解

函数栈帧是什么?说实话我刚听到这个名词的时候以为是函数战争( ̄ー ̄)尴了个尬。
那么到底什么是函数栈帧呢?简单来说就是:每一次函数的调用都是一个过程,在这个过程中要为函数在栈区创建一个空间,用于本次函数调用中临时变量的保存和现场保护等,而这块栈空间就被称之为函数栈帧。而在我们了解函数栈帧之前就不得不先了解这两个寄存器:

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

ebp:指向栈底的指针
esp:指向栈顶的指针

下面我将通过一段简单的代码从汇编角度来分析一下函数调用过程中底层寄存器的使用。

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

首先是主函数的反汇编代码:

int main() {
00D218E0  push        ebp  
00D218E1  mov         ebp,esp  
00D218E3  sub         esp,0E4h  
00D218E9  push        ebx  
00D218EA  push        esi  
00D218EB  push        edi  
00D218EC  lea         edi,*[ebp-0E4h]* //显示符号名
00D218F2  mov         ecx,39h  
00D218F7  mov         eax,0CCCCCCCCh  
00D218FC  rep stos    dword ptr es:[edi]  
    int a = 10;
00D21908  mov         dword ptr *[ebp-8]*,0Ah//不显示符号名
	int b = 20;
00D2190F  mov         dword ptr [ebp-14h],14h  
	int c = 0;
00D21916  mov         dword ptr [ebp-20h],0  
	c = Add(a, b);
00D2191D  mov         eax,dword ptr [ebp-14h]  
00D21920  push        eax  
00D21921  mov         ecx,dword ptr [ebp-8]  
00D21924  push        ecx  
00D21925  call        00D210B4  
00D2192A  add         esp,8  
00D2192D  mov         dword ptr [ebp-20h],eax  
	printf("%d\n", c);
00D21930  mov         eax,dword ptr [ebp-20h] 
00D21933  push        eax  
00D21934  push        offset string "%d\n" (0D27B30h)  
00D21939  call        _printf (0D210D2h)  
00D2193E  add         esp,8  
	return 0;
00D21941  xor         eax,eax  
}
00D21943  pop         edi  
00D21944  pop         esi  
00D21945  pop         ebx  
00D21946  add         esp,0E4h  
00D2194C  cmp         ebp,esp  
00D2194E  call        __RTC_CheckEsp (0D21244h)  
00D21953  mov         esp,ebp  
00D21955  pop         ebp  
00D21956  ret  

主函数的栈帧

由于 mainCRTstartup调用主函数,为主函数建立了函数栈帧,因此程序在未执行前,函数栈帧如下:
在这里插入图片描述
下面让我们一步一步来分析一下上面的反汇编代码是如何执行的:
首先执行push ebp,就是把ebp的值放如了栈中,而此时esp的地址应往上走四个字节单位
在这里插入图片描述

由于在栈中是从下往上走,由高地址到低地址,所以esp地址减少了四个字节

然后执行move ebp,esp,是把esp的值给了ebp
在这里插入图片描述
接下来执行下面的语句:

00D218E3  sub         esp,0E4h  //做减法,让esp减去0E4h,esp指针上移,实际上为main函数开辟了一段空间。
00D218E9  push        ebx  
00D218EA  push        esi  
00D218EB  push        edi  

在这里插入图片描述
接下来将ebx,esi,edi压入栈内,此时esp应再上升12个字节。
在这里插入图片描述
此时函数栈帧如下:
在这里插入图片描述
继续执行下面语句:

00D218EC  lea         edi,[ebp-0E4h]//就是把后面的地址加载到edi中,相当于给edi放了一个地址进去。
00D218F2  mov         ecx,39h //把39h的值放入ecx中 
00D218F7  mov         eax,0CCCCCCCCh  //0CCCCCCCCh放入eax中
00D218FC  rep stos    dword ptr es:[edi]  

lea:load effective address加载有效地址

在这里插入图片描述
真正产生效果的是 rep stos dword ptr es:[edi] 这条语句,意思是从刚刚的edi这个位置开始向下的ecx(39h次)个dword(double word)的数据全部改成0CCCCCCCCh到ebp前结束。
在这里插入图片描述
继续执行下面语句:

    int a = 10;
00D21908  mov         dword ptr [ebp-8],0Ah//把0Ah(10)放到ebp-8这个地址中去
	int b = 20;
00D2190F  mov         dword ptr [ebp-14h],14h//把14h(20)放到ebp-14h这个地址中去
	int c = 0;
00D21916  mov         dword ptr [ebp-20h],0 //把0放到ebp-20h这个地址中去 

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
函数栈帧如下:
在这里插入图片描述
继续执行下面语句:

	c = Add(a, b);
00D2191D  mov         eax,dword ptr [ebp-14h]  
00D21920  push        eax  
00D21921  mov         ecx,dword ptr [ebp-8]  
00D21924  push        ecx  

在这里插入图片描述

00D21925  call        00D210B4  

在这里插入图片描述

保存call指令的下一条指令地址是为了在Add函数调用完后找到并通过这个地址继续往下执行。

Add函数的栈帧

按F11进入Add函数内部,反汇编为:

int Add(int x, int y) {
00D21770  push        ebp  
00D21771  mov         ebp,esp  
00D21773  sub         esp,0CCh  
00D21779  push        ebx  
00D2177A  push        esi  
00D2177B  push        edi  
00D2177C  lea         edi,[ebp-0CCh]  
00D21782  mov         ecx,33h  
00D21787  mov         eax,0CCCCCCCCh  
00D2178C  rep stos    dword ptr es:[edi]  
	int z = 0;
00D21798  mov         dword ptr [ebp-8],0  
	z = x + y;
00D2179F  mov         eax,dword ptr [ebp+8]  
00D217A2  add         eax,dword ptr [ebp+0Ch]  
00D217A5  mov         dword ptr [ebp-8],eax  
	return z;
00D217A8  mov         eax,dword ptr [ebp-8]  
}
00D217AB  pop         edi  
00D217AC  pop         esi  
00D217AD  pop         ebx  
00D217BB  mov         esp,ebp  
00D217BD  pop         ebp  
00D217BE  ret  

以下语句和上面语句使用方法一样,此处是在为Add函数准备栈帧

00D21770  push        ebp  
00D21771  mov         ebp,esp  
00D21773  sub         esp,0CCh  
00D21779  push        ebx  
00D2177A  push        esi  
00D2177B  push        edi  
00D2177C  lea         edi,[ebp-0CCh]  
00D21782  mov         ecx,33h  
00D21787  mov         eax,0CCCCCCCCh  
00D2178C  rep stos    dword ptr es:[edi]  

在这里插入图片描述

	int z = 0;
00D21798  mov         dword ptr [ebp-8],0  
	z = x + y;
00D2179F  mov         eax,dword ptr [ebp+8]  //把ebp+8的值放入eax中
00D217A2  add         eax,dword ptr [ebp+0Ch]//把ebp+0ch(12)的值加上上一个eax的值放入eax中
00D217A5  mov         dword ptr [ebp-8],eax  //将eax的值放入ebp-8中
	return z;

在这里插入图片描述

	return z;
00D217A8  mov         eax,dword ptr [ebp-8]  //把[ebp-8](z)的值保存在eax寄存器中
00D217AB  pop         edi  
00D217AC  pop         esi  
00D217AD  pop         ebx  
00D217BB  mov         esp,ebp  
00D217BD  pop         ebp  

将edi,dsi和ebx元素全部从栈中弹出,把ebp的值给esp,并弹出ebp,回到main函数。

在这里插入图片描述

00D217BE  ret  

由于main函数的栈顶元素为call指令的下一条指令的地址,所以执行ret语句后直接跳到call指令的下一条指令。

在这里插入图片描述

此条指令是让esp加上8,此时esp指向edi,而这时候就把形参x和y这两个空间还给了操作系统。

00D2192D  mov         dword ptr [ebp-20h],eax  
	printf("%d\n", c);

把寄存器eax的值(30)返回到[ebp-20h]中去,也就是放入c中然后输出c。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值