You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DOSBOX下MASM编写TSR程序键盘无响应问题求助及开发建议

问题解决与DOS TSR编程建议

代码错误分析与修复

你的TSR程序导致DOS键盘无响应,核心是以下几个致命错误:

  1. 非法寄存器压栈:中断处理程序里的push si0是笔误,应为push si,非法寄存器会破坏栈结构,导致中断返回异常。
  2. 数组索引逻辑错误:mov si, x中x未定义,正确做法是将8位的CurrentRate扩展为16位索引(数组索引需要16位寄存器)。
  3. 中断处理流程错误:调用旧中断的方式导致栈失衡。你用pushf + push旧段偏移 + retf直接跳转到旧中断,旧中断执行iret后直接返回被中断的程序,当前中断处理程序中压栈的寄存器从未被弹出,栈持续失衡最终导致系统崩溃。
  4. 常驻数据存储错误:INT 27h仅保留CS段的内存,你将常驻数据放在DATA段,程序退出后DOS会回收DATA段,中断处理程序访问这些数据时会读取到垃圾值。
  5. INT 27h参数错误:dx需指向常驻部分的末尾,你设置为offset start会导致仅极小部分代码被保留,后续执行必然出错。

修正后的代码

.MODEL small
.STACK 100h

.CODE
; 把所有需要常驻的数据移到CODE段
old_handler_offset DW 0
old_handler_segment DW 0

TickCounter     db 0          
CurrentRate     db 0          
RepeatRates     db 32 dup (?)

InitRepeatRates proc
    mov cx, 32
    mov si, offset RepeatRates
    mov al, 1Fh            ; 最慢重复率对应值
FillLoop:
    mov [si], al
    dec al
    inc si
    loop FillLoop
    ret
InitRepeatRates endp

int_handler proc far
    push ax
    push bx
    push dx
    push si
    push es
    push di

    ; 每秒更新一次(DOS时钟中断18次/秒)
    inc cs:TickCounter
    cmp cs:TickCounter, 18
    jl skip_update

    mov cs:TickCounter, 0

    ; 获取当前要设置的重复率索引
    movzx si, cs:CurrentRate  ; 将8位CurrentRate扩展为16位索引
    ; 更新索引,循环0-31
    inc cs:CurrentRate
    cmp cs:CurrentRate, 32
    jb no_reset
    mov cs:CurrentRate, 0
no_reset:

    ; 发送键盘重复率设置命令
wait_input_ready:
    in al, 64h
    test al, 02h
    jnz wait_input_ready

    mov al, 0F3h       ; 键盘设置重复率命令
    out 60h, al

wait_data_ready:
    in al, 64h
    test al, 02h
    jnz wait_data_ready

    mov al, cs:RepeatRates[si]
    out 60h, al

skip_update:
    ; 调用旧的时钟中断处理程序
    pushf
    call dword ptr cs:old_handler_offset  ; 直接远调用旧处理程序

    ; 恢复寄存器
    pop di
    pop es
    pop si
    pop dx
    pop bx
    pop ax
    iret
int_handler endp

; 标记常驻部分的起始
start:
    mov ax, @CODE
    mov ds, ax    ; 因为数据现在在CODE段,所以DS指向CS
    
    call InitRepeatRates

    ; 获取旧的INT 1Ch中断向量
    xor ax, ax
    mov es, ax

    mov ax, es:[1Ch*4]
    mov old_handler_offset, ax
    mov ax, es:[1Ch*4+2]
    mov old_handler_segment, ax

    ; 设置新的中断向量
    cli
    mov word ptr es:[1Ch*4], offset int_handler
    mov ax, seg int_handler
    mov word ptr es:[1Ch*4+2], ax
    sti

    ; INT 27h:dx指向常驻部分的下一个字节,确保所有代码数据都被保留
    mov dx, offset end_resident
    int 27h               

; 常驻部分结束标签
end_resident:
END start

DOS TSR编程通用建议

  • 数据存储:将常驻数据放入CS段:INT 27h和INT 31h(DOS 4+)默认仅保留CS段内存,DATA段会被DOS回收。所有中断处理程序需要访问的数据必须放在CS段,或通过INT 31h明确申请常驻内存块。
  • 中断处理流程必须严谨:
    • 进入中断后立即保存所有用到的寄存器(包括段寄存器),退出前恢复。
    • 调用旧中断处理程序时,必须通过pushf + call far的方式,确保旧中断执行完后回到当前程序,再恢复寄存器并执行iret,绝对不能用retf直接跳转(会导致栈失衡)。
    • 禁止在中断处理程序中调用可能触发重入的DOS功能(如INT 21h的大部分子功能),如需调用必须先关闭中断或使用重入安全的子功能。
  • 正确设置中断向量:修改中断向量前必须执行cli关闭中断,修改完成后sti开启中断,防止中断发生在向量修改过程中导致系统崩溃。
  • 常驻范围精确控制:使用INT 27h时,dx必须指向所有需要常驻的代码和数据的下一个字节,确保没有遗漏也没有多余的内容被保留。如果使用INT 31h(推荐),可以更灵活地申请和释放常驻内存。
  • 测试与卸载:TSR程序一旦加载很难直接卸载,测试时尽量在虚拟机(如DOSBox)中进行,避免破坏物理DOS环境。如需支持卸载,需在常驻代码中保存原始中断向量和内存块信息,提供卸载入口。
  • 避免硬件访问冲突:直接访问硬件(如键盘端口60h/64h)时,必须严格按照硬件时序操作(如等待输入输出缓冲区就绪),否则会导致硬件状态混乱。

内容的提问来源于stack exchange,提问作者Youvii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:27:08