通过简单函数来了解函数的栈帧

函数的栈帧

定义

在函数的调用过程中为函数开辟的栈空间用于本次函数的调用中临时变量的保存,现场保护。这块栈空间被称为栈帧。

创建函数

为了了解函数调用栈空间的详细过程,我们先写一段代码,通过代码的反汇编来观察函数的调用过程中汇编指令都做了什么。首先,写入下面的函数:

int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 10;
    int b = 20;
    int ret = 0;
    Add(a, b);
    ret = Add(a, b);
    printf("%d ",ret);
    return 0;

可以看出上面的代码中主要包含了一个main函数和一个Add函数,在main函数中调用了Add函数。所以在申请栈空间的先后顺序上来说应该是先为main函数申请再为Add函数申请。
并将该代码转为反汇编指令来观察:

main函数部分反汇编内容

00401060   push        ebp     //用ebp来存放栈底的地址,方便后面恢复
00401061   mov         ebp,esp         //用esp和ebp两个寄存器来维护main函数的空间
00401063   sub         esp,4Ch         //给esp加上4ch后为main函数开辟空间
00401066   push        ebx              
00401067   push        esi
00401068   push        edi            //把ebx,esi.edi三个数压栈,esp的位置向栈顶的方向移动
00401069   lea         edi,[ebp-4Ch]
0040106C   mov         ecx,13h
00401071   mov         eax,0CCCCCCCCh
00401076   rep stos    dword ptr [edi]   //对main函数的内部空间初始化为0xCCCCCCCC
11:       int a = 10;
00401078   mov         dword ptr [ebp-4],0Ah
12:       int b = 20;
0040107F   mov         dword ptr [ebp-8],14h
13:       int ret = 0;
00401086   mov         dword ptr [ebp-0Ch],0 //创建a,b,c三个局部变量
14:       Add(a, b);
0040108D   mov         eax,dword ptr [ebp-8]    
00401090   push        eax                      //把b的值传给eax,生成形参y并压栈
00401091   mov         ecx,dword ptr [ebp-4]
00401094   push        ecx                     //把a的值传给ecx,生成形参x并压栈
00401094   push        ecx
00401095   call        @ILT+0(_Add) (00401005)  //调用函数,把call指令的下一条指令的地址存入栈顶,以便后面返回到函数
0040109A   add         esp,8

以上为main函数的创建栈空间和创建变量以及调用Add函数的过程,每个函数都要在栈上开辟一块空间由esp和ebp这两个寄存器来维护。 下面按步骤来分析这个过程中具体汇编语言都需要做哪些事情:
(1)把ebp的地址存放起来
(2)esp指向main函数空间的顶部。
(3)把ebp指向esp,然后esp-4ch,也就是为main函数开辟了4ch个字节的空间。
(4)在main函数中放入ebx,esi,edi三个数据,esp继续向上移动3个数据。
(5)对main函数内部数据进行初始化,在VC6.0上初始化为0xCCCCCCCC。
(6)向内存中申请空间存放三个局部变量。
(7)建立临时变量存放函数的参数,产生形参,与实参数值相同,地址不同。形参的存放顺序为从右到左。
(8)调用Add函数,把call指令下一条指令的地址存入栈顶,方便后面返回。

Add函数的部分反汇编

012C13C0  push        ebp  //用ebp来存放栈底的地址,方便后面恢复
012C13C1  mov         ebp,esp  //把esp赋值给ebp
012C13C3  sub         esp,0CCh  //esp向上移动创建新的函数空间
012C13C9  push        ebx  
012C13CA  push        esi  
012C13CB  push        edi  
012C13CC  lea         edi,[ebp-0CCh]  
012C13D2  mov         ecx,33h  
012C13D7  mov         eax,0CCCCCCCCh  
012C13DC  rep stos    dword ptr es:[edi]   //将Add函数内部全初始化为0xCCCCCCCC 
012C13DE  mov         dword ptr [z],0        //创建局部变量z
012C13E5  mov         eax,dword ptr [x]        // 形参需要函数通过ebp加上128在main函数中去找
012C13E8  add         eax,dword ptr [y]      //将获取的形参相加
012C13EB  mov         dword ptr [z],eax  
012C13EE  mov         eax,dword ptr [z]      把相加的结果保存到eax寄存器当中,通过寄存器带回函数的返回值
012C13F1  pop         edi  
012C13F2  pop         esi  
012C13F3  pop         ebx  //让edi,esi,ebx出栈
012C13F4  mov         esp,ebp  //把ebp的值给esp,即esp下移
012C13F6  pop         ebp     //把存放在main栈顶的ebp的值赋给ebp寄存器,ebp和esp重新返回main函数空间,z被销毁
012C13F7  ret  

对Add函数的反汇编部分做出如下总结
(1)在main函数顶部存入ebp的地址方便函数返回使用
(2)与main函数的创建类似,主要是esp和ebp创建空间,并进行数据初始化和局部变量的创建。
(3)通过ebp的位置在main函数中找到形参,并且使用形参进行运算。
(4)把运算所得的结果存入z内,然后再放入寄存器eax中。
(5)三次出栈后,把ebp赋值给esp,esp下移,再popmain函数顶部存放的ebp之后,ebp和esp重新返回main函数空间。z被销毁。
(6)把a和b形参的结果消除,eax把结果带回给ret。

图解

这里写图片描述
数据入栈顺序以及esp和ebp的指向位置
这里写图片描述

main函数跟Add函数在栈空间中的存放大致关系

猜你喜欢

转载自blog.csdn.net/higher_and/article/details/79996200
今日推荐