標籤:
壓棧一次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
函數參數壓棧,棧幀ebp,esp怎樣移動的?