程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 學 Win32 匯編 [18]: 關於壓棧(PUSH)與出棧(POP) 之二

學 Win32 匯編 [18]: 關於壓棧(PUSH)與出棧(POP) 之二

編輯:Delphi

 由於 "棧" 是由高到低使用的, 所以新壓入的數據的位置更低.

  ESP 中的指針將一直指向這個新位置, 所以 ESP 中的地址數據是動態的.

  每次 PUSH, ESP = ESP - x; 每次 POP, ESP = ESP + x;

  其中的 x 只能是 4 或 2, 因為 Win32 的 PUSH 只可以壓入 32 位(默認)或 16 位的數據.

  ESP 有個名字叫 "棧頂", 其實它指向的是棧中最低位置的數據.

  實例查看 ESP 的變化:

; Test18_1.asm 
.386 
.model flat, stdcall 
 
include  Windows.inc 
include  kernel32.inc 
include  masm32.inc 
include  debug.inc 
includelib kernel32.lib 
includelib masm32.lib 
includelib debug.lib 
 
.data 
   ddVal1 dd 1 
   ddVal2 dd 2 
   dwVal1 dw 3 
   dwVal2 dw 4 
.code 
main proc 
   PrintHex esp ;0012FFA4 
   
   push ddVal1 
   PrintHex esp ;0012FFA0 
   push ddVal2 
   PrintHex esp ;0012FF9C 
   push dwVal1 
   PrintHex esp ;0012FF9A 
   push dwVal2 
   PrintHex esp ;0012FF98 
   
   pop dwVal2 
   PrintHex esp ;0012FF9A 
   pop dwVal1 
   PrintHex esp ;0012FF9C 
   pop ddVal2 
   PrintHex esp ;0012FFA0 
   pop ddVal1 
   PrintHex esp ;0012FFA4 
   ret 
main endp 
end main 

 使用參數壓棧的方式調用函數, 同時揭示 invoke 的本質:

; Test18_2.asm 
.386 
.model flat, stdcall 
 
include  Windows.inc 
include  kernel32.inc 
;include   masm32.inc 
;include   debug.inc 
includelib kernel32.lib 
;includelib masm32.lib 
;includelib debug.lib 
 
include  user32.inc 
includelib user32.lib 
 
.data 
   szMsg   db 'Hello World!', 0 
   szCaption db 'Hi', 0 
.code 
main proc 
  ;invoke MessageBox, NULL, addr szMsg, addr szCaption, MB_OK 
  ;用壓棧的方式調用 MessageBox 函數; 本來就是如此, invoke 只是簡化了這個步驟 
   push MB_OK ;C 函數和系統函數讀取參數的順序是: 從右到左; 最左邊的參數最後使用, 要先壓入 
   push offset szCaption 
   push offset szMsg 
   push NULL ;一個常數會默認當作 32 位數據壓入 
   call MessageBox 
   pop edx  ;隨便出棧到一個地方, 已經沒用了, 相當於進回收站 
   pop edx  ;盡管沒用, 不出是不行的, 因為 push 和 pop 要成對出現 
   pop edx 
   pop edx 
   
  ;invoke ExitProcess, NULL 
  ;用壓棧的方式調用 ExitProcess 函數 
   push NULL 
   call ExitProcess 
   pop edx 
main endp 
end main 

 從上面的例子看出, 函數調用是需要先壓棧(PUSH)參數的;

  PUSH 另一重要作用是保護數據, 因為在 call 其他函數或子過程時可能會覆蓋掉某些寄存器的數據.

  最先需要保護的就是 ESP 中的數據!

  你的函數(或子過程)可能要調用其他函數, 你的函數也可能會被其他函數調用, 甚至是嵌套調用;

  在這種情況下(其實這是最正常不過的事情), ESP 的變化就會比較復雜.

  所以, 每次調用函數(或子過程)都應該先對 ESP 執行保護, 調用完畢再恢復;

  但因 ESP 的動態有些捉摸不定, 所以一般是先 mov ebp, esp, 然後 push ebp ... 像這樣:

mov ebp, esp 
push ebp 
;...函數或子過程 
pop ebp 
mov esp, ebp 
;leave ;可以使用 leave 指令代替上面兩行, 它是對上面兩行的簡化 

  從調試器中查看編譯器添加的保護 ESP 的代碼:

; Test18_3.asm; 這是用於調試的例子 
.386 
.model flat, stdcall 
 
include  Windows.inc 
include  kernel32.inc 
include  masm32.inc 
include  debug.inc 
includelib kernel32.lib 
includelib masm32.lib 
includelib debug.lib 
 
.code 
;求和函數 
sumProc proc v1:dword, v2:dword, v3:dWord 
   mov eax, v1 
   add eax, v2 
   add eax, v3 
   ret 
sumProc endp 
; 
main proc 
   invoke sumProc, 11, 22, 33 
   PrintDec eax ;66 
   ret 
main endp 
end main 
;-------------------------- 
 
;Ctrl + T 是設置或取消斷點 
;Ctrl + D 是調試運行 
;從調試器中看到 sumProc 函數的代碼變成了: 
 
PUSH EBP 
MOV EBP,ESP 
MOV EAX,DWord PTR SS:[EBP+8] 
ADD EAX,DWord PTR SS:[EBP+C] 
ADD EAX,DWord PTR SS:[EBP+10] 
LEAVE 
 
;看來保護 ESP 的工作是由編譯器做的 
;從這裡也看出了 EBP 寄存器的主要用途就是中轉 ESP 中的數據 

  利用 ESP 的地址偏移讀取棧中的數據:

; Test18_4.asm 
.386 
.model flat, stdcall 
 
include  Windows.inc 
include  kernel32.inc 
include  masm32.inc 
include  debug.inc 
includelib kernel32.lib 
includelib masm32.lib 
includelib debug.lib 
 
.code 
main proc 
   push 111 
   push 222 
   push 333 
   push 444 
   
   mov eax, [esp] 
   PrintDec eax   ;444 
   mov eax, [esp+4] 
   PrintDec eax   ;333 
   mov eax, [esp+12] 
   PrintDec eax   ;111 
   
   pop edx 
   pop edx 
   pop edx 
   pop edx 
   ret 
main endp 
end main 

  總結 PUSH 和 POP 的主要用途: 1、暫存與恢復數據; 2、處理函數參數.




  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved