2013-06-03 08:28:42Morris

[組合語言][作業] TSR program


作業要求:

  • Write a TSR program which uses the keyboard interrupt.
    寫一個以鍵盤interrupt作為啟動條件的TSR(Terminate and Stay Resident)程式。
  • When a hot key (using ^ as hot key) is pressed, the TSR program writes your English name and student ID in the middle of the console.
    用“^”當作啟動的熱鍵,按下去後在螢幕上顯示你的“英文名字和學號”。
  • Note: the TSR program must write your name and ID directly onto the video RAM of the console instead of using INT calls.
    提醒:TSR程式必須直接將字串資料寫進“控制畫面的記憶體位址”,而不是使用INT指令。(用了程式會當掉)












心得

這次作業的程序流程跟以往在撰寫的程式不一樣,會保留一部分的程式片段於記憶體內部,並且在執行的時候執行類似於安裝的處理程序,將 DOS 系統的interrupt vector table 修改成跳躍到自己指定的程序片段,因此,所編寫的程序會被當作中繼指令被觸發,而在鍵入鍵盤時,會觸發顯示於螢幕的指令,中間經過剛剛載入的程序進行控制。
對於早期的 DOS 來說有很多 interrupt vector table 的數值是可以被修改的,為了鼓勵早期的開發者在 MS-DOS 上面開發,因此允許使用者進行修改,但由於interrupt vector 會有碰撞的可能,這就產生程序相容性的問題。現在的 vector table 是被部分保護以防止作業系統流程不正確,也是防止病毒的一種方式。
而在處理鍵盤鍵入的 scan key 時,可以參照以下的網址:
http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html
這次的程序流程稍微比較麻煩,有判斷的 AND OR 布林運算,為了不大幅度修改程式碼,採用展開的方式處理每一步。

釐清 push 指令與 call proc 取得參數的觀念,由於丟入的是 reg16 因此參數的是 [bp+4]、[bp+6],又因為是 near call,bp 與 return_address(只有 ip)都是 2 bytes,所以第一參數位置在 [bp+4]。
在音樂處理部分,轉換於以下的網址。
http://www.autoitscript.com/forum/topic/40848-beep-music-mario-bros-theme/


