壓棧一次esp-4,ebp不變
esp是棧頂指針寄存器,堆棧操作只和esp有關
比如有一個函數a,有兩個參數,一般是這樣的
PUSH 1 參數2壓棧,esp-4
PUSH 2 參數1壓棧,esp-4
CALL a 調用
a:
PUSH EBP 保存ebp
MOV EBP,ESP 改變棧幀,以后訪問參數通過ebp,訪問局部變量通過esp
SUB ESP,8 分配局部變量空間
...
ADD ESP,8
POP EBP 恢復ebp
RETN 8 返回,esp+8
C語句對應匯編語句:
例如函數:
int aaa(int a,int b)
{
int c;
c=a+b;
return c;
}
aaa(1,2);
調試版aaa的代碼
PUSH EBP
MOV EBP,ESP
SUB ESP,4//分配局部變量空間,一個int是4個字節
MOV EAX,DWORD PTR SS:[EBP+8]//讀取參數a
ADD EAX,DWORD PTR SS:[EBP+C]//加上參數b
MOV DWORD PTR SS:[EBP-4],EAX//保存到局部變量c
MOV EAX,DWORD PTR SS:[EBP-4]//eax是返回值
MOV ESP,EBP//恢復棧頂指針
POP EBP//恢復ebp
RETN//返回
調用
PUSH 2//參數2壓棧,esp-4
PUSH 1//參數1壓棧,esp-4
CALL aaa//調用函數
ADD ESP,8//esp+8,平衡堆棧,清除掉參數
發布版的就是這樣了,精簡掉了很多內容
MOV EAX,DWORD PTR SS:[ESP+8]
MOV ECX,DWORD PTR SS:[ESP+4]
ADD EAX,ECX
RETN
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
下面要講的是子程序如何存取參數,因為缺省對堆棧操作的寄存器有 ESP 和 EBP,而 ESP是堆棧指針,無法暫借使用,所以一般使用 EBP 來存取堆棧,假定在一個調用中有兩個參數,而且在 push 第一個參數前的堆棧指針 ESP 為 X,那么壓入兩個參數后的 ESP 為 X-8,程序開始執行 call 指令,call 指令把返回地址壓入堆棧,這時候 ESP 為 X-C,這時已經在子程序中了,我們可以開始使用 EBP 來存取參數了,但為了在返回時恢復 EBP 的值,我們還是再需要一句 push ebp 來先保存 EBP 的值,這時 ESP 為 X-10,再執行一句 mov ebp,esp,根據上圖可以看出,實際上這時候 [ebp + 8] 就是參數1,[ebp + c]就是參數2。另外,局部變量也是定義在堆棧中的,它們的位置一般放在 push ebp 保存的 EBP 數值的后面,局部變量1、2對應的地址分別是 [ebp-4]、[ebp-8],下面是一個典型的子程序,可以完成第一個參數減去第二個參數,它的定義是:
MyProc proto Var1,Var2 ;有兩個參數
local lVar1,lVar2 ;有兩個局部變量
注意,這里的兩個 local 變量實際上沒有被用到,只是為了演示用,具體實現的代碼是:
MyProc proc
push ebp
mov ebp,esp
sub esp,8
mov eax,dword ptr [ebp + 8]
sub eax,dword ptr [ebp + c]
add esp,8
pop ebp
ret 8
MyProc endp
現在對這個子程序分析一下,push ebp/mov ebp,esp 是例行的保存和設置 EBP 的代碼,sub esp,8 在堆棧中留出兩個局部變量的空間,mov /add 語句完成相加,add esp,8 修正兩個局部變量使用的堆棧,ret 8 修正兩個參數使用的堆棧,相當於 ret / add esp,8 兩句代碼的效果。可以看出,這是一個標准的 Stdcall 約定的子程序,使用時最后一個參數先入堆棧,返回時由子程序進行堆棧修正。當然,這個子程序為了演示執行過程,使用了手工保存 ebp 並設置局部變量的方法,實際上,386 處理器有兩條專用的指令是完成這個功能用的,那就是 Enter 和 Leave,Enter 語句的作用就是 push ebp/mov ebp,esp/sub esp,xxx,這個 xxx 就是 Enter 的,Leave 則完成 add esp,xxx/pop ebp 的功能,所以上面的程序可以改成:
MyPorc proc
enter 8,0
mov eax,dword ptr [ebp + 8]
sub eax,dword ptr [ebp + c]
leave
ret 8
MyProc endp
文章出處:飛諾網(www.diybl.com):http://www.diybl.com/course/3_program/hb/hbjs/20071226/93663_2.html
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
一:在分析匯編代碼時總是要遇到無數的 Call ,對於這些 Call ,盡量要根據 Call 之前傳遞的參數和 Call 的返回值來判斷 Call 的功能。傳遞參數的工作必須由函數調用者和函數本身來協調,計算機提供了一種被稱為棧的數據結構來支持參數傳遞。
當參數個數多於一個時,按照什么順序把參數壓入堆棧。函數調用后,由誰來把堆棧恢復。在高級語言中,通過函數調用約定來說明這兩個問題。常見的調用約定有:
二:堆棧框架也稱為活動記錄,它為程序的返回地址,傳遞進來的參數,保存的寄存器喝局部變量保存的堆棧空間。堆棧框架是按以下的步驟創建的。
1 :參數被壓入堆棧。
2 :過程被調用,返回地址被壓入堆棧。
3 :過程開始執行時, EBP 被壓入堆棧。
4 :使 EBP 和 ESP 的值相等,從這里開始, EBP 就作為尋址參數的基址指針。
5 :可以從 ESP 中減掉一個數值來給過程的局部變量創建空間。
【例】按 __stdcall 約定調用函數 test2(Par1, Par2)
push par2 ; 參數 2 ; 參數被壓入堆棧(從右向左)
push par1 ; 參數 1 ;
call test2; ; 過程被調用
{ ; 過程開始執行
push ebp ; EBP 被壓入堆棧,保護現場原先的 EBP 指針
mov ebp, esp ; 使 EBP=ESP, 設置新的 EBP 指針,指向棧頂,
; 從這里開始, EBP 就作為尋址參數的基址指針。
mov eax, [ebp+0C] ; 調用參數 2
mov ebx, [ebp+08] ; 調用參數 1
sub esp, 8 ; 若函數要用局部變量,則要在堆棧中留出點空間
; 從 ESP 中減掉一個數值來給過程的局部變量創建空間
…
add esp, 8 ; 釋放局部變量占用的堆棧
pop ebp ; 恢復現場的 ebp 指針
ret 8 ; 返回(相當於 ret; add esp,8 )
}
三 : 其堆棧調用示意圖:(編譯原理的教程中說的更清楚)
四 : 例子
00401000 /$ 6A04 push 4 ; /Arg2 = 00000004
00401002 |. 6A03 push 3 ; |Arg1 = 00000003
00401004 |. E8 16000000 call 0040101F ; \local.0040101F
00401009 |. 8BD8 mov ebx, eax
0040100B |. 6A00 push 0 ; /ExitCode = 0
0040100D \. FF15 00204000 call dword ptr [<&KERNEL32.ExitProces>; \ExitProcess
00401013 00 db 00
00401014 00 db 00
00401015 00 db 00
00401016 00 db 00
00401017 00 db 00
00401018 00 db 00
00401019 00 db 00
0040101A 00 db 00
0040101B 00 db 00
0040101C 00 db 00
0040101D 00 db 00
0040101E 00 db 00
0040101F /$ 55 push ebp
00401020 |. 8BEC mov ebp, esp
; 使 EBP=ESP=0012FFB4
; 設置新的 EBP 指針,指向棧頂,
; 從這里開始, EBP 就作為尋址參數的基址指針。
00401022 |. 83EC 04 sub esp, 4
; 擴展棧空間 ESP=0012FFB0
00401025 |. 8B450C mov eax, dword ptr [ebp+C] ;
; 當前堆棧 3 個雙字偏移的數值
; ebp+C=0012FFB4+C=0012FFC0
;EAX=[0012FFC0]=00000004
00401028 |. 8B5D 08 mov ebx, dword ptr [ebp+8]
; 當前堆棧 2 個雙字偏移的數值
; ebp+8==0012FFB4+8=0012FFBC
;EBX=[0012FFBC]=00000003
0040102B |. 895D FC mov dword ptr [ebp-4], ebx
;[ebp-4] 就是局部變量 =[0012FFB4-4]=[0012FFB0]
; 自動減 4, 也就是將 EBX 中的值 00000003
; 放入堆棧的 0012FFB0 地址處
; 參數 1 放局部變量里
0040102E |. 0345 FC add eax, dword ptr [ebp-4]
; 將局部變量與 EAX 相加后放入 EAX 中
;EAX=00000007
; 參數 2 與局部變量相加
00401031 |. 83C4 04 add esp, 4
; 釋放局部變量占用的堆棧 ,ESP: 0012FFB4
00401034 |. 5D pop ebp
; 恢復原有的 EBP, ESP:0012FFB8
00401035 \. C2 0800 retn 8
; 返回 ( 相當於 ret; add esp,8)
; ESP: 0012FFC4
堆棧的情況:
0012FFB0 00000003 ; 局部變量
0012FFB4 0012FFF0 ; 保存 EBP , push ebp
0012FFB8 00401009 ; 壓入返回地址
; 返回到 local.< 模塊入口點 >+9 來自 local.0040101F
0012FFBC 00000003 ; push 3 ,每次堆棧地址加 32 位,雙字
0012FFC0 00000004 ; push 4
五 : 說明
Intel 的堆棧是在內存中是向下擴展的。先進棧的數據內存地址最高,后進棧的數據內存地址減少。且數據是按小尾類型存儲,例如:數值 12345678H 存放的形式(假設按字):先存 1234 ,后存放 5678