第 2 章 另一種選擇:JWASM/HJWASM/UASM


JWASM/HJWASM/UASM 簡介

發展歷史

在前面一章提到,前些時候 ( 民國 100 年前後 ) 要以組合語言撰寫 Win64 程式,仍是相當麻煩的事,這是因為微軟公司移除了 ML64.EXE 的高級語法 ( 這些高級指令有 INVOKE、.IF/.ELSEIF/.ELSE/.ENDIF、.WHILE/ENDW…等等),使 ML64.EXE 變成殘廢,並且至今不打算加入,因此前輩們試圖以不同的方法恢復這些高級指令。有些試圖以巨集指令實現這些高級指令,例如俄國的 Vasily Sotnikov;有些則是打造另一個相容於 ML64.EXE 的組譯器,例如 Japheth。這些先進都有不錯的成績,令人尊敬。

這一章要介紹的是 Japheth 所開發的 JWASM 組譯器。JWASM 起源於加拿大的 Watcom 電腦公司,在民國 77 年時,Watcom 曾經發售聞名於世的 Watcom C/C++ 產品。想當年,Watcom 公司所開發的這套 Watcom C/C++ 編譯器能夠與微軟、寶蘭 ( Borland,最著名的產品是 Turbo C/C++ ) 公司開發行 C 編譯器分庭亢禮並三分天下。所以說,這個 JWASM 可是系出名門,並非泛泛之輩。當年,在這套 Watcom C/C++ 堙A也包含了一個組譯器,稱為 WASM ( WASM 全名為 Open Watcom Assembler )。而後由前輩 Japheth 主導開發,並完成了 32 及 64 位元的組譯器,因為由 Japheth 主導,故前面冠上其名字,稱為 JWASM。JWASM 具有以下的特色:

  1. 可以組譯成多種目的檔格式,包含英特爾 OMF、微軟的 COFF、Linux 的 ELF…等等,以上同時包含 32 位元及 64 位元的格式。
  2. 搭配適當的連結器,就能製作出可在 DOS、Windows、Linux 的可執行檔。
  3. JWASM 是以 C 語言撰寫,可以移植,最新版本是 2.12 ( 民國 102 年發行 ( 即 2013.12.19 ))。
  4. 支援 x64 指令集及多媒體指令集 ( 所支援的多媒體指令集包含 MMX、3DNow!、SSE(1∼4.2)、AVX、VMX )。
  5. JWASM 的原始碼可在 Sybase Open Watcom Public License 協議下,允許免費和非商業用途。
  6. 最重要一點是完全支援 MASM 6.x 及 MASM 8.0 的語法。換句話說,就是 MASM 6.x 的高級語法都回來了!

到了西元 2014 年元月,Japheth 放棄開發新版的 JWASM 組譯器,但是在 MASM32 論壇上,有一群以 Habran 為首的先進接續 Japheth 的工作,繼續維護並開發新的組譯器,並將它改名為 HJWASM,最新版的 HJWASM 可以在下載。但小木偶還是習慣用 JWASM 2.12 版,可以在 SourceForge 下載;而 2.11 版的原始碼也可以在 SourceForge 下載。

到了西元 2017 年五月,HJWASM 被 Github 公司接收,由 Branislav Habus 與 John Hankinson 繼續改良,並更名為 UASM。西元 2017 年 12 月,最新版本是 2.46 版,可以在 UASM 網站上下載。底下小木偶就以 UASM 2.46 版說明安裝方式,至於舊版的 JWASM/HJWASM 的安裝方式也大同小異,讀者可以自行嘗試。

安排 JWASM 的環境

進入 UASM 網站後,可以看到下面的畫面

UASM 2.46.5 (32bit)14/12/2017 uasm246_x86.zip 32bit Binary Package (Windows)
UASM 2.46.5 (64bit)14/12/2017 uasm246_x64.zip 64bit Binary Package (Windows)
UASM 2.46.5 (Linux 64bit)14/12/2017 uasm246_linux64.zip 64bit Linux Executable (GCC)
UASM 2.46.5 (OSX Universal)14/12/2017 uasm246_osx.zip 64bit OSX Executable (GCC)

如果您的系統是 64 位元的 Windows 就下載 uasm246_x64.zip。

