2004.4.18
一、課程設計之目的
學習DOS下內存駐留程序的基本思想,了解與熟悉用匯編語言編寫程序。本課程設計將完成一個小的.com程序,運行程序后,你的所有按鍵輸入(指在DOS或Windows的DOS模式下)將不被接受,所有輸入將被替換成特定的字符串(回車鍵除外)。
二、內存駐留程序的基本框架(framework of a TSR)
內存駐留程序的基本思想就是讓程序一直停留在內存中,不斷的執行特定的命令。但內存駐留如何被執行呢?一般地,內存駐留程序都是通過修改BIOS或DOS的系統中斷向量表來實現的。比如修改向量表中16H位置的中斷(這個中斷接收鍵盤的按鍵,在DOS中,按鍵按下,這個中斷就會被調用),讓其指向我的程序,這時若有按鍵被按下,則執行的是我的程序。下面是一個最簡單的框架:
CSEG SEGMENT ASSUME CS:CSEG, DS:CSEG ORG 100H Start: JMP Initialize
new_keyboard_io PROC FAR // 這一部分是駐留在內存的內容 STI NOP IRET new_keyboard_io ENDP // 到這里結束
Initialize: MOV DX, OFFSET new_keyboard_io // 新的鍵盤處理程序 MOV AL, 16H // 需更改的向量號(interrupt index) MOV AH, 25H // 更改系統中斷向量表 INT 21H
MOV DX, OFFSET Initialize INT 27H // 將標簽Initialize前的程序駐留內存
CSEG ENDS END Start
|
三、實現原來設計程序
首先,我需要還是需要捕獲用戶的回車鍵,所以需要將原來的DOS本身的鍵盤處理程序保留起來。下面的代碼:
old_keyboard_io DD ? …… Initialize: …… MOV AL, 16H ; Interrupt index in vector table MOV AH, 35H ; Get the interrupt dealing INT 21H ; program's pointer MOV old_keyboard_io, BX ; offset MOV old_keyboard_io[2], ES ; base address ……
|
old_keyboard_io用來儲存原鍵盤處理程序的指針,其中INT 21H – AH=35H,是獲得其指針,返回值在ES:BX中。ES是指針的基地址,BX是偏移量。
其次,就是實現我原來設計的功能,截獲按鍵信息,并改為特定的字符串。下面的實現的代碼:
…… Hello_Msg DB 'Kasi, haha!' ; string to display when catch a key-press Msg_Index DW 0 ; which char in the string been displayed(char index) …… new_keyboard_io PROC FAR ASSUME CS:CSEG, DS:CSEG STI
CMP AH, 00H ; INT 16H - AH = 0 to catch JE new_io_0 ; key-press func ASSUME DS:nothing JMP old_keyboard_io ; No catch, jump to old handler new_io_0: PUSHF ASSUME DS:nothing CALL old_keyboard_io CMP AL, 0DH ; Is a ENTER been pressed ? JNE new_io_1 ; no, output string 'Kasi, haha!' MOV Msg_Index, 0 ; yes, reset the string index JMP new_io_done ; and return new_io_1: PUSH SI MOV SI, Msg_Index ; Get current char index MOV AL, Hello_Msg[SI] ; Get current char
INC SI ; Next char in the Hello_Msg CMP SI, 11 ; Reach the end of the Hello_Msg ? JNE new_io_2 ; no, jump MOV SI, 0 ; yes, set the char index to the beginning new_io_2: MOV Msg_Index, SI ; Save the char index POP SI new_io_done: IRET new_keyboard_io ENDP ……
|
下面的分段說明:
CMP AH, 00H ; INT 16H - AH = 0 to catch JE new_io_0 ; key-press func ASSUME DS:nothing JMP old_keyboard_io ; No catch, jump to old handler
|
這一段代碼是根據書上抄下來的,先檢測AH中是否為0(INT 21H - AH=0表示用戶按下鍵盤),不為0就進入old_keyboard_io,由系統原來的處理程序去處理用戶的請求。這里”ASSUME DS:nothing”是告訴編譯器忽略DS的內容,這樣才能正確跳轉。
new_io_0: PUSHF ASSUME DS:nothing CALL old_keyboard_io CMP AL, 0DH ; Is a ENTER been pressed ? JNE new_io_1 ; no, output string 'Kasi, haha!' MOV Msg_Index, 0 ; yes, reset the string index JMP new_io_done ; and return
|
如果是有按鍵被按下,則先檢測按鍵是否為回車鍵(0DH),如果不是則跳轉到new_io_1去處理,否則將字符串的索引置0(Msg_Inedx = 0)并結束程序。
new_io_1: PUSH SI MOV SI, Msg_Index ; Get current char index MOV AL, Hello_Msg[SI] ; Get current char
INC SI ; Next char in the Hello_Msg CMP SI, 11 ; Reach the end of the Hello_Msg ? JNE new_io_2 ; no, jump MOV SI, 0 ; yes, set the char index to the beginning new_io_2: MOV Msg_Index, SI ; Save the char index POP SI
|
若用戶按下的不是回車鍵,將Hello_Msg[Msg_Index]這個字符放入AL中(因為AL是INT 21H – AH=16H調用的返回值)并讓Msg_Index的值加1,然后判斷Msg_Index是否指向Hello_Msg的尾部了,是的話將Msg_Index置0。 這樣,就完成了整個程序。
四、調試程序
程序寫好了,當然就要編譯和運行。編譯通過,但程序運行后卻沒有任何效果。 按理說,程序應該是沒有問題的,但為何沒有任何效果呢?我懷疑new_keyboard_io是不是沒其作用,如何檢查錯誤呢?用debug一步步跟蹤顯然不明智,于是我在這里加了一個斷點:
new_keyboard_io PROC FAR ASSUME CS:CSEG, DS:CSEG STI INT 03H ; break point CMP AH, 00H ; INT 16H - AH = 0 to catch
|
編譯運行,并在debug用a命令寫入
mov ah, 10 mov al, 00 int 21
|
手動調用INT 21H – AH=16H,希望能在程序中停住,看new_keyboard_io是否被執行了。但我在debug中一t(trace),整個debug就出問題了,原因不明,看來不能用這種方法試驗。 那我就換一個方法,用一個沒有任何命令的new_keyboard_io作測試,代碼如下:
CSEG SEGMENT ASSUME CS:CSEG, DS:CSEG ORG 100H Start: JMP Initialize new_keyboard_io PROC FAR ASSUME CS:CSEG, DS:CSEG STI NOP IRET new_keyboard_io ENDP Initialize: ASSUME CS:CSEG, DS:CSEG MOV DX, OFFSET new_keyboard_io MOV AL, 16H MOV AH, 25H INT 21H
MOV DX, OFFSET Initialize INT 27H
CSEG ENDS END Start
|
編譯運行之后,任何按鍵輸入都不起作用了,看來new_keyboard_io還是被執行了的,那問題就出現在我寫的new_keyboard_io的代碼里面了。我查了查書,INT 21H – AH=00H是接受按鍵消息的啊。但我還發現了一個INT 21H – AH=10H也是接受鍵盤消息的,會不會DOS在提示符(c:\>)下用的是AH=10H呢?我馬上在原程序中加了一下代碼:
…… CMP AH, 00H ; INT 16H - AH = 0 to catch JE new_io_0 ; key-press func ;------------------------------- ; In the DOS prompt(C:>), DOS uses ; INT 16H - AH = 10H to get a char, not ; AH = 00H CMP AH, 10H ; new added codes JE new_io_0 ;------------------------------- ASSUME DS:nothing JMP old_keyboard_io ; No catch, jump to old handler ……
|
然后編譯運行,一切OK!看來是書上的代碼給錯了。(注:我只是在Win98的MS-DOS環境下調試的,不知道純DOS用的是AH=00H還是AH=10H)
五、參考書目
《IBM PC Assembly Language and Programming(Fourth Edition)》, Peter Abel, Prentice Hall, 1998
《DOS內存駐留程序設計與實例》,李振格等,北京航空航天大學出版社,1994
附:打包下載
trick.asm 匯編源程序 trick.com 編譯好的com程序 trick_d.asm 用于調試的源程序 trick_d.com 編譯好的測試程
|