Values for keyboard make/break (scan) code:
 01h	Esc		 31h	N
 02h	1 !		 32h	M
 03h	2 @		 33h	, <		 63h	F16
 04h	3 #		 34h	. >		 64h	F17
 05h	4 $		 35h	/ ?		 65h	F18
 06h	5 %		 36h	Right Shift	 66h	F19
 07h	6 ^		 37h	Grey*		 67h	F20
 08h	7 &		 38h	Alt		 68h	F21 (Fn) [*]
 09h	8 *		 39h	SpaceBar	 69h	F22
 0Ah	9 (		 3Ah	CapsLock	 6Ah	F23
 0Bh	0 )		 3Bh	F1		 6Bh	F24
 0Ch	- _		 3Ch	F2		 6Ch	--
 0Dh	= +		 3Dh	F3		 6Dh	EraseEOF
 0Eh	Backspace	 3Eh	F4
 0Fh	Tab		 3Fh	F5		 6Fh	Copy/Play
 10h	Q		 40h	F6
 11h	W		 41h	F7
 12h	E		 42h	F8		 72h	CrSel
 13h	R		 43h	F9		 73h	<delta> [*]
 14h	T		 44h	F10		 74h	ExSel
 15h	Y		 45h	NumLock		 75h	--
 16h	U		 46h	ScrollLock	 76h	Clear
 17h	I		 47h	Home		 77h	[Note2] Joyst But1
 18h	O		 48h	UpArrow		 78h	[Note2] Joyst But2
 19h	P		 49h	PgUp		 79h	[Note2] Joyst Right
 1Ah	[ {		 4Ah	Grey-		 7Ah	[Note2] Joyst Left
 1Bh	] }		 4Bh	LeftArrow	 7Bh	[Note2] Joyst Up
 1Ch	Enter		 4Ch	Keypad 5	 7Ch	[Note2] Joyst Down
 1Dh	Ctrl		 4Dh	RightArrow	 7Dh	[Note2] right mouse
 1Eh	A		 4Eh	Grey+		 7Eh	[Note2] left mouse
 1Fh	S		 4Fh	End
 20h	D		 50h	DownArrow
 21h	F		 51h	PgDn
 22h	G		 52h	Ins
 23h	H		 53h	Del
 24h	J		 54h	SysReq		---non-key codes---
 25h	K		 55h	[Note1] F11	 00h	kbd buffer full
 26h	L		 56h	left \| (102-key)
 27h	; :		 57h	F11		 AAh	self-test complete
 28h	' "		 58h	F12		 E0h	prefix code
 29h	` ~		 59h	[Note1] F15	 E1h	prefix code
 2Ah	Left Shift	 5Ah	PA1		 EEh	ECHO
 2Bh	\ |		 5Bh	F13 (LWin)	 F0h	prefix code (key break)
 2Ch	Z		 5Ch	F14 (RWin)	 FAh	ACK
 2Dh	X		 5Dh	F15 (Menu)	 FCh	diag failure (MF-kbd)
 2Eh	C					 FDh	diag failure (AT-kbd)
 2Fh	V					 FEh	RESEND
 30h	B					 FFh	kbd error/buffer full


程式碼說明

基本上都是由 TSR.asm 為主幹下去修改的,必須了解 segment at 的指令是抓記憶體的絕對位置。對於 combination 的鍵入組合,讀取 keyboard flag 利用 bit mask 將數字抓出來判斷。
而在處理輸出的時候,特別要注意記憶體的位址,如果沒有加指定的 segment address,則會用正在執行的程序的 cs, ds, ss,由於這程序會用到另一個 segment address 寫入,因此寫的時候要特別注意。
由於 mov 指令沒有 mem, mem 兩個記憶體的格式,因此需要多一部分去處理。在顯示訊息的部分,定義座標的 base 是左上角,為了不讓 counter 產生負數的情況去做處理。而在記憶體的 RAM 中,是用兩個 byte 去表示一格的顯示(印出字元+顯示屬性),因此每次印出的移動是 2 bytes 為單位。
使用 wasd 上下控制一個方向圖示 (^<)=)

[2013/6/8] 新增快捷 m 鍵撥放音樂,音樂為馬力歐主題曲
字型撰寫 beep function,發現 delay 處理上不方便,沒有 int 指令去直接計算時間差,如果要求是以秒為單位,則可以用系統時間去判斷,但如果在其以下 ms 單位則無法表示,藉此使用一些 nop 去拖延時間。



TITLE TSR homework6 (TSR.asm)
; Write a TSR program which uses the keyboard interrupt.
; When a hot key (using ^ as hot key) is pressed, the TSR
; program writes your English name and student ID in the
; middle of the console.
; Note: the TSR program must write your name and ID directly
; onto the video RAM of the console instead of using INT calls.


intno      EQU 09h               ; keyboard interrupt number
center     EQU (80*13+40)*2     ; screen 80x25, two bytes for each cell
leftup    EQU (80*0+0)*2        ; left up side
six_key EQU 07h                ; scan code for 6 key
w_key    EQU    11h                ; scan code for w key
a_key    EQU    1eh                ; scan code for a key
s_key    EQU    1fh                ; scan code for s key
d_key    EQU    20h                ; scan code for d key
m_key    EQU 32h                ; scan code for m key
rt_shift    EQU 01h            ; right shift key: bit 0
lt_shift    EQU 02h            ; left shift key: bit 1

VRamText SEGMENT at 0b800h     ; screen ram memory
VRamText ENDS

codeseg SEGMENT PARA
    assume cs:codeseg, ds:codeseg, es:VRamText
   
    ORG    100h            ; this is a .com program. must be before main
start:                  ; start ENDS
    jmp setup           ; jump to TSR installation
; data block
lcounter      dw    0    ; pressed counter
scounter      dw    0    ; shift counter
ncounter      dw    0   ; shift next counter
shiftimg      db    '>' ; shift image character
old_interrupt dd    ?    ; DWORD(32bit) es:ip
msg              db    'Morris_100502205', '$'
msglen          equ     $-msg-1
musichz    WORD    2600, 796, 796, 796, 1686, 1592, 1592, 1592, 3373, 3183
WORD     3373, 3183, 3183, 6366, 1686, 1592, 1592, 1686, 1592, 1592
WORD     1686, 14857, 1418, 1502, 1418, 1263, 1418, 1592, 1787, 1686
WORD     1592, 1592, 1686, 1592, 1592, 1686, 1592, 1418, 1502, 1418
WORD     1263, 1, 1126, 1062, 843, 796, 1, 1686, 1592, 1592
WORD     1686, 1592, 1592, 1686, 1592, 1418, 1502, 1418, 1263, 1418
WORD     1592, 1787, 1893, 1787, 1592, 1418, 1592, 1787, 1893, 2125
WORD     1893, 1787, 1592, 1787, 1893, 2125, 2385, 2125, 1893, 1787
WORD     1893, 2125, 2527, 2385, 1, 3573, 3183, 3785, 2385, 2527
WORD     2677, 2836, 2527, 2385, 1418, 2527, 1418, 709, 2836, 3183
WORD     2836, 2527, 1592, 2836, 1592, 796, 3183, 3573, 3183, 2836
WORD     1787, 3005, 1787, 893, 3573, 3785, 4011, 3785, 1893, 1787
WORD     1592, 2836, 2527, 2385, 1418, 2527, 1418, 709, 2836, 3183
WORD     2836, 2527, 1592, 2836, 1592, 796, 3183, 3573, 3183, 2836
WORD     1787, 1893, 1787, 1686, 1592, 3183, 3183, 3183, 3183, 6366
WORD     6366, 6366, 6745, 6366, 6745, 6366, 6009, 5672, 5354, 5053
musicde WORD    200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 400, 400, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 400, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     400, 200, 10, 200, 10, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 400, 200
WORD     200, 200, 200, 200, 200, 400, 200, 200, 200, 200
WORD     200, 200, 400, 200, 200, 200, 200, 200, 200, 400
WORD     200, 200, 200, 200, 400, 400, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 400
WORD     400, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
WORD     200, 200, 200, 200, 200, 200, 200, 200, 200, 200
; memory-resident code begins here
int_handler:
    pushf            ; save flags
    push    ax        ; save regs
    push    dx
    push    es
    push    cx
    push    di
    push    si
; point ES:DI to the DOS keyboard flag byte:
    mov        ax, 40h     ; DOS data segment is at 40h
    mov     es, ax   
    mov        di, 17h        ; location of keyboard flag
    mov        ah, es:[di]    ; copy keyboard flag into AH
; test for the LEFT_SHIFT and RIGHT_SHIFT keys:
    test    ah, rt_shift    ; right shift key held down??
    jnz        L1              ; yes: process
    test    ah, lt_shift    ; left shift key held down?
    jnz        L1                ; yes: process
    jmp        byPass            ; no LEFT_SHIFT or RIGHT_SHIFT: exit
; test if hot-key(^) pressed by (left/right)shift+6
L1:    in        al, 60h        ; 60h keyboard input port
    cmp        al, six_key    ; 6 key pressed?
    jnz        byPass        ; no: exit
; point ES:DI to the DOS screen RAM, DS:SI to the string address
; clear last time print
    sub        lcounter, 2*msglen    ; make clear using
    mov        ax, cs
    mov     ds, ax
    mov        ax,    VRamText
    mov        es, ax
    mov        ax, center
    add        ax, lcounter
    mov        di, ax    ; screen buffer offset
    lea        si, msg ; string address offset
; place the message into the video text buffer
    mov        cx, msglen
CL1:        ; run write into screen buffer
    mov        al, ' '        ; get character from string[si]
    mov        BYTE PTR es:[di], al    ; write into buffer[di]
    inc        di
    inc        di                        ; skip over the color attribute byte
    inc        si
    add        lcounter, 2
    loop     CL1
    sub        lcounter, 2*msglen
    add        lcounter, 2
   
    mov        ax, cs
    mov     ds, ax
    mov        ax,    VRamText
    mov        es, ax
    mov        ax, center
    add        ax, lcounter
    mov        di, ax    ; screen buffer offset
    lea        si, msg ; string address offset
; place the message into the video text buffer
    mov        cx, msglen
DP1:        ; run write into screen buffer
    mov        al, BYTE PTR [si]        ; get character from string[si]
    mov        BYTE PTR es:[di], al    ; write into buffer[di]
    inc        di
    inc        di                        ; skip over the color attribute byte
    inc        si
    add        lcounter, 2
    loop     DP1
    jmp        byPassEnd
byPass:        ; ignore unneeded input
; check w, a, s, d input
    in        al, 60h        ; 60h keyboard input port
    cmp        al, w_key    ; w key pressed?
    jz        wshift        ; yes: wshift
    cmp        al, a_key    ; a key pressed?
    jz        ashift        ; yes: ashift
    cmp        al, s_key    ; s key pressed?
    jz        sshift        ; yes: sshift
    cmp        al, d_key    ; d key pressed?
    jz        dshift        ; yes: dshift
    jmp        byPassEnd
wshift:
    mov        ax, scounter
    mov        ncounter, ax
    sub        ncounter, 80
    sub        ncounter, 80
    mov        shiftimg, '^'
    jmp        shiftpro
ashift:   
    mov        ax, scounter
    mov        ncounter, ax
    dec        ncounter
    dec        ncounter
    mov        shiftimg, '<'
    jmp        shiftpro
sshift:   
    mov        ax, scounter
    mov        ncounter, ax
    add        ncounter, 80
    add        ncounter, 80
    mov        shiftimg, '='
    jmp        shiftpro
dshift:   
    mov        ax, scounter
    mov        ncounter, ax
    add        ncounter, 1
    add        ncounter, 1
    mov        shiftimg, '>'
    jmp        shiftpro
; point ES:DI to the DOS screen RAM, DS:SI to the string address
shiftpro:
; clear last time print
    mov        ax, cs
    mov     ds, ax
    mov        ax,    VRamText
    mov        es, ax
    mov        ax, leftup
    add        ax, scounter
    mov        di, ax            ; screen buffer offset
    lea        si, shiftimg     ; string address offset
; place the message into the video text buffer
    mov        cx, 1
CSL1:        ; run write into screen buffer
    mov        al, ' '                    ; get character from string[si]
    mov        BYTE PTR es:[di], al    ; write into buffer[di]
    inc        di
    inc        di                        ; skip over the color attribute byte
    inc        si
    add        scounter, 2
    loop     CSL1
    sub        scounter, 2
;
    mov        ax, ncounter
    mov        scounter, ax
    mov        ax, cs
    mov     ds, ax
    mov        ax,    VRamText
    mov        es, ax
    mov        ax, leftup
    add        ax, scounter
    mov        di, ax            ; screen buffer offset
    lea        si, shiftimg     ; string address offset
; place the message into the video text buffer
    mov        cx, 1
SL1:        ; run write into screen buffer
    mov        al, BYTE PTR [si]        ; get character from string[si]
    mov        BYTE PTR es:[di], al    ; write into buffer[di]
    inc        di
    inc        di                        ; skip over the color attribute byte
    inc        si
    add        scounter, 2
    loop     SL1
    sub        scounter, 2
byPassEnd:
    in        al, 60h        ; 60h keyboard input port
    cmp        al, m_key    ; m key pressed?
    jnz        byPassEnd2    ; no: exit
; music play
    mov        ax, cs
    mov     ds, ax
    mov        cx, 160        ; music size
    lea        di, musichz
    lea        si, musicde
loops:
    mov        ax, [di]
    push    ax
    mov        ax, [si]
    push    ax
    call    beep
    inc        di
    inc        di
    inc        si
    inc        si       
    mov        ax, 1        ; make delay between music note
    push    ax
    mov        ax, 200
    push    ax
    call    beep
    loop    loops
byPassEnd2:
    pop        si        ; restore regs
    pop        di
    pop        cx
    pop        es
    pop        dx
    pop        ax
    popf            ; restore flags
    sti        ; set interrupt flag
    jmp        cs:old_interrupt    ; jump to INT routine
                                ; jmp cs:[old_interrupt] is the same, and by this method far call
    align    dword     ; because memory cell bound, DWORD will be faster.
beep PROC ; push hz[bp+6], push delay[bp+4]
    push    bp
    mov        bp, sp
    push    cx
    push    ax
   
    mov     al, 10110110b     ; out control byte
    out     43h, al           ;     to port 43h
    mov     ax, [bp+6]        ; Frequency = 1.19 MHz/ ???hz
    out     42h,al             ; out low-byte of frequency divider
    mov     al, ah
    out     42h, al         ; out high-byte of frequence divider
; on-off-on
    in         al, 61h
    or      al, 00000011b     ;on bit0 and bit 1
    out     61h, al        
    and     al, 11111100b   ; off bit0 and bit 1
    out     61h, al         ;  of port 61h        
    or      al,    00000011b     ;on bit0 and bit 1
    out     61h, al
; delay ??? ms
    mov        cx, [bp+4]        ; get delay parameter
delay_loop:
    push    cx
    mov        cx, 300h        ; this value will response different computer clock rate.
loop2:
    and        ax, ax           ; nop
    loop     loop2
    pop        cx
    loop     delay_loop
; close
    in         al, 61h
    and     al, 11111100b        ; off bit0 and bit 1
    out     61h, al              ;  of port 61h      
    pop        ax
    pop        cx
    pop        bp
    ret        4                ; clear stack 2+2 bytes
beep ENDP
end_ISR label byte
setup:
;    mov ax, cs        ; not necessary for .com program
;   mov ds, ax        ; code segment already correct.
    mov        ah, 35h
    mov        al, intno ; get INT 9 vector
    int        21h
    mov        WORD PTR old_interrupt, bx   ; save INT 9 vector
    mov        WORD PTR old_interrupt+2, es
    cli        ; clear interrupt flag, disables external interrupts
    mov        ah, 25h      ; set interrupt vector, INT 9 with ds:dx
    mov        al, intno ;
    mov        dx, OFFSET int_handler
    int        21h
    sti        ; set interrupt flag
   
    mov        ah, 09h      ; write a $-terminated string to standard output
    mov        dx, OFFSET msg
    int        21h          ; only to show something.
   
    mov        ax, 3100h  ; terminate and stay resodent
    mov        dx, OFFSET end_ISR ; point to end of resident code
    int        21h          ; dx must be the program size in bytes. execute MS-DOS function
codeseg ENDS
END start

2014-06-24 23:29:35

真不知道你到底是想幫學弟妹還是想害學弟妹.....