2004.4.18
一、課程設(shè)計(jì)之目的
學(xué)習(xí)DOS下內(nèi)存駐留程序的基本思想,了解與熟悉用匯編語言編寫程序。本課程設(shè)計(jì)將完成一個(gè)小的.com程序,運(yùn)行程序后,你的所有按鍵輸入(指在DOS或Windows的DOS模式下)將不被接受,所有輸入將被替換成特定的字符串(回車鍵除外)。
二、內(nèi)存駐留程序的基本框架(framework of a TSR)
內(nèi)存駐留程序的基本思想就是讓程序一直停留在內(nèi)存中,不斷的執(zhí)行特定的命令。但內(nèi)存駐留如何被執(zhí)行呢?一般地,內(nèi)存駐留程序都是通過修改BIOS或DOS的系統(tǒng)中斷向量表來實(shí)現(xiàn)的。比如修改向量表中16H位置的中斷(這個(gè)中斷接收鍵盤的按鍵,在DOS中,按鍵按下,這個(gè)中斷就會(huì)被調(diào)用),讓其指向我的程序,這時(shí)若有按鍵被按下,則執(zhí)行的是我的程序。下面是一個(gè)最簡單的框架:
CSEG SEGMENT ASSUME CS:CSEG, DS:CSEG ORG 100H Start: JMP Initialize
new_keyboard_io PROC FAR // 這一部分是駐留在內(nèi)存的內(nèi)容 STI NOP IRET new_keyboard_io ENDP // 到這里結(jié)束
Initialize: MOV DX, OFFSET new_keyboard_io // 新的鍵盤處理程序 MOV AL, 16H // 需更改的向量號(hào)(interrupt index) MOV AH, 25H // 更改系統(tǒng)中斷向量表 INT 21H
MOV DX, OFFSET Initialize INT 27H // 將標(biāo)簽Initialize前的程序駐留內(nèi)存
CSEG ENDS END Start
|
三、實(shí)現(xiàn)原來設(shè)計(jì)程序
首先,我需要還是需要捕獲用戶的回車鍵,所以需要將原來的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用來儲(chǔ)存原鍵盤處理程序的指針,其中INT 21H – AH=35H,是獲得其指針,返回值在ES:BX中。ES是指針的基地址,BX是偏移量。
其次,就是實(shí)現(xiàn)我原來設(shè)計(jì)的功能,截獲按鍵信息,并改為特定的字符串。下面的實(shí)現(xiàn)的代碼:
…… 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
|
這一段代碼是根據(jù)書上抄下來的,先檢測(cè)AH中是否為0(INT 21H - AH=0表示用戶按下鍵盤),不為0就進(jìn)入old_keyboard_io,由系統(tǒng)原來的處理程序去處理用戶的請(qǐng)求。這里”ASSUME DS:nothing”是告訴編譯器忽略DS的內(nèi)容,這樣才能正確跳轉(zhuǎn)。
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
|
如果是有按鍵被按下,則先檢測(cè)按鍵是否為回車鍵(0DH),如果不是則跳轉(zhuǎn)到new_io_1去處理,否則將字符串的索引置0(Msg_Inedx = 0)并結(jié)束程序。
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]這個(gè)字符放入AL中(因?yàn)锳L是INT 21H – AH=16H調(diào)用的返回值)并讓Msg_Index的值加1,然后判斷Msg_Index是否指向Hello_Msg的尾部了,是的話將Msg_Index置0。 這樣,就完成了整個(gè)程序。
四、調(diào)試程序
程序?qū)懞昧耍?dāng)然就要編譯和運(yùn)行。編譯通過,但程序運(yùn)行后卻沒有任何效果。 按理說,程序應(yīng)該是沒有問題的,但為何沒有任何效果呢?我懷疑new_keyboard_io是不是沒其作用,如何檢查錯(cuò)誤呢?用debug一步步跟蹤顯然不明智,于是我在這里加了一個(gè)斷點(diǎn):
new_keyboard_io PROC FAR ASSUME CS:CSEG, DS:CSEG STI INT 03H ; break point CMP AH, 00H ; INT 16H - AH = 0 to catch
|
編譯運(yùn)行,并在debug用a命令寫入
mov ah, 10 mov al, 00 int 21
|
手動(dòng)調(diào)用INT 21H – AH=16H,希望能在程序中停住,看new_keyboard_io是否被執(zhí)行了。但我在debug中一t(trace),整個(gè)debug就出問題了,原因不明,看來不能用這種方法試驗(yàn)。 那我就換一個(gè)方法,用一個(gè)沒有任何命令的new_keyboard_io作測(cè)試,代碼如下:
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
|
編譯運(yùn)行之后,任何按鍵輸入都不起作用了,看來new_keyboard_io還是被執(zhí)行了的,那問題就出現(xiàn)在我寫的new_keyboard_io的代碼里面了。我查了查書,INT 21H – AH=00H是接受按鍵消息的啊。但我還發(fā)現(xiàn)了一個(gè)INT 21H – AH=10H也是接受鍵盤消息的,會(huì)不會(huì)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 ……
|
然后編譯運(yùn)行,一切OK!看來是書上的代碼給錯(cuò)了。(注:我只是在Win98的MS-DOS環(huán)境下調(diào)試的,不知道純DOS用的是AH=00H還是AH=10H)
五、參考書目
《IBM PC Assembly Language and Programming(Fourth Edition)》, Peter Abel, Prentice Hall, 1998
《DOS內(nèi)存駐留程序設(shè)計(jì)與實(shí)例》,李振格等,北京航空航天大學(xué)出版社,1994
附:打包下載
trick.asm 匯編源程序 trick.com 編譯好的com程序 trick_d.asm 用于調(diào)試的源程序 trick_d.com 編譯好的測(cè)試程
|