下載回來的 uasm246_x64.zip ( 1004885 個位元組 ) 後,把它解壓縮到「C:\UASM」子目錄堙A就安裝完成了。如果以後要解除安裝,把「C:\UASM」子目錄連同其內的所有檔案刪除即可。除了組譯器外,也應該把連結器 ( LINK.EXE ) 及其所需檔案 ( MSPDB80.DLL、MSOBJ80.DLL、MSPDBCORE.DLL、MSPDBSRV.EXE、CVTRES.EXE、LINK.EXE.CONFIG ) 和資源編譯器 ( RC.EXE ) 及所需檔案 ( RCDLL.DLL ) 拷貝到「C:\UASM」子目錄堙C此外也應該建立一個子目錄,「C:\UASM\LIB」,堶惇O GDI32.LIB、KERNEL32.LIB、USER32.LIB 等匯入程式庫。到此組譯器、連結器與匯入程式庫都已建置完成。上述檔案,可在「第 0 章組譯器、連結器、匯入程式庫」處得到更詳細的資料。

最傷腦筋的還有一個,那就是各式各樣的包含檔。幸好許多前輩的努力,有許多包含檔已製作完成,可以使用了。例如 Vasily Sotnikov 製作的 masm64.zip,下載後把 masm64.zip 堙uinclude」子目錄的兩個檔案:win64.inc、Resource.h 解壓縮到「C:\UASM\INCLUDE」子目錄堙C前者地位與 MASM32 堛 WINDOWS.INC 相似,只是還不支援萬國碼;後者是資源編譯器要用到的檔案。

不過小木偶推薦的包含檔是由 Japheth 所領導的小組所製作的,稱為 WinInc 2.08/2.09。各位讀者可以到 UASM 的網站 上下載。根據該網站的資料,WinInc 是專門為 JWASM/HJWASM/UASM、MASM 等組譯器所打造的,包含了所有 Win32/64 API 所需宣告及原型。與 MASM32 不同的是,在 MASM32 堛 WINDOWS.INC 包含了所有常數的定義,因此檔案非常大;但是在 WinInc 堙A卻是分門別類的在各個包含檔堙A它們是由 h2incx.exe 轉換微軟的 VC 所提供的包含檔,*.H 和 *.INC,以 1:1 轉換而來的。除此之外,WinInc 也支援萬國碼,這對開發多國語言程式,是個好消息。把 WinInc208.zip 下載回來後,只需把其內的 Include 子目錄整個解壓縮到「C:\UASM」,就可以使用了。底下的檔案,就是小木偶硬碟中的「C:\UASM\」目錄堛漱漁e:

 96/11/08  上午 08:19            38,904 cvtres.exe
106/12/02  下午 11:37           134,162 History.txt
106/11/26  下午 05:24    <DIR>          Include
102/11/05  上午 10:01            16,307 JWasm.chi
102/11/05  上午 10:01            64,004 JWasm.chm
106/11/26  上午 09:55    <DIR>          Lib
102/12/19  上午 10:01            21,145 License.txt
 96/11/08  上午 08:19         1,045,496 link.exe
 95/06/05  下午 06:02               268 link.exe.config
 96/11/08  上午 08:19            97,280 msobj80.dll
 96/11/07  上午 10:08           235,520 mspdb80.dll
 96/11/07  上午 10:08           396,800 mspdbcore.dll
 96/11/07  上午 10:08           132,096 mspdbsrv.exe
 96/12/11  下午 02:15            67,944 rc.exe
 96/12/11  下午 02:15           394,600 rcdll.dll
102/12/19  上午 10:01             7,564 Readme.txt
101/09/25  下午 12:43             6,144 res2inc.exe
106/12/19  下午 10:47    <DIR>          Samples
106/12/08  上午 10:50           357,200 uasm246_ext.pdf
106/12/14  上午 09:28           818,176 uasm64.exe
106/05/20  下午 05:24             5,291 UasmTargets.zip
102/07/05  上午 10:21             5,132 VS2010CustomBuildRule.zip

接下來,要用文書處理軟體製作 UASM.BAT,其內容如下:

