学习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
……