C++函数调用栈的变化分析

程序中栈的基础知识

栈是向下生长的

向下生长指的是从内存的高地址-->低地址的方向拓展。ios

栈有栈底和栈顶,从上面能够知道栈顶的地址是比栈底的要低的。函数

对于X86体系的CPU而言,大概须要知道如下基础知识:指针

  1. ebp寄存器:通常叫作基址指针或者帧指针
  2. esp寄存器:通常叫作栈指针
  3. ebp在没有改变以前始终指向栈底,ebp主要用于在堆栈中寻址
  4. esp会随着数据入栈和出栈变化,esp始终指向栈顶

函数调用的过程描述

若函数A调用函数B,那么A函数通常叫作调用者,B函数通常为被调用者,函数调用过程能够作以下描述code

  1. 现将函数A的堆栈基址ebp入栈,用于保存以前任务信息
  2. 而后将函数A的栈顶指针esp的值赋给ebp,用做新的基址(这里就是函数B的栈底)
  3. 紧接着在新的ebp基础上开辟相应的空间当作被调用者B的栈空间,开辟空间通常用sub指令;
  4. 函数B返回后,从当前栈底ebp恢复为调用者A的栈顶esp,使得栈顶恢复成函数B被调用前的位置;
  5. 最后调用者A从恢复的栈顶弹出以前的ebp值(由于在函数调用前一步被压入堆栈);这样ebpesp都变成了调用函数B前的位置;

示意图以下所示
funcstackblog

简单例子

函数调用示例代码

一个简单的函数调用例子内存

#include <iostream>

int __cdecl Add(int a, int b)
{
    return a + b;
}

int main()
{
    auto res = Add(2, 3);
    std::cout << "2 + 3 = " << res << std::endl;
    std::cout << "Hello World!\n";
}

函数调用过程汇编解析

  1. main函数调用Add函数以前,main函数的栈帧状况以下所示
    main stackio

  2. 当main函数调用Add函数的时候,汇编以下stream

    auto res = Add(2, 3);

00E12618  push        3  

00E1261A  push        2  

00E1261C  call        Add (0E111D6h)  

00E12621  add         esp,8  

00E12624  mov         dword ptr [res],eax 
  1. 从调用Add函数的汇编语言中大概能够得出调用函数的大概模式就是以下:
push parameter_n
push parameter_...
push parameter_1

call funcName; 调用函数funcName, 加你个返回地址填入栈,而且跳转到funcName

main函数调用Add函数的栈示意图以下:
use add stack基础

call        Add (0E111D6h) 进入Add函数以后,汇编语言以下所示bug

int __cdecl Add(int a, int b)
{

00E12300  push        ebp  

00E12301  mov         ebp,esp  

00E12303  sub         esp,0C0h  

00E12309  push        ebx  

00E1230A  push        esi  

00E1230B  push        edi  

00E1230C  lea         edi,[ebp-0C0h]  

00E12312  mov         ecx,30h  

00E12317  mov         eax,0CCCCCCCCh  

00E1231C  rep stos    dword ptr es:[edi]  

00E1231E  mov         ecx,offset _44E0C52E_AnalyseFunc@cpp (0E1F026h)  

00E12323  call        @__CheckForDebuggerJustMyCode@4 (0E11280h)  

    return a + b;

00E12328  mov         eax,dword ptr [a]  

00E1232B  add         eax,dword ptr [b]  

}

00E1232E  pop         edi  

00E1232F  pop         esi  

00E12330  pop         ebx  

00E12331  add         esp,0C0h  

00E12337  cmp         ebp,esp  

00E12339  call        __RTC_CheckEsp (0E1128Ah)  

00E1233E  mov         esp,ebp  

00E12340  pop         ebp  

00E12341  ret 

在Add函数的汇编语言中能够看到开始的前3句,这里作以下解释

00E12300  push        ebp; 进入新的函数,新函数也须要一个栈帧了,就必须将main函数的栈帧底部所有保存起来,栈顶则是做为一个新函数的栈底

00E12301  mov         ebp,esp;上一个栈帧顶部就是这个栈帧的底部

00E12303  sub         esp,0C0h;为当前栈帧开辟相应的空间
  1. main函数进入Add函数的示意图以下所示
    add stack

当Add函数执行完以后,将执行ret 指令返回,而且esp指向Add函数栈帧底部(就是main 函数栈帧顶部), 紧接着就是从弹出保存的ebp恢复现场,这样就回到了调用Add函数以前的状态。