向下生長指的是從記憶體的高位址-->低位址的方向拓展。棧有棧底和棧頂,從上面可以知道棧頂的位址是比棧底的要低的。
對於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 int __cdecl add(int a, int b)
int main()
在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)
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函式之前的狀態。
函式呼叫堆疊變化分析
比如 我們有這樣乙個c函式 include long test int a,int b void main 寫成32位彙編就是這樣 386 model flat,stdcall 這裡我們用stdcall 就是函式引數 壓棧的時候從最後乙個開始壓,和被呼叫函式負責清棧 option casemap n...
函式呼叫堆疊變化分析
跟乙個朋友談堆疊的時候 就寫下了這段文字,順便發到這裡給需要的看看吧 彙編初學者比較頭痛的乙個問題 比如 我們有這樣乙個c函式 include long test int a,int b void main 寫成32位彙編就是這樣 386 model flat,stdcall 這裡我們用stdcal...
獻給彙編初學者 函式呼叫堆疊變化分析
標 題 獻給彙編初學者 函式呼叫堆疊變化分析 作 者 墮落天才 時 間 2007 01 19,19 20 鏈 接 http bbs.pediy.com showthread.php?threadid 38234 跟乙個朋友談堆疊的時候 就寫下了這段文字,順便發到這裡給需要的看看吧 彙編初學者比較頭痛...