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函數以前的狀態。

相關文章
相關標籤/搜索