[組合語言][作業] Real-mode program
- Using the DateTime.asm on page 447 (5th edition) as the example.
改寫課本p.447~p449的範例程式碼『Example: Displaying the Time and Date』。 - Modify the example such that it becomes self-content without (1) the include file, (2) calling external procedures, and (3) using 32-bit registers.
不能include任何額外程式碼與呼叫函式庫procedure (自己寫的procedure則不受此限制) ,也不能用32bit的暫存器。 - The output format should be as shown on page 449 (5th edition).
輸出的時間格式必須跟p.449相同。
題目限制:
- 註解或刪掉『INCLUDE IrvineXX.inc』
符合不include額外程式碼。
因為沒有include,所以也自然不能呼叫函式庫中的procedure。
Ex: call WriteChar
Ex: call WaitMsg - 程式碼中請宣告『.model small』
不能使用eax、ebx、ecx…等32位元的暫存器(組譯可能會失敗),
但原本的ax、bx、cx 16位元暫存器能正常使用。
- 組譯器:
asm2009的ml (需更改參數與link16)
MASM (Visual Studio) 的ml (可參考Ch13.Keybd的make檔組譯方式) - Debugger:
Windows內建的debug
asm2009的windbg (不是用asm2009組譯的話,會缺少它看的懂的.pdb檔,就無法同步顯示程式碼視窗,會不知道執行到哪一行code) - 組譯出來的程式(你的作業)應該能在16或32位元的作業系統執行,注意執行路徑中不能有中文。
64位元作業系統無法模擬16位元環境,也無debug.exe。
假設程式或debug不能在你的系統上執行,可以使用『DOSBox』軟體模擬16位元的環境。
debug在Windows上的位置
C:\Windows\System32\debug.exe
DOSBox的下載點
ftp://etron3.savs.hcc.edu.tw/windows/DOSBox0.74-win32-installer.exe
組語助教Demohw5.png
引用:組語作業5 64-bit Windows 相關問題之參考解決方法 by FlashTeens
和我一樣使用 64-bit Windows 寫組語作業5的同學們,
是否也在 coding 之前就遇到問題了?
這裡提供一個解決方法
Step 1:
進入以下網址,可直接下載 16-bit Linker 的解壓縮程式:
ftp://ftp.microsoft.com/softlib/mslfiles/lnk563.exe
(參考自 http://wyding.blogspot.tw/2009/04/helloworld-for-16bit-dos-assembly.html )
Step 2:
將剛才下載的 lnk563.exe 移到「符合 16-bit DOS 格式的資料夾路徑」(詳見以下附註),
然後將該程式拖移到 DOSBox 執行,
便會看到同一資料夾中出現 LINK.exe 及 CVPACK.exe。
(我們只要使用前者就好)
執行完畢後,關閉 DOSBox。
[附註]
(1) 檔案名稱有「8.3」的字元數限制,
也就是「基本名稱.副檔名」中,
前面限制最多8個字元,後面則是最多3個字元。
※ 資料來源:參考自 http://zh.wikipedia.org/wiki/8.3
※ 至於資料夾的字元數限制,我目前還不大清楚...
(2) 檔案或資料夾的名稱不區分大小寫,
而限制只能輸入英文字母、數字、"-"(減號) 或 "_"(底線),
不可輸入其他符號及中文。
(3) 路徑中建議不要出現任何空格。(如 "D:\My codes" 便不符合 DOS 的路徑格式。)
Step 3:
將 LINK.exe 改名為 LINK16.exe,並且移動至 asm2009/WINdbg Folder 資料夾中。
Step 4:
將先前作業1~3的 make.bat,複製到本次作業的資料夾,並進行以下兩項修改:
(1) 刪去 "ML /c /coff /Zi /Fl %1.asm" 這一行裡面的 "/coff" 選項。
(2) 將 "LINK /debug /subsystem:console /entry:start......" 開頭的一行,改用以下語法:
LINK16 %filename%.obj
※別忘了和之前一樣,將作業的檔名改一下喔~~
Step 5:
寫完組語程式碼以後,執行 make.bat,
一開始跳出的幾項「找不到」訊息不會影響assembler執行(參見Notes),
而畫面將會跳出幾項設定輸出路徑的訊息,都使用預設即可,也就是一直按Enter (一共4次)。
[Notes]
如果不想讓「找不到」訊息出現,可以將 make.bat 其中5行程式碼:
del %filename%.lst
del %filename%.obj
del %filename%.map
del %filename%.pdb
del %filename%.exe
每一行都修改為如同以下的樣子:
if exist %filename%.副檔名 del %filename%.副檔名
Step 6:
如果懶得每次在DOSBox中輸入一堆指令才進行除錯,
那麼可以到以下網址下載壓縮檔: (我自行撰寫的程式)
http://in1.csie.ncu.edu.tw/~100502514/file/upload/run_debug_exe.zip
下載完畢後,請先開啟「檔案說明.txt」,
依照其說明進行解壓縮,以及其他相關的操作,
如此只要使用拖移程式執行檔的方式,
同樣也可以在 DOSBox 中使用 debug.exe,對於 16-bit 程式進行除錯。
心得
這次流程要使用 16 bits DOS 環境,在當前盛行的 64 bits WINDOWS 環境中不能被執行,而在上次作業中討論編譯出來的 .asm 代碼,助教有講到由於現在 64 bits 作業環境由於暫存器比較多,而且也有特別對於浮點數運算的暫存器,因此格式與 32 bits eax … 等的不同。所以也有可能是因為 64 bits 環境編譯出來的 .asm 代碼,有可能這就是看不到上課教的暫存器名稱的緣故。
回歸正題,由於限制 .model small 又外加 16 bits會有很多指令無法使用,如 movzx … 等,因此在操作上會有些許地困擾。而宣告函數 prototype 前面一定要告知組譯器是 stdcall or C 類型,以方便在拆解 MASM 的假指令,如 INVOKE 傳參等任何相關指令。
在使用 debug 時,必須先將 debug.exe 移入相同資料夾中,如果在 C:\Windows\System32\debug.exe 找不到,可以在以下的網址得到載點 http://blog.thelemur.com/code/how-to-get-debug-exe-working-in-windows-7
程式碼說明
在不用 include 任何函數時,要如何打印出一個非負整數(以十進制方式呈現)是個困難的操作,以ANSI C 代碼實現的方式如下,利用遞迴去解決,但沒有辦法打印出零,因此在零的部分特別處理。又或者可以多傳入一個參數去實現,但這裡忽略不討論。
void print(int n) {
if(n == 0)
return;
print(n/10);
printf("%c", n%10 + '0');
}
在 printf(“%c”) 指令可以用 INT 21h function 2 去完成。
而當要實做 ANSI printf(“%02d”, ax); 時,則採用 special case,判斷 < 10 多印一個 ‘0‘ 之後便呼叫 PrintDec(),但在 PrintDec() 定義為印出一個正整數,因此又special case 0 的輸出。再處理月日年的部分,由於不會有月日年任一數字為 0 的情況,因此可以不用擔心。電腦系統也不會有西元元年的問題。
TITLE ;Display the Data and Time(h5.asm)
.model small, stdcall
PrintChar PROTO char:BYTE ; prototype
PrintDec PROTO int16:WORD ; prototype
PrintPeddedDec PROTO int16:WORD ; prototype
.data
str1 BYTE "Date: $"
str2 BYTE ", Time: $"
.code
start PROC
mov ax, @data
mov ds, ax
; Display the date:
mov ah, 9 ; <print "Date:">
mov dx, OFFSET str1; print string by '$' end
int 21h ; </print "Date:">
mov ah, 2Ah ; get system date
int 21h
; process print format "month-day-year"
mov ah, 0
mov al, dh ; month
INVOKE PrintDec, ax
INVOKE PrintChar, '-'
mov al, dl ; day
INVOKE PrintDec, ax
INVOKE PrintChar, '-'
mov ax, cx ; year
INVOKE PrintDec, ax
; Display the time:
mov ah, 9 ; <print ", Time: ">
mov dx, OFFSET str2; print string by '$' end
int 21h ; </print ", Time: ">
mov ah, 2Ch ; get system time
int 21h
; process print format "hour:minute:second"
mov ah, 0
mov al, ch ; hours
INVOKE PrintPeddedDec, ax
INVOKE PrintChar, ':'
mov al, cl ; minutes
INVOKE PrintPeddedDec, ax
INVOKE PrintChar, ':'
mov al, dh ; seconds
INVOKE PrintPeddedDec, ax
; all process end.
mov ax, 4C00h ; exit program
int 21h
start ENDP
;-------------------------
PrintChar PROC char:BYTE
; Display a single character
;-------------------------
push ax
push dx
mov ah, 2 ; single character output function
mov dl, char
int 21h
pop dx
pop ax
ret
PrintChar ENDP
;-------------------------
PrintDec PROC int16:WORD
; Display a postive integer by recursion
;-------------------------
push ax
push bx
push dx
cmp int16, 0
je L1
mov ax, int16; dividend ax, divisor r/m8 quotient al, remainder ah
mov bl, 10
div bl
mov bl, ah
add bl, '0'
INVOKE PrintDec, al
INVOKE PrintChar, bl
L1:
pop dx
pop bx
pop ax
ret
PrintDec ENDP
;-------------------------
PrintPeddedDec PROC int16:WORD
; Display unsigned integer ax, padding
; to two digit positions with a leading zero.
;-------------------------
push ax
push dx
cmp int16, 10
jae L1 ; if int16 >= 10, jump L1
INVOKE PrintChar, '0'
L1:
cmp int16, 0
jne L2
INVOKE PrintChar, '0'
L2:
INVOKE PrintDec, int16
pop dx
pop ax
ret
PrintPeddedDec ENDP
END start
; INVOKE [subprogram_name] [arg1, arg2, ..., argn]
; => push argn
; push ...
; push arg1
; call subprogram_name
; by page 448 for using ASM display Date and Time.