如何在DOS程序集中正确钩住Interrupt 28h并将其还原

时间:2019-06-01 02:05:14

标签: assembly dos interrupt x86-16

我试图将Interrupt 28h的处理程序设置为我自己的例程,恢复涉及的所有寄存器和标志,并恢复原始的Interrupt处理程序。

我已经考虑过调试,但是在TSR程序上进行调试听起来似乎是不可能的。我尝试将数据段推到代码段上,并保存原始数据段以供以后还原,但是即使还原了数据段后,它似乎仍挂起了计算机。

section .text ;Code Section
org 100h ;DOS Executable Start
mov ah,35h ;Get Interrupt Vector
mov al,28h ;Of Interrupt 28h
int 21h ;Call DOS Kernel
push cs ;Push Code Segment
pop ds ;Onto Data Segment
mov [oldseg],es ;Save Old Interrupt Vector Segment
mov [oldoff],bx ;Save Old Interrupt Vector Offset
mov ah,25h ;Set Interrupt Vector
mov dx,resstart ;To Resstart
int 21h ;Call DOS Kernel
mov dx,resend ;Set Data Offset to Resend
sub dx,resstart ;Subtract Resstart
shr dx,4h ;Shift Right 4 Bits for Paragraph
inc dx ;One Extra Paragraph for PSP
mov ah,31h ;Terminate and Stay Resident
xor al,al ;Return Code
int 21h ;Call DOS Kernel

resstart: ;Resident Code Start
push ax ;Save AX
push es ;Save ES
push di ;Save DI
push cx ;Save CX
push ds ;Save DS
push dx ;Save DX
mov ah,00h ;Set Video Mode
mov al,13h ;To Mode 13h
int 10h ;Call BIOS Video
mov ax,0A000h ;VGA Segment
mov es,ax ;Stored in ES
xor di,di ;VGA Offset in DI
mov cx,0FA00h ;Fill Entire Screen
mov al,09h ;With Light Blue Color
rep stosb ;Repeat Store AL at ES:DI
mov ah,25h ;Set Interrupt Vector
mov al,28h ;Of Interrupt 28h
mov ds,[oldseg] ;Restore Old Interrupt Vector Segment
mov dx,[oldoff] ;Restore Old Interrupt Vector Offset
int 21h ;Call DOS Kernel
pop dx ;Restore DX
pop ds ;Restore DS
pop cx ;Restore CX
pop di ;Restore DI
pop es ;Restore ES
pop ax ;Restore AX
iret ;Return and Restore Flags
resend: ;Resident Code End

section .data
oldseg dw 0 ;Old Interrupt Vector Segment
oldoff dw 0 ;Old Interrupt Vector Offset

在返回原始中断向量地址并将新的中断向量地址设置为“重新启动”之后,程序应终止并保持驻留状态。此后,由于DOS无需执行其他操作,Interrupt 28h将自动触发,这将反过来运行我的Interrupt处理程序。

Interrupt处理程序将视频模式设置为13h,尝试用浅蓝色填充整个屏幕,还原原始的Interrupt 28h处理程序,还原涉及的所有寄存器和标志,然后返回DOS。执行该程序不会产生任何结果,系统甚至无法挂起。在运行设置视频模式13h的部分并单独用蓝色填充整个屏幕的同时,它可以很好地工作。

2 个答案:

答案 0 :(得分:3)

mov dx,resend ;Set Data Offset to Resend
sub dx,resstart ;Subtract Resstart
shr dx,4h ;Shift Right 4 Bits for Paragraph
inc dx ;One Extra Paragraph for PSP

在此.COM程序中,您正在正确保存和设置中断向量。但是,您无法准确计算出DOS.TerminateAnd StayResident函数要保留的段落数量。

需要inc dx向上舍入到最接近的段落。当然不考虑PSP。由于PSP具有256个字节,因此需要16个段落。

分配给此.COM程序的内存从PSP开始,因此DX计数也必须从那里开始。

mov     dx, resend 
shr     dx, 4
inc     dx
mov     ax, 3100h   ; DOS.TerminateAndStayResident
int     21h

提示如果将此重新发送标签与段落边界对齐,则不再需要inc dx

如果您当前的代码在诸如virtualbox之类的仿真器中部分工作,那是因为以前由程序占用的内存尚未被例如程序外壳。与DOS不同,仿真器可以在远距离执行命令解释器。

  

尽管系统挂起,但使用virtualbox屏幕确实显示为蓝色

如果我在写东西的时候关掉灯,我也会挂死!这就是您的处理程序在突然更改视频模式时所要做的...


对于TSR程序,我们通常跳过要保留的部分,因此一次性设置所占用的空间可以由系统回收。

