第七章 堆疊框(二)

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


堆疊框是什麼

如同前一章所述,堆疊框是應用程式呼叫副程式時,如有必要會在堆疊上建立一塊記憶體空間,供其執行期間使用。這塊空間通常包含傳遞給副程式的參數、返回位址、呼叫者狀態(如原先的 RBP 或被保存的暫存器)、副程式所需的區域變數,以及與相關的資料,這樣的記憶體空間稱為堆疊框(stack frame)。

對於 x64 系統而言,執行 CALL 指令的時候,RSP 所指的位址必須能被 10h 整除,否則應用程式會當掉。因此當 CALL 指令把返回位址推入堆疊時,返回位址的個位數必為 8,如右圖的 XYZ1C8。

接下來是把 RBP 推入堆疊,將 RBP 原值保存起來,然後把 RSP 之值存入 RBP。如下程式:

        push    rbp
        mov     rbp,rsp

接下來會根據區域變數個數、須保存的暫存器個數、副程式內所呼叫其他副程式最多參數


GCD.ASM 原始程式

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