时钟中断hook

原文链接:

http://cxc200026.blog.163.com/blog/static/34268672009127101618670/


应朋友之约写一个DOS下的时钟程序,要求在屏幕右上角显示实时时间,想了下有两种方案:一种是在系统中安装一个时钟中断hook,每次发生时钟中断时计数,累计18次之后刷新显示,程序驻留内存但不活动;第二种就简单了,程序在前台运行,循环更新时间,更新间隔(1秒)必须用CPU空转耗过去,这样方法只用于演示获取时间和打印字符串的一些函数调用,没有实用价值。


下面是第一种方案的代码:

; clock.asm
; 此程序的功能是在DOS系统中安装一个时钟,一直在屏幕右上角显示当前时间;
; 程序修改了中断向量表,并使相关代码常驻内存;但目前并不具备卸载功能;
; 通过int 21 - 2ch功能调用取得当前时间,然后每隔18次时钟中断更新一次显示;
; 在Microsoft MASM 5中编译通过,编译步骤为:
;   masm clock;
;   link clock;
; 龙第九子 2009/02/26
; 龙第九子 2009/02/28修改


stack   segment stack 'stack'
        dw 32 dup(?)
stack   ends


code    segment
clock   proc far
        assume ss:stack, cs:code
        jmp  start


        old_i8      dd  ?             ; 保存旧的时钟中断向量
        cursor      dw  ?             ; 保存旧的光标位置
        hour        dw  0             ; 保存当前小时数
        minute      dw  0             ; 保存当前分钟数
        second      dw  0             ; 保存当前分钟数
        tickcnt     dw  1             ; 时钟中断计数器
        obuf        db  "00:00:00"    ; 输出缓冲区


new_i8  proc far                  ; 新的时钟中断处理器
        pushf                     ;
        call dword ptr cs:old_i8  ; 先调用原时钟中断处理程序
        dec  cs:tickcnt           ;
        jz   show                 ; tickcnt变为0时刷新显示
        iret                      ; 否则,中断返回


show:   mov  cs:tickcnt, 18
        sti                       ; 及时开中断,使CPU能响应外设的中断请求
        ; 保护现场 pusha
        push    ax
        push    bx
        push    cx
        push    dx
        push    si
        push    di
        push    bp
        push    sp


        push    ds
        push    es
        mov     ax,cs
        mov     ds,ax
        mov     es,ax


        ;保存原光标位置
        mov     bh,0
        mov     ah,3
        int     10h
        mov     cursor,dx


        call showtm               ; 获取并显示当前时间


        ;恢复光标位置
        mov     bh,0
        mov     dx,cursor
        mov     ah,2
        int     10h


        pop     es
        pop     ds
        
        ; 恢复现场 popa
        pop     sp
        pop     bp
        pop     di
        pop     si
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        iret
new_i8  endp


showtm  proc near            ; 在屏幕右上角显示当前时间
        ; DOS功能调用2Ch:real-time -> ch:cl:dh
        mov  ah, 2ch
        int  21h
        mov  byte ptr hour,   ch
        mov  byte ptr minute, cl
        mov  byte ptr second, dh


        ; hour -> obuf[0,1]
        mov  cx, 10
        mov  ax, hour
        div  cl
        add  al, 30h
        mov  obuf[0], al
        add  ah, 30h
        mov  obuf[1], ah


        ; minute -> obuf[3,4]
        mov  ax, minute
        div  cl
        add  al, 30h
        mov  obuf[3], al
        add  ah, 30h
        mov  obuf[4], ah


        ; second -> obuf[6,7]
        mov  ax, second
        div  cl
        add  al, 30h
        mov  obuf[6], al
        add  ah, 30h
        mov  obuf[7], ah


        ; display
        mov  ax, cs
        mov  es, ax
        mov  bp, offset obuf    ; es:[bp] is the address of the string
        mov  al, 0              ; flag
        mov  bh, 0              ; page
        mov  bl, 0Eh            ; character property
        mov  cx, 8              ; character count
        mov  dx, 46h            ; cursor potition
        mov  ah, 13h
        int  10h
        ret
showtm  endp


;;;;;;;;;;;;;;;;;;;;;;;; Entry Point ;;;;;;;;;;;;;;;;;;;;;;;;
start:  ; 把返回地址设置在PSP开头
        push ds
        xor  ax, ax
        push ax
        mov  ax, code
        mov  ds, ax


        ; 保存旧的时钟中断向量
        mov  ax, 3508h
        int  21h
        mov  word ptr old_i8, bx
        mov  word ptr old_i8[2], es


        ; 设置新的时钟中断向量
        mov  dx, offset new_i8
        mov  ax, 2508h
        int  21h


        ; 退出后仍驻留内存
        mov  dx, offset start+15 ; 计算中断处理程序所占用的字节数,+15为了>>4后向上去整
        mov  cl, 4
        shr  dx, cl
        add  dx, 10h       ; 驻留的长度还需包括PSP的内容(100h个字节=10h节)
        mov  ax, 3100h
        int  21h
clock   endp
code    ends
        end start


int 21H(DOS功能调用)
AH=2CH  取系统时间并存放到CH:CL:DH       没有参数
AH=31H   终止用户程序并驻留在主存            AL=退出码   DX=驻留节数(从PSP开始的程序总字节数/16)


int10H(显示器驱动程序)
AH=13H   写字符串到指定的光标位置
AL(0)等于0时表示写后光标不动,等于1时表示写后修改光标位置;AL(1)等于0表示在BL中包含字符属性,等于1时表示字符属性包含在字符串中;BH=页号;CX=字符串长度;(DH DL)=起始光标位置(行 列);ES:[BP]=待显示字符串的起始地址。
8位字符属性的定义:[0-2]表示字符的前景颜色;[3]表示字符亮度“增亮/普通”;[4-6]为字符的背景颜色;[7]表示字符是否闪烁。[0-2]&[4-6]的值为:000黑 001蓝 010绿 011青 100红 101洋红 110褐 111白


以上代码在MASM5中编译通过,在Win32 ntvdm中测试可用,但是程序运行时ntvdm会发生一些诡异的问题,不知道是不是和兼容性有关。在FreeDOS下面测试根本就通过不了。看改系统中断向量表的做法是危险的,或者说我水平比较菜,干不了那么高深的活儿。下面是第二种解决方案的主调过程代码:
;;;;;;;;;;;;;;;;;;;;;;;; Entry Point ;;;;;;;;;;;;;;;;;;;;;;;;
start:  push ds
        xor  ax, ax
        push ax


loop1:  ; refresh display before each loop
        call get_tm
        call showtm
        ; loop 40000^2 times, about 1 sec
        mov  cx, 40000
loop2:  mov  dx, 40000
loop3:  sub  dx, 1
        jnz  loop3
        sub  cx, 1
        jnz  loop2
        sub  bx, 1
        mov  ah, 0Bh
        int  21h
        test al, al
        je   loop1
        ret


编程过程中主要参考了 王元珍等《80X86汇编语言程序设计》,以及网上一些代码,在此表示感谢。


[龙第九子 2009-02-27]


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值