栈帧

每次调用一个函数,都要为该次调用的函数实例分配栈空间。为单个函数分配的那部分栈空间就叫做栈帧(Stack Frame)。

用一个简单函数Add来理解栈帧,它的功能是求两个数的和。

以下代码运行在Microsoft Visual Studio 2015 X86平台下。

#include<stdio.h>
#include<stdlib.h>
int Add(int i, int j)
{
    int ret = 0;
    ret = i + j;
    return ret;
}
int main()
{
    int a = 10;
    int b = 20;
    int ret = Add(a, b);
    system("pause");
    return 0;
}

在main函数处添加断点,开始调试。打开函数调用堆栈看到调用堆栈如图所示。
这里写图片描述

由此可知,main函数也是被其他函数调用。转到反汇编查看main函数的汇编代码

int main()
{
000E16E0 55                   push        ebp  
000E16E1 8B EC                mov         ebp,esp  
000E16E3 81 EC E4 00 00 00    sub         esp,0E4h  
000E16E9 53                   push        ebx  
000E16EA 56                   push        esi  
000E16EB 57                   push        edi  
000E16EC 8D BD 1C FF FF FF    lea         edi,[ebp-0E4h]  
000E16F2 B9 39 00 00 00       mov         ecx,39h  
000E16F7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
000E16FC F3 AB                rep stos    dword ptr es:[edi]  
    int a = 10;
000E16FE C7 45 F8 0A 00 00 00 mov         dword ptr [a],0Ah  
    int b = 20;
000E1705 C7 45 EC 14 00 00 00 mov         dword ptr [b],14h  
    int ret = Add(a, b);
000E170C 8B 45 EC             mov         eax,dword ptr [b]  
000E170F 50                   push        eax  
000E1710 8B 4D F8             mov         ecx,dword ptr [a]  
000E1713 51                   push        ecx  
000E1714 E8 DC F9 FF FF       call        _Add (0E10F5h)  
000E1719 83 C4 08             add         esp,8  
000E171C 89 45 E0             mov         dword ptr [ret],eax  
    system("pause");
000E171F 8B F4                mov         esi,esp  
000E1721 68 30 6B 0E 00       push        offset string "pause" (0E6B30h)  
000E1726 FF 15 60 91 0E 00    call        dword ptr [__imp__system (0E9160h)]  
000E172C 83 C4 04             add         esp,4  
000E172F 3B F4                cmp         esi,esp  
000E1731 E8 DD F9 FF FF       call        __RTC_CheckEsp (0E1113h)  
    return 0;
000E1736 33 C0                xor         eax,eax  
}

Add函数的汇编代码如下

{
000E1690 55                   push        ebp  
000E1691 8B EC                mov         ebp,esp  
000E1693 81 EC CC 00 00 00    sub         esp,0CCh  
000E1699 53                   push        ebx  
000E169A 56                   push        esi  
000E169B 57                   push        edi  
000E169C 8D BD 34 FF FF FF    lea         edi,[ebp-0CCh]  
000E16A2 B9 33 00 00 00       mov         ecx,33h  
000E16A7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
000E16AC F3 AB                rep stos    dword ptr es:[edi]  
    int ret = 0;
000E16AE C7 45 F8 00 00 00 00 mov         dword ptr [ret],0  
    ret = i + j;
000E16B5 8B 45 08             mov         eax,dword ptr [i]  
000E16B8 03 45 0C             add         eax,dword ptr [j]  
000E16BB 89 45 F8             mov         dword ptr [ret],eax  
    return ret;
000E16BE 8B 45 F8             mov         eax,dword ptr [ret]  
}

涉及的特殊寄存器有如下特点(在x86的环境下 ):
ESP 寄存器为 Stack Pointer ,它始终指向栈顶的位置。
EIP 寄存器为返回地址,它是调用函数在执行完 Call 指令后的下一条指令的地址。
EBP 寄存器为 Frame Pointer(亦称 Base Pointer),它被用作在当前的栈帧中寻址所有的函数参数以及局部变量。

根据汇编代码,画出函数的栈帧图如下图
这里写图片描述
在执行Add函数之前,main函数把寄存器ebp入栈,把一段栈空间初始化为0xcccccccc,然后分别把ebx、esi、edi入栈,把函数的参数存入寄存器eax、ecx并入栈,也需要把Add函数的下一条指令地址存入寄存器eip,然后把eip入栈。做完这些工作才开始正式调用Add函数。

当Add函数执行完毕,执行了这样一段汇编代码

000E16C1 5F                   pop         edi  
000E16C2 5E                   pop         esi  
000E16C3 5B                   pop         ebx  
000E16C4 8B E5                mov         esp,ebp  
000E16C6 5D                   pop         ebp  
000E16C7 C3                   ret  

这段代码的意思是,Add函数执行完毕寄存器edi、esi、ebx需要出栈,esp指向ebp然后ebp出栈。由于esp始终执行栈顶,所以此时esp应指向寄存器eip也就是Add函数下一条指令的地址。此时Add函数申请的栈空间已经释放,栈帧图如下图。
这里写图片描述
此时返回main函数,Add函数的栈空间已释放,一次函数调用结束。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值