這是以前的一個求和函數的例子
; Test32_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
.code
sum proc v1:dword, v2:dword, v3:dWord
mov eax, v1
add eax, v2
add eax, v3
ret
sum endp
;
main proc
invoke sum, 11, 22, 33
PrintDec eax; 66
ret
main endp
end main
把上面的例子改為用寄存器傳遞參數:
; Test32_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
.code
sum proc
add eax, ecx
add eax, edx
ret
sum endp
;
main proc
mov eax, 11
mov ecx, 22
mov edx, 33
invoke sum
PrintDec eax; 66
ret
main endp
end main
如果調用的函數在之後實現, 須用 PROTO 提前聲明:
; Test32_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
;sum proto v1:dword, v2:dword, v3:dWord
sum proto :dword, :dword, :dWord ;函數聲明的主要是參數類型, 一般省略參數名
.code
main proc
invoke sum, 11, 22, 33 ;現在調用的是之後的函數
PrintDec eax; 66
ret
main endp
;
sum proc v1, v2, v3
mov eax, v1
add eax, v2
add eax, v3
ret
sum endp
end main
測試 StdCall 模式下的參數壓棧順序:
子程序可以指定語言模式(StaCall、C、SysCall、Basic、Fortran、Pascal);
如果不指定則默認使用在 .model 中指定的語言模式.
StaCall、C、SysCall 是從右到左壓棧參數;
Basic、Fortran、Pascal 是從左到右壓棧參數.
; Test32_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
sum proc stdcall v1, v2, v3
;查看參數壓棧順序(StdCall 是從右到左 push)
mov edx, [ebp+16]
PrintHex edx ;33
mov edx, [ebp+12]
PrintHex edx ;22
mov edx, [ebp+8]
PrintHex edx ;11
PrintLine
;下面求和代碼
mov eax, v1
add eax, v2
add eax, v3
ret
sum endp
;
main proc
invoke sum, 11h, 22h, 33h
PrintDec eax; 66
ret
main endp
end main
測試 Pascal 模式下的參數壓棧順序:
這是和上面的對比練習, 它們的壓棧參數的順序是反的.
其中的 EBX+8 是最後壓棧參數(DWord)的地址, 同樣 EBX 向上偏移 12、16 就分別是另外兩個參數的地址.
地址 EBX+4 是 RET 將要返回的地址.
為什麼參數不是在 EBX 的下偏移? 因為是先壓棧參數在調用函數.
; Test32_5.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
sum proc pascal v1, v2, v3
;查看參數壓棧順序(pascal 是從左到右 push)
mov edx, [ebp+16]
PrintHex edx ;11
mov edx, [ebp+12]
PrintHex edx ;22
mov edx, [ebp+8]
PrintHex edx ;33
PrintLine
;下面求和代碼
mov eax, v1
add eax, v2
add eax, v3
ret
sum endp
;
main proc
invoke sum, 11h, 22h, 33h
PrintDec eax; 66
ret
main endp
end main
如果用 Call 代替 invoke 能更好地理解壓參順序:
; Test32_6.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
VIEwParam proc C v1, v2, v3 ;把這裡的 C 換為 pascal 會有完全不同的結果
PrintDec v1 ;11
PrintDec v2 ;22
PrintDec v3 ;33
ret
VIEwParam endp
;
main proc
push 33
push 22
push 11
call VIEwParam
leave ;leave 是上面幾個 push 的反操作, 省了不少 pop
ret
main endp
end main
子過程使用 uses 保護寄存器:
所謂保護就是在子過程執行前先壓棧, 執行後在出棧.
; Test32_7.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
dwVal dd ?
.code
sum proc stdcall uses eax ecx edx, v1, v2, v3 ;這其中的 stdcall 可省略
mov eax, v1
mov ecx, v2
mov edx, v3
add eax, ecx
add eax, edx
mov dwVal, eax
ret
sum endp
;
main proc
;sum 對這三個寄存器進行的保護, 先給些測試值
mov eax, 7
mov ecx, 8
mov edx, 9
invoke sum, 11, 22, 33
PrintDec dwVal ;66
PrintDec eax ;7
PrintDec ecx ;8
PrintDec edx ;9
ret
main endp
end main
使用 uses 不如使用 pushad 和 popad 來得簡潔:
; Test32_8.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
dwVal dd ?
.code
sum proc v1, v2, v3
pushad
mov eax, v1
mov ecx, v2
mov edx, v3
add eax, ecx
add eax, edx
mov dwVal, eax
popad
ret
sum endp
;
main proc
mov eax, 7
mov ecx, 8
mov edx, 9
invoke sum, 11, 22, 33
PrintDec dwVal ;66
PrintDec eax ;7
PrintDec ecx ;8
PrintDec edx ;9
ret
main endp
end main
和子程序密切相關的有兩個指令: call 和 ret
call 相當於 push+jmp;
ret 相當於 pop+jmp;
有些 ret 後面還有個數字, 如 ret 8, 這相當於 ret 後再 esp+8(這是清理 8 字節的堆棧).
另外程序可以同 public 和 private 指定是否能跨模塊使用, 默認是 public, 極少用到 private.
聲明其他模塊成員的 extrn、extern、public 關鍵字, 現在用 proto 都可以代替了.
該學模塊化編程了.