2013-05-20 20:02:46Morris

[組合語言][作業] 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.