如何在 DOS 程序集中正确挂接中断 28h,并恢复它?
How do I properly hook Interrupt 28h in assembly for DOS, and restore it?
我正在尝试将中断 28h 的处理程序设置为我自己的例程,恢复所有涉及的寄存器和标志,并恢复原始中断处理程序。
我在 VirtualBox 的 DOSBox 和 MS-DOS 6.22 下使用 NASM 汇编器。
我考虑过调试,但在 TSR 程序上这样做听起来不可能。我试过将Data Segment推到Code Segment上,并保存原来的Data Segment以备后用,但恢复Data Segment后机器好像挂了。
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
返回原来的中断向量地址,并将新的中断向量地址设置为"resstart"后,程序应该终止并驻留。此后,由于 DOS 无事可做,中断 28h 将自动触发,这又会 运行 我的中断处理程序。
中断处理程序将视频模式设置为13h,尝试用淡蓝色填充整个屏幕,恢复原来的中断28h处理程序,恢复所有涉及的寄存器和标志,returns到DOS。执行这个程序没有结果,系统甚至没有挂起。虽然 运行设置视频模式 13h 并单独用蓝色填充整个屏幕的部分,它工作得很好。
您的程序存在多个问题:
问题 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
段来存储数据。这在"real-mode"操作系统(例如MS-DOS)中是可能的,但在"protected-mode"操作系统(例如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 21h
内部调用 int 28h
)也不是一个好主意。
但是,函数 25h
只会将 4 个字节的数据写入中断向量 table(使用 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],dx
和mov [0A2h],ax
两条指令之间不会发生硬件中断。
如果您可以确保 int 28h
不是从硬件中断调用的,则不需要执行此操作。
iret
指令将自动恢复中断的旧状态(启用或禁用)。
问题3
从 int 28h
中断调用复杂函数(例如 int 10h
)似乎也不是最好的主意。
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当然不能占。这将需要 16 个段落,因为 PSP 有 256 个字节。
分配给此 .COM 程序的内存从 PSP 开始,因此 DX
计数也必须从那里开始。
mov dx, resend
shr dx, 4
inc dx
mov ax, 3100h ; DOS.TerminateAndStayResident
int 21h
提示 如果将此 resend 标签与段落边界对齐,则不再需要 inc dx
。
如果你现在的代码在像 virtualbox 这样的模拟器中部分工作,那是因为你的程序以前占用的内存还没有被覆盖。程序 shell。与 DOS 不同,仿真器可以从很远的距离执行命令解释器。
the screen does fill with blue using virtualbox, though the system hangs
如果有人在我写东西的时候关掉灯,我也会挂!这就是您的处理程序在突然更改视频模式时所做的...
对于TSR程序,我们通常会跳过需要常驻的部分,所以one-time设置占用的space可以被系统回收
您可以使用的另一个技巧是将旧中断向量的偏移量和段直接写入将恢复向量的指令中。处理程序中的段寄存器不再有问题。
这是我重写的你的程序:
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, (256+Setup-Start+15)/16
mov ax, 3100h ; DOS.TerminateAndStayResident
int 21h
我正在尝试将中断 28h 的处理程序设置为我自己的例程,恢复所有涉及的寄存器和标志,并恢复原始中断处理程序。 我在 VirtualBox 的 DOSBox 和 MS-DOS 6.22 下使用 NASM 汇编器。
我考虑过调试,但在 TSR 程序上这样做听起来不可能。我试过将Data Segment推到Code Segment上,并保存原来的Data Segment以备后用,但恢复Data Segment后机器好像挂了。
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
返回原来的中断向量地址,并将新的中断向量地址设置为"resstart"后,程序应该终止并驻留。此后,由于 DOS 无事可做,中断 28h 将自动触发,这又会 运行 我的中断处理程序。
中断处理程序将视频模式设置为13h,尝试用淡蓝色填充整个屏幕,恢复原来的中断28h处理程序,恢复所有涉及的寄存器和标志,returns到DOS。执行这个程序没有结果,系统甚至没有挂起。虽然 运行设置视频模式 13h 并单独用蓝色填充整个屏幕的部分,它工作得很好。
您的程序存在多个问题:
问题 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
段来存储数据。这在"real-mode"操作系统(例如MS-DOS)中是可能的,但在"protected-mode"操作系统(例如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 21h
内部调用 int 28h
)也不是一个好主意。
但是,函数 25h
只会将 4 个字节的数据写入中断向量 table(使用 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],dx
和mov [0A2h],ax
两条指令之间不会发生硬件中断。
如果您可以确保 int 28h
不是从硬件中断调用的,则不需要执行此操作。
iret
指令将自动恢复中断的旧状态(启用或禁用)。
问题3
从 int 28h
中断调用复杂函数(例如 int 10h
)似乎也不是最好的主意。
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当然不能占。这将需要 16 个段落,因为 PSP 有 256 个字节。
分配给此 .COM 程序的内存从 PSP 开始,因此 DX
计数也必须从那里开始。
mov dx, resend
shr dx, 4
inc dx
mov ax, 3100h ; DOS.TerminateAndStayResident
int 21h
提示 如果将此 resend 标签与段落边界对齐,则不再需要 inc dx
。
如果你现在的代码在像 virtualbox 这样的模拟器中部分工作,那是因为你的程序以前占用的内存还没有被覆盖。程序 shell。与 DOS 不同,仿真器可以从很远的距离执行命令解释器。
the screen does fill with blue using virtualbox, though the system hangs
如果有人在我写东西的时候关掉灯,我也会挂!这就是您的处理程序在突然更改视频模式时所做的...
对于TSR程序,我们通常会跳过需要常驻的部分,所以one-time设置占用的space可以被系统回收
您可以使用的另一个技巧是将旧中断向量的偏移量和段直接写入将恢复向量的指令中。处理程序中的段寄存器不再有问题。
这是我重写的你的程序:
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, (256+Setup-Start+15)/16
mov ax, 3100h ; DOS.TerminateAndStayResident
int 21h