程序中棧的基礎知識
棧是向下生長的
向下生長指的是從內存的高地址-->低地址的方向拓展。
棧有棧底和棧頂,從上面可以知道棧頂的地址是比棧底的要低的。
對於X86體系的CPU而言,大概需要知道以下基礎知識:
- ebp寄存器:一般叫做基址指針或者幀指針;
- esp寄存器:一般叫做棧指針
- ebp在沒有改變之前始終指向棧底,ebp主要用於在堆棧中尋址
- esp會隨着數據入棧和出棧變化,esp始終指向棧頂
函數調用的過程描述
若函數A調用函數B,那么A函數一般叫做調用者,B函數一般為被調用者,函數調用過程可以做如下描述
- 現將函數A的堆棧基址ebp入棧,用於保存之前任務信息
- 然后將函數A的棧頂指針esp的值賦給ebp,用作新的基址(這里就是函數B的棧底)
- 緊接着在新的ebp基礎上開辟相應的空間當做被調用者B的棧空間,開辟空間一般用sub指令;
- 函數B返回后,從當前棧底ebp恢復為調用者A的棧頂esp,使得棧頂恢復成函數B被調用前的位置;
- 最后調用者A從恢復的棧頂彈出之前的ebp值(因為在函數調用前一步被壓入堆棧);這樣ebp和esp都變成了調用函數B前的位置;
示意圖如下所示
簡單例子
函數調用示例代碼
一個簡單的函數調用例子
#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";
}
函數調用過程匯編解析
-
在main函數調用Add函數之前,main函數的棧幀情況如下所示
-
當main函數調用Add函數的時候,匯編如下
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
- 從調用Add函數的匯編語言中大概可以得出調用函數的大概模式就是如下:
push parameter_n
push parameter_...
push parameter_1
call funcName; 調用函數funcName, 加你個返回地址填入棧,並且跳轉到funcName
main函數調用Add函數的棧示意圖如下:
當call Add (0E111D6h) 進入Add函數之后,匯編語言如下所示
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;為當前棧幀開辟相應的空間
- main函數進入Add函數的示意圖如下所示
當Add函數執行完之后,將執行ret 指令返回,並且esp指向Add函數棧幀底部(就是main 函數棧幀頂部), 緊接着就是從彈出保存的ebp恢復現場,這樣就回到了調用Add函數之前的狀態。