函數參數壓棧,棧幀ebp,esp怎樣移動的?


 

 

壓棧一次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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM