Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2076630
  • 博文数量: 519
  • 博客积分: 10070
  • 博客等级: 上将
  • 技术积分: 3985
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-29 14:05
个人简介

只问耕耘

文章分类

全部博文(519)

文章存档

2016年(1)

2013年(5)

2011年(46)

2010年(220)

2009年(51)

2008年(39)

2007年(141)

2006年(16)

我的朋友

分类: WINDOWS

2010-03-19 11:12:57

本文作者:Kasi
文章性质:原创
发布日期:2004-07-16

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

阅读(849) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~