1
2
3
4
SET INCLUDE=C:\UASM\INCLUDE;%INCLUDE%
SET LIB=C:\UASM\LIB;%LIB%
SET PATH=C:\UASM;%PATH%
SET LINK=/SUBSYSTEM:WINDOWS /DEBUG

第一行是指定包含檔位置,第二行是指定匯入程式庫位置,第三行則設定執行檔位置,第四行則是設定微軟連結器的參數,/SUBSYSTEM:WINDOWS 表示製作出視窗程式,/DEBUG 則是指定製造出 PDB 檔案,除錯器可以使用這種檔案來顯示使用者定義的符號名稱,而不是只顯示位址而已。

在命令提示字元堙AUASM 的參數

您可以在「命令提示字元」輸入「UASM64 -h」或「UASM64 -?」,UASM 會顯示出所有參數的簡單描述,也可以在開啟「C:\UASM\JWasm.chm」說明檔閱讀詳細說明。在此小木偶僅簡單介紹最重要且與 MASM 不同的部份。此外,這些參數大小寫是有區別的,例如「-win64」是正確的參數;但如果您輸入「-WIN64」,那麼 UASM 就無法正確組譯了。儘管 UASM 功能強大,但是它無法組譯成功後,直接呼叫連結器,因此得自行連結,所以沒有「-link」這個參數。

UASM -Zi「0|1|2|3]

使用「-Zi」參數,可以在所建立的目的檔中,加上使用者在 *.ASM 堜狳洏峈熔顫兢禤ヾA這符號資料符合除錯程式 CodeView V4,很多除錯程式都可使用,像 WinDbg、OllyDbg 等都可以。UASM 所建立的 OMF 或 COFF 格式的目的檔,都可使用「Zi」參數。「Zi」後面還可以選擇,0∼3 四種選項。0 表示只有全域變數會被寫入目的檔堙F1 表示全域和區域變數都會寫入目的檔;2 表示全域和區域變數以及使用者定義的符號都會寫入,這是內定值;3 表示以 EQU 定義的常數也會被寫入目的檔。

UASM -win64

使用這個參數,會使 UASM 製造出 64 位元 COFF 格式的目的檔,進一步連結後就能產生 PE+ 格式的可執行檔。換句話說,如果要製造出 Win64 系統中的可執行檔,就必須使用這個參數。這個參數會自動啟用

        .MODEL  FLAT,FASTCALL
        OPTION  WIN64:0

UASM 在組合語言原始碼新增功能

OPTION WIN64:switches

「OPTION WIN64:switches」通常是放在原始碼的前面,用來開啟某些設定。這些設定由 switches 數值控制,switches 的每個位元好比一個開關,用來開啟或關閉某些設定。

大部分的情形,我們只要設為 11,就夠了。如果您想使區域變數對齊一節 ( paragraph ),才要設為 15,但是區域變數是不是由一個節開始,並不是很重要,並且一定要遵守的。

OPTION FRAME:<AUTO|NOAUTO>

此指令只會影響 64 位元的程式,這個指令是決定是否讓 UASM 在具有「FRAME」屬性的副程式,自動加上「prologues」和「epilogues」。prologues 是指進入副程式時為了要遵守 Win64 呼叫協定,必須要多出來的程式碼,例如使堆疊增加幾個位元組等工作。而 epilogues 則是結束副程式,返回主程式時,多出來的程式碼。如果使用「OPTION FRAME:AUTO」,UASM 就會自動在具有「FRAME」屬性的副程式,自動加上「prologues」和「epilogues」;如果是「OPTION FRAME:NOAUTO」就不會自動加上,這是內定的選項,建議不要使用這個選項。


HELLOW.ASM

底下小木偶打算撰寫一個可被 JWASM 組譯的 Win64 組合語言程式,當然,此程式是最簡單的,也是初學者學所有語言第一個學習的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        OPTION  CASEMAP:NONE
        OPTION  WIN64:2

INCLUDE         WINDOWS.INC
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      USER32.LIB
;*******************************************************************************
.CONST
szTitle BYTE    '最簡單的程式',0
szText  BYTE    '這是在 Windows 64 位元作業系統,',0dh,0ah
        BYTE    '用組合語言寫的程式。',0