您可以使用的另一种技巧是直接在恢复中断向量的指令中写入旧中断向量的偏移量和段。处理程序中的段寄存器不再有问题。

这是我对您的程序的重写:

    org     100h
Start:
    jmp     Setup

MyInt28:
    push    ax
    push    es
    push    di
    push    cx
    push    ds
    push    dx
    mov     ax, 0013h   ; BIOS.SetVideoMode
    int     10h
    mov     ax, 0A000h
    mov     es, ax
    xor     di, di
    mov     cx, 64000/2
    mov     ax, 0909h
    cld
    rep stosw
PatchA:
    mov     ax, 0       ; Don't change this to 'xor ax,ax'
    mov     ds, ax
PatchB:
    mov     dx, 0       ; Don't change this to 'xor dx,dx'
    mov     ax, 2528h   ; DOS.SetInterruptVector
    int     21h
    pop     dx
    pop     ds
    pop     cx
    pop     di
    pop     es
    pop     ax 
    iret

Setup:                  ; Resident part ends here.
    mov     ax, 3528h   ; DOS.GetInterruptVector
    int     21h         ; -> ES:BX
    mov     [PatchA + 1], es
    mov     [PatchB + 1], bx
    mov     dx, MyInt28
    mov     ah, 25h     ; DOS.SetInterruptVector
    int     21h
    mov     dx, (Setup-Start+15)/16
    mov     ax, 3100h   ; DOS.TerminateAndStayResident
    int     21h

答案 1 :(得分:1)

您的程序中存在多个问题:

问题1

push cs ;Push Code Segment
pop ds ;Onto Data Segment
mov [oldseg],es ;Save Old Interrupt Vector Segment
mov [oldoff],bx ;Save Old Interrupt Vector Offset
...
mov ds,[oldseg] ;Restore Old Interrupt Vector Segment
mov dx,[oldoff] ;Restore Old Interrupt Vector Offset

这四个mov指令假定ds寄存器指向.data部分。

但是,在前两个mov指令的情况下,ds指向.text部分,而不是.data指向push cs部分}-pop ds序列。

对于.COM文件,.text.data节通常是相同的;但是在.EXE文件中,它们通常是不同的。

对于第三条mov指令,ds指向与程序相关的任何部分是非常不可能的。对于第四条指令,几乎是不可能的,因为第三条mov指令更改了ds寄存器。

一种解决方案是使用.text段存储数据。在“实模式”操作系统(例如MS-DOS)中,这是可能的,但在“保护模式”操作系统(例如Windows)中,这是不可能的:

将两条dw 0行(例如oldseg dw 0)放在section .data行之前。现在,数据存储的四个字节与您的代码位于同一部分。然后,您可以通过以下方式访问数据:

 push cs
 pop ds
 mov [oldseg],es ;We know that ds=cs, so no "cs:" is required here
 ...
 mov ds,cs:[oldseg] ;Restore Old Interrupt Vector Segment
 mov dx,cs:[oldoff] ;Restore Old Interrupt Vector Offset

cs:”将告诉CPU您访问的数据位于cs所指向的部分中; cs始终指向包含当前正在执行的代码的部分。这是.text部分。

请注意,正确的语法(行中字母“ cs:”的位置)因汇编程序而异:

 mov dx,cs:[oldoff]
 cs:mov dx,[oldoff]
 mov dx,[cs:oldoff]

也许您的汇编器使用了另一种语法。

问题2

mov ah,25h ;Set Interrupt Vector
mov al,28h ;Of Interrupt 28h
mov ds,[oldseg] ;Restore Old Interrupt Vector Segment
mov dx,[oldoff] ;Restore Old Interrupt Vector Offset
int 21h ;Call DOS Kernel

int 21h内部调用int 21h(并且从int 28h内部调用int 21h)也不是一个好主意。

但是,函数25h除了将4字节的数据写入中断向量表(而使用cli禁用中断)之外什么也不会做:

您可以直接通过将地址0:0A0h的偏移量和地址0:0A2h的段存储起来直接执行此操作:

mov ax,0      ;You might also use "xor ax,ax" or "sub ax,ax"
mov ds,ax     ;Now ds=0
mov ax,cs:[oldseg]
mov dx,cs:[oldoff]
cli           ;Disable the interrupts
mov [0A0h],dx ;Write dx to ds:0A0h which is 0:0A0h
mov [0A2h],ax ;Write ax to ds:0A2h which is 0:0A2h

cli可以确保两个指令mov [0A0h],dxmov [0A2h],ax之间不会发生硬件中断。

如果可以确保没有从硬件中断中调用int 28h,则不需要这样做。

iret指令将自动恢复中断的旧状态(启用或禁用)。

问题3

int 10h中断调用复杂函数(例如int 28h)似乎也不是最好的主意。