前一章已經把 GCD.ASM 會用到但未曾提過的 x64 指令、Win64 API 介紹完畢,這一章就要進入主題,說明堆疊框了。

如同前一章所述,堆疊框是應用程式呼叫副程式時,如有必要會在堆疊上建立一塊記憶體空間,供其執行期間使用。這塊空間通常包含傳遞給副程式的參數、返回位址、呼叫者狀態(如原先的 RBP 或被保存的暫存器)、副程式所需的區域變數,以及與相關的資料,這樣的記憶體空間稱為堆疊框(stack frame)。
對於 x64 系統而言,執行 CALL 指令的時候,RSP 所指的位址必須能被 10h 整除,否則應用程式會當掉。因此當 CALL 指令把返回位址推入堆疊時,返回位址的個位數必為 8,如右圖的 XYZ1C8。
接下來是把 RBP 推入堆疊,將 RBP 原值保存起來,然後把 RSP 之值存入 RBP。如下程式:
push rbp
mov rbp,rsp
接下來會根據區域變數個數、須保存的暫存器個數、副程式內所呼叫其他副程式最多參數
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
21 22 23 24
25 26 27 28
29 30 31 32
33 34 35 36
37 38 39 40
41 42 43 44
45 46 47 48
49 50 51 52
53 54 55 56
57 58 59 60
61 62 63 64
65 66 67 68
69 70 71 72
73 74 75 76
77 78 79 80
81 82 83 84
85 86 87 88
89 90 91 92
93 94 95 96
97 98 99 100
101 102 103 104
105 106 107 108
109 110 111 112
113 114 115 116
117 118 119 120
121 122 123 124
125 126 127 128
129 130 131 132
133 134 135 136 |
OPTION CASEMAP:NONE
EXTRN GetStdHandle:PROC,CloseHandle:PROC,ExitProcess:PROC
EXTRN StrToIntA:PROC,wsprintfA:PROC,WriteConsoleA:PROC,ReadConsoleA:PROC
INCLUDELIB E:\masm64\lib64\kernel32.lib
INCLUDELIB E:\masm64\lib64\user32.lib
INCLUDELIB E:\masm64\lib64\shlwapi.lib
one_hundred_million EQU 100000000 ;一億
;******************************************************************************
.DATA
hInput DQ ? ;標準輸入裝置控制代碼
hOutput DQ ? ;標準輸出裝置控制代碼
n1 DD ? ;使用者輸入的數值1
n2 DD ? ;使用者輸入的數值2
gcd DD ? ;n1、n2的最大公因數
szGcd DB 52 DUP (0)
sHint DB "請輸入小於一億的正整數:"
sErr1 DB "您輸入大於一億的數!"
sErr2 DB "輸入錯誤或輸入零!"
szFmt DB "%d、%d的最大公因數是%d。",0
;******************************************************************************
.CODE
;------------------------------------------------------------------------------
Input PROC USES rbx rsi rdi hIn:QWORD,hOut:QWORD,pNumber:QWORD
;輸入:hIn、hOut-標準輸入、輸出裝置控制代碼
; pNumber-使用者輸入的數值存放位址
;輸出:RAX=0,輸入錯誤
; RAX=1,輸入正確,pNumber所指位址存有使用者輸入的數值
LOCAL nWritn:DWORD,nRead:DWORD,buffer[12]:BYTE
sub rsp,30h
;把參數存入影子空間
mov hIn,rcx
mov hOut,rdx
mov rbx,r8
;invoke WriteConsoleA,hOut,ADDR sHint,SIZEOF sHint,ADDR nWrtin,0(印出sHint字串)
mov rcx,hOut
mov rdx,OFFSET sHint
mov r8,SIZEOF sHint
lea r9,nWritn
mov QWORD PTR [rsp+20h],0
call WriteConsoleA
;invoke ReadConsoleA,hIn,ADDR buffer,ADDR nRead,0(讓使用者輸入數值字串)
mov rcx,hIn
lea rdx,buffer
mov r8,SIZEOF buffer
lea r9,nRead
mov QWORD PTR [rsp+20h],0
call ReadConsoleA
;invoke StrToIntA,ADDR buffer(把使用者輸入的數值字串轉換成數值)
lea rcx,buffer
call StrToIntA
;檢查使用者輸入的數值是否超過十億
cmp eax,one_hundred_million
jbe chk_zr
lea rdx,sErr1 ;若超過印出sErr1
mov r8,SIZEOF sErr1
jmp pnt_it
chk_zr: test eax,eax ;若未超過,再檢查是否為零
jnz ok ;若不為零,跳至ok
lea rdx,sErr2 ;若為零,印出sErr2
mov r8,SIZEOF sErr2
;invoke WriteConsoleA,hOut,rdx,r8d,ADDR nWritn,0(印出sErr1或sErr2的錯誤訊息)
pnt_it: mov rcx,hOut
lea r9,nWritn
mov QWORD PTR [rsp+20h],0
call WriteConsoleA
;使RAX變為零,並離開Input
xor rax,rax
jmp quit
;使使用者輸入的數值存入pNumber所指位址
ok: mov [rbx],eax
quit: ret
Input ENDP
;------------------------------------------------------------------------------
FindGcd PROC num1:DWORD,num2:DWORD
;輸入:num1、num2兩個整數
;輸出:EAX=最大公因數
cmp ecx,edx ;ECX=num1,EDX=num2
jb below ;第一次檢查ECX、EDX何者大,使ECX小於EDX,ECX作為除數
again: xchg ecx,edx ;餘數變為下次的除數,原來的除數變被除數
below: mov eax,edx
xor edx,edx ;使EDX:EAX(被除數)除以ECX(除數)
div ecx
or edx,edx ;餘數為EDX
jnz again ;若EDX為零,最後一次的除數即為GCD
mov eax,ecx ;把最大公因數存入RAX
cdqe
ret
FindGcd ENDP
;------------------------------------------------------------------------------
main PROC
sub rsp,28h
;取得標準輸入、輸出裝置的控制代碼
mov rcx,-10 ;STD_INPUT_HANDLE=-10
call GetStdHandle
mov hInput,rax
mov rcx,-11 ;STD_OUTPUT_HANDLE=-11
call GetStdHandle
mov hOutput,rax
;invoke Input,hInput,hOutput,ADDR n1(輸入第一個正整數)
mov rcx,hInput
mov rdx,hOutput
mov r8,OFFSET n1
call Input
or eax,eax
jz error
;invoke Input,hInput,hOutput,ADDR n2(輸入第二個正整數)
mov rcx,hInput
mov rdx,hOutput
mov r8,OFFSET n2
call Input
or eax,eax
jz error
;invoke FindGcd,n1,n2(計算n1、n2的最大公因數)
mov ecx,n1
mov edx,n2
call FindGcd
;invoke wsprintfA,ADDR szGcd,ADDR szFmt,n1,n2,eax(生成szGcd字串)
mov rcx,OFFSET szGcd
mov rdx,OFFSET szFmt
mov r8d,n1
mov r9d,n2
mov [rsp+20h],eax
call wsprintfA
;invoke WriteConsole,hOutput,ADDR szGcd,eax,0,0(印出szGcd字串)
mov rcx,hOutput
lea rdx,szGcd
mov r8d,eax
xor r9,r9
mov QWORD PTR [rsp+20h],0
call WriteConsoleA
;invoke ExitProcess,0(離開GCD.EXE)
error: xor rcx,rcx
call ExitProcess
main ENDP
;******************************************************************************
END |