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 编译好的测试程 |