;*******************************************************************************
.CODE
;-------------------------------------------------------------------------------
main    PROC
        INVOKE  MessageBox,0,ADDR szText,ADDR szTitle,MB_OKCANCEL
        INVOKE  ExitProcess,0
main    ENDP
;*******************************************************************************
END     main

把上述檔案以「HELLOW.ASM」的名稱,存入硬碟的「E:\HOMEPAGE\SOURCE\WIN64\HELLOW\」子目錄堙A然後開啟「命令提示字元」,依照下面方法組譯與連結 ( 黃字是您要輸入的,不要忘記每輸完一個指令後要按「Enter」鍵 ):

Microsoft Windows [版本 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\Users\wanker>jwasm [Enter]    →執行 JWASM.BAT ( 見安排 JWASM 的環境 )

C:\Users\wanker>SET INCLUDE=C:\JWASM\INCLUDE;

C:\Users\wanker>SET LIB=C:\JWASM\LIB;

C:\Users\wanker>SET PATH=C:\JWASM;C:\ProgramData\Oracle\Java\javapath;C:\Windows
\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerSh
ell\v1.0\

C:\Users\wanker>SET LINK=/SUBSYSTEM:WINDOWS /DEBUG

C:\Users\wanker>E:

E:\>cd E:\HomePage\SOURCE\WIN64

E:\HomePage\SOURCE\Win64>cd hellow [Enter]    →切換到 hellow.asm 所在子目錄

E:\HomePage\SOURCE\Win64\HELLOW>jwasm -win64 -Zi hellow.asm [Enter]    →組譯
JWasm v2.12pre, Nov 27 2013, Masm-compatible assembler.
Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.

hellow.asm: 20 lines, 2 passes, 141 ms, 0 warnings, 0 errors

E:\HomePage\SOURCE\Win64\HELLOW>link hellow.obj [Enter]    →連結
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/SUBSYSTEM:WINDOWS /DEBUG

E:\HomePage\SOURCE\Win64\HELLOW>

到這個步驟,您已經得到 HELLOW.EXE 可執行檔,執行後如右上圖。

在組譯過程中,所下的指令中,如果要獲得 64 位元 Windows 系統的程式,「-win64」不能省略;而「-Zi」指令是為了除錯時,能顯示符號資料,如不除錯可省略。在此小木偶使用微軟的連結程式,可由微軟「Windows SDK for Windows Server 2008 and .NET Framework 3.5」的光碟影像檔媯悃出來,詳細情形請閱讀第零章的說明。連結時,因為在 JWASM.BAT 堣w經設定環境變數「LINK=/SUBSYSTEM:WINDOWS /DEBUG」,所以在連結時,就不需要再輸入一次。如果要製作 32 或 64 位元 Windows 系統的程式,「/SUBSYSTEM:WINDOWS」不能省略;而「/DEBUG」則是依據 hellow.obj 的內容,製作 hellow.pdb,這個檔案可供除錯器顯示符號資料使用,如果不需要,可省略「/DEBUG」參數。

說明 HELLOW.ASM 原始碼

看見 HELLOW.ASM 的第 16、17 行,INVOKE 又回來了,真是高興!這一切都要歸功於前輩的努力!在 HELLOW.ASM 堛熔 2 行,「OPTION WIN64:2」是和以前在 Win32 不同的地方。因為 HELLOW.ASM 堨u有一個副程式,而且系統把控制權交給 main 時,沒有參數,因此不須把暫存起存入堆疊堙A所以「OPTION WIN64:switch」中,switch 的位元 0,設為 0 即可。各位看官,您會發現,用 JWASM/HJWASM 撰寫 Win64 組合語言程式,和 Win32 幾乎是一模一樣。


用 JWASM 組譯在 Win64 堛獐郱З礸

底下來個較為複雜的 Win64 程式吧,那就是寫一個標準視窗,程式碼如下:

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
        OPTION  CASEMAP:NONE
        OPTION  WIN64:3

UNICODE         EQU             TRUE

INCLUDE         WINDOWS.INC
INCLUDELIB      GDI32.LIB
INCLUDELIB      KERNEL32.LIB
INCLUDELIB      USER32.LIB
;*************************************************************************************************************
.DATA
hInstance       HINSTANCE       ?
hwnd            HWND            ?
buffer          DB              40h DUP (0)
;*************************************************************************************************************
.CONST
ClassName       DW      53h,69h,6dh,70h,6ch,65h,57h,6eh,64h,43h,6ch,61h,73h,73h,0h      ;SimpleWndClass
AppName         DW      7d44h,5408h,8a9eh,8a00h,5f88h,597dh,73a9h,0ff01h,0h             ;組合語言很好玩!
szFmt           DW      60a8h,7684h,96fbh,8166h,5df2h,958bh,6a5fh,25h,75h,65e5h,25h     ;您的電腦已開機%u日%
                DW      75h,6642h,25h,75h,5206h,25h,75h,2eh,25h,75h,79d2h,0h            ;u時%u分%u.%u秒
rect            RECT    <0,0,300,100>
;*************************************************************************************************************
.CODE
;-------------------------------------------------------------------------------------------------------------
;從開機到執行HELLOW1為止,所花的時間記錄在ptString所指的字串堙A返回時,RAX=ptString所指的字串長度
get_time        PROC    ptString:LPSTR
                LOCAL   day:QWORD,hour:QWORD,minute:QWORD,sec:QWORD,msec:QWORD
                call    GetTickCount
                xor     rdx,rdx
                mov     r8,1000
                div     r8
                mov     msec,rdx   ;毫秒
                mov     r8,60
                xor     rdx,rdx
                div     r8
                mov     sec,rdx    ;秒
                xor     rdx,rdx
                div     r8
                mov     minute,rdx ;分
                xor     rdx,rdx
                div     r8
                mov     hour,rax   ;小時
                xor     rdx,rdx
                mov     r8,24
                div     r8
                mov     day,rdx    ;日
                INVOKE  wsprintf,ptString,OFFSET szFmt,day,hour,minute,sec,msec
                ret
get_time        ENDP
;-------------------------------------------------------------------------------------------------------------
WndProc         PROC    hWnd:QWORD,uMsg:DWORD,wParam:QWORD,lParam:QWORD
                LOCAL   ps:PAINTSTRUCT
.IF uMsg==WM_PAINT
                INVOKE  BeginPaint,hWnd,ADDR ps
                INVOKE  get_time,OFFSET buffer
                INVOKE  DrawText,ps.hdc,OFFSET buffer,eax,OFFSET rect,DT_WORDBREAK
                INVOKE  EndPaint,hWnd,ADDR ps

.ELSEIF uMsg==WM_CLOSE
exit:           INVOKE  DestroyWindow,hWnd

.ELSEIF uMsg==WM_DESTROY
                INVOKE  PostQuitMessage,NULL

.ELSE
                INVOKE  DefWindowProc,rcx,edx,r8,r9
                ret
.ENDIF
                xor     rax,rax
                ret
WndProc         ENDP
;-------------------------------------------------------------------------------------------------------------
main            PROC
                LOCAL   wc:WNDCLASSEX
                LOCAL   msg:MSG
                INVOKE  GetModuleHandle,NULL
                mov     hInstance,rax
                mov     rdx,OFFSET ClassName
                mov     rcx,OFFSET WndProc
                mov     wc.cbSize,SIZEOF WNDCLASSEX
                mov     wc.style,CS_HREDRAW or CS_VREDRAW
                mov     wc.lpfnWndProc,rcx
                mov     wc.cbClsExtra,NULL
                mov     wc.cbWndExtra,NULL
                mov     wc.hInstance,rax
                mov     wc.lpszClassName,rdx
                INVOKE  GetStockObject,COLOR_BTNFACE
                mov     wc.hbrBackground,rax
                mov     wc.lpszMenuName,NULL
                INVOKE  LoadIcon,NULL,IDI_APPLICATION
                mov     wc.hIcon,rax
                mov     wc.hIconSm,rax
                INVOKE  LoadCursor,NULL,IDC_ARROW
                mov     wc.hCursor,rax
                INVOKE  RegisterClassEx,ADDR wc
                INVOKE  CreateWindowEx,0,OFFSET ClassName,OFFSET AppName,WS_OVERLAPPEDWINDOW,350,600,300,\
                        100,0,0,hInstance,0
                mov     hwnd,rax
                INVOKE  ShowWindow,hwnd,SW_SHOWNORMAL
                INVOKE  UpdateWindow,hwnd

.WHILE TRUE
                INVOKE  GetMessage,ADDR msg,0,0,0
.BREAK .IF !eax
                INVOKE  TranslateMessage,ADDR msg
                INVOKE  DispatchMessage,ADDR msg
.ENDW       
                mov     rax,msg.wParam
                INVOKE  ExitProcess,eax
main            ENDP
;*************************************************************************************************************
END             main

組譯的方法在底下,而右邊則是執行時的畫面。

E:\HomePage\SOURCE\Win64\HELLOW>jwasm -win64 hellow1.asm [Enter]
JWasm v2.12pre, Nov 27 2013, Masm-compatible assembler.
Portions Copyright (c) 1992-2002 Sybase, Inc. All Rights Reserved.
Source code is available under the Sybase Open Watcom Public License.

hellow1.asm: 112 lines, 3 passes, 140 ms, 0 warnings, 0 errors

E:\HomePage\SOURCE\Win64\HELLOW>link hellow1.obj [Enter]
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/SUBSYSTEM:WINDOWS /DEBUG

E:\HomePage\SOURCE\Win64\HELLOW>

解說 HELLOW1.ASM

OPTION WIN64:3

程式的第 2 行,「OPTION WIN64:3」中,「3」表示了第 0、1 位元均為 1,而第 0 位元代表著 JWASM 組譯時,會自動把副程式所使用的參數暫存器保存在堆疊堙C在 HELLOW1 堙A有兩個副程式,一是 get_time,另一個是視窗函式,WndProc。以視窗函式為例,在進入 WndProc 後,最前面的四道指令是:

00000001`400010c7 48894c2408      mov     qword ptr [rsp+8],rcx
00000001`400010cc 4889542410      mov     qword ptr [rsp+10h],rdx
00000001`400010d1 4c89442418      mov     qword ptr [rsp+18h],r8
00000001`400010d6 4c894c2420      mov     qword ptr [rsp+20h],r9

雖然在 HELLOW1.ASM 的原始程式中沒有寫出來,但是您可以由 Windbg 載入 HELLOW1.EXE 觀察,得到確認。這四道指令就是「OPTION WIN64:switch」中,switch 的第 0 位元為 1 時,JWASM 組譯自動加上去的。如果使 switch 的第 0 位元為 0,JWASM 就不會自行加上這四道指令,除非在原始程式中寫上去,否則在第 54、57、60 行呼叫 WIN64 API 時,因為 hWnd 沒有指定,就會發生錯誤。同樣的,get_time 副程式的第一道指令,也是 JWASM 自行加上去的,

00000001`40001030 48894c2408      mov     qword ptr [rsp+8],rcx

這是因為 get_time 只有一個參數,所以只加上一條指令而已。

使用萬國碼

接下來,第 4 行是「UNICODE EQU TRUE」,這道指令是指示 JWASM 所撰寫的程式在呼叫 WIN64 API 時,是呼叫寬字元版的 API。事實上,Win32/64 API 包含兩種版本的 API,ANSI 版與 UNICODE 版。那程式呼叫 API 時,要怎麼判斷呢?其實在「Include\winuser.inc」堙A有幾行像下面的敘述

@DefProto WINUSERAPI, wsprintfA, c, , <:LPSTR, :LPSTR, :VARARG>
@DefProto WINUSERAPI, wsprintfW, c, , <:LPWSTR, :LPWSTR, :VARARG>
ifdef UNICODE
wsprintf	EQU	<wsprintfW>
else 
wsprintf	EQU	<wsprintfA>

所以只要在原始程式的前面,定義「UNICODE EQU TRUE」,就能使用萬國碼,呼叫 Win API 時,就是呼叫 UNICODE 版的 API。如果使用萬國碼,那麼中文字就得以萬國碼編碼。因此 HELLOW1.ASM 的第 17∼20 行的字串,全以萬國碼編碼。到目前為止,JWASM 組譯器似乎還沒聽說支援萬國碼編碼的原始程式,但是這並不是太大的問題。小木偶已在 Win32 組合語言的第 28 章寫了一個 A2U 程式,可以把 ANSI/ASCII/Big 5 碼轉換成萬國碼。

自動載入的包含檔

前面提過,WinInc 的 WINDOWS.INC 把 Win32/64 的常數、API 原型等定義分門別類的歸類,當呼叫某個 WIN32/64 API 時,能自動載入,所以只需在 HELLOW1.ASM 包含一個 WINDOWS.INC 檔案就可以了,不必再寫「INCLUDE GDI32.INC」這樣的敘述,很方便簡潔,見 HELLOW1.ASM 的第 6 行。當然其他的匯入程式庫還是得一個一個包含進來,無法省略。

進入點

在 ML64.EXE 的語法堥S有進入點,所以原始程式的最後一行只能寫「END」而已,而在命令提示字元執行 ML64.EXE 時,以「/entry:」參數指定進入點。但是 JWASM 最後一行可以用「END entry」指定 entry 為進入點,較為貼近 MASM 6.x 的語法,見 HELLOW1.ASM 的 112 行。

建立標準視窗

在 WIN64 系統中,建立標準視窗的方法與 WIN32 一樣。首先要呼叫 RegisterClassEx,建立視窗類別,見 HELLOW1.ASM 的第 76∼95 行。向作業註冊視窗類別完成後,再以此視窗類別的名稱,呼叫 CreateWindowEx 建立視窗,然後顯示視窗。最後進入無窮的訊息迴圈內,以獲取訊息。

處理訊息的是視窗函式,在 HELLOW1.ASM 程式堙A它是一個稱為 WndProc 的副程式,見第 51∼71 行。視窗函式並不需要處理所有的訊息,只處理程式要做或者關心的的訊息,大部分的訊息都交由 DefWindowProc 處理,見第 66 行。但有離開而關閉程式的兩個訊息,大概是無法避免,一定得處理。這兩個訊息是 WM_CLOSE 和 WM_DESTROY。因此,一般而言,視窗函式長得像下面的樣子,您可以把它當作是視窗函式的樣板,用到時用複製貼上的方法撰寫,很方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WndProc         PROC    hWnd:QWORD,uMsg:DWORD,wParam:QWORD,lParam:QWORD
.IF uMsg==WM_XXX
                ;處理 WM_XXX 的程式碼

.ELSEIF uMsg==WM_YYY
                ;處理 WM_YYY 的程式碼

... ... ...

.ELSEIF uMsg==WM_CLOSE
exit:           INVOKE  DestroyWindow,hWnd

.ELSEIF uMsg==WM_DESTROY
                INVOKE  PostQuitMessage,NULL

.ELSE
                INVOKE  DefWindowProc,rcx,edx,r8,r9
                ret
.ENDIF
                xor     rax,rax
                ret
WndProc         ENDP

視窗函式的四個參數堙A只有 uMsg 是 32 位元長,在堆疊中,仍然要佔用 64 位元的大小,但是較高位址的雙字組是沒有用到的。上面的樣板,必須配合「OPTION WIN64:3」,使自動把 RCX、RDX、R8、R9 保存在堆疊堙A並自動分配呼叫副程式時的堆疊框。否則,到呼叫 DestroyWindow、PostQuitMessage 時,必產生錯誤,因為在堆疊堛 hWnd 參數是錯誤的資料。

64 位元的 MOV 指令

在 x64 組合語言堙A無法把 64 位元大小的立即值存入 64 位元長的記憶體變數內;只能把 32 位元大小的立即值存入 64 位元長的記憶體變數堙C例如底下的例子:

x       DQ      ?                   ;x為 64 位元長的變數
szStr   DB      "This is a dog.",0
        mov     x,4294967296        ;這是錯誤的用法,因為 232=4294967296
        mov     x,4294967295        ;這是正確的用法

在 WIN64 系統中,位址是以 64 位元長度表示,即使是較高的字組或雙字組是 0,但是仍是 64 位元的數值,因此像下面的語法,也是不合法的:

        mov     x,OFFSET szStr

結語

這段日子,小木偶使用 JWASM 去組譯 WIN64 程式,發覺比 ML64 方便好用得多,正努力探索這個組譯器。我想今後,大概都會使用這個組譯器了吧!


回上一章回到首頁到下一章