用 GNU(GCC 和 GAS)替换 DOS 中的定时器中断处理程序
正如标题所示,我正在尝试用我自己的处理程序替换 DOS 中定时器中断的现有处理程序。 在广泛搜索各种解决方案之后,我找到了一些完全可以做到这一点的汇编代码,我什至成功地编译和测试了它,并发现它可以工作。
现在的问题是我找到的代码(见下文)是为 TASM 编写的,我希望将它与我正在编写的一些 C 代码一起使用,我用 GCC 编译这些代码。
我尝试将代码转换为 GAS(GNU 汇编器)语法,但我似乎无法让它工作(在我的多次尝试中,我大多经历过一种或另一种崩溃)。
如果有人能启发我一个解决方案(无论是 GAS 可以编译的汇编代码的工作版本,还是用 C 语言完成整个事情的方法——“中断”关键字不起作用,我将非常感激) “attribute ((interrupt))”之类的东西也没有——甚至是在 TASM 和 GCC 之间架起桥梁的一种方式。
我还应该提到,我使用的 DOS 系统实际上是一个 OracleVM VirtualBox Manager,运行一个安装了 FreeDOS 的虚拟机,并且我使用的 C 编译器是 DJGPP 开发环境提供的 GCC 。
这是我的工作 TASM 代码(取自 http://www.programmersheaven.com/mb/x86_asm/276128/276185/re-redefining-the-timer-interrupt-handler/):
_stack SEGMENT STACK
db 32 DUP ('STACK ')
_stack ENDS
_code SEGMENT PARA 'CODE'
ASSUME CS:_code, SS:_stack
Lstart LABEL NEAR
JMP Linstall
;+---------------------------------------------
;| My New 1Ch INT
;| Print 'random' chars to the first video line
new_Int PROC FAR
DEC BYTE PTR CS:Counter
CLD
PUSH AX
MOV AX, 0B800h
MOV ES,AX ; ES = b800h
MOV DI,000h ; DI = 0000h
MOV AH,CS:Counter ; set foreground and background color
MOV AL,CS:Counter ; set char
MOV CX,80
REP STOSW ; From AX to ES:DI
POP AX
STI
IRET
new_Int ENDP
Counter DB 0Fh
;+-----------------------------------------
;| Store old INT and Install the new one
;|
Linstall LABEL NEAR
old_INT DD 00000000h
MOV AL,01Ch ;+-
MOV AH,35h ;| Save old_INT
INT 21h ;|
MOV WORD PTR [old_INT],BX
MOV WORD PTR [old_INT][2],ES
CLI ;+-
PUSH CS ;| Install
POP DS ;|
LEA DX,new_INT
MOV AL,1Ch
MOV AH,25h
INT 21h
MOV AH,0 ;+-
INT 16H ;| Wait for a keypress
;+-----------------------------------------
;| Disinstall and exit
CLI
PUSH DS
LDS DX,CS:[old_INT] ;+-
MOV AL,1Ch ;| Disinstall int
MOV AH,25h ;|
INT 21h ;|
POP DS
STI
MOV AL,0 ;+-
MOV AH,4Ch ;| Exit
INT 21h ;|
_code ENDS
END Lstart
它在我的机器上完全可以工作。我启动程序,看到控制台的整个第一行都被不断变化的彩色字符所取代。
这是我尝试将上述代码转换为 GAS 语法:
.file "ttv2.s"
# Define a variable for "randomizing" characters and colors
.globl _MyVar
.section .bss
_MyVar:
.space 1
.section .text
# Define a variable for storing the address of the current ISR
.globl _OldInt
.section .bss
.p2align 2
_OldInt:
.space 4
.section .text
# Program entry point
.text
.globl start
start:
jmp _Install
# This is the new Interrupt Service Routine that is going to be installed
.globl _NewInt
_NewInt:
movb _MyVar, %al
decb %al # Decrement our variable
movb %al, _MyVar
cld
pushw %ax
movw $0xB800, %ax
movw %ax, %es # ES = 0xB800
movw $0, %di # DI = 0
movb _MyVar, %ah # Set the foreground and background colors
movb _MyVar, %al # Set the charater to be displayed
movw $80, %cx # The screen is 80 characters wide
rep stosw # Start copying from AX to AS:DI
popw %ax
sti
iret
.globl _Install
_Install:
# Save old ISR address
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x35, %ah # 0x35 is the code for getting the current ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
movw %es, %dx #
shll $16, %edx # Save the address of the
movw %bx, %dx # old interrupt handler
movl %edx, _OldInt #
# Install the new ISR
cli
pushw %cs
popw %ds
lea _NewInt, %dx # Set the address of the ISR we're installing
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x25, %ah # 0x25 is the code for setting a new ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
# Wait for a key press
movl $0, %eax
int $0x16
.globl _Uninstall
_Uninstall:
cli
pushw %ds
lds %cs:_OldInt, %dx # Install the address of the old ISR
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x25, %ah # 0x25 is the code for setting a new ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
popw %ds
sti
.globl _End
_End:
# Exit
movb $0, %al
movb $0x4C, %ah # 0x4C is the code for program exit in DOS
int $0x21
.ident "GCC: (GNU) 4.5.2"
我使用以下命令编译我的文件(称为“ttv2.s”):
as -o ttv2.o ttv2.s
ld -o ttv2.exe ttv2.o
当我运行生成的 EXE 文件时(在汇编和链接期间没有警告或错误) ),程序崩溃并出现错误“Exception 0D in Ring 0”(以及大量寄存器值)。 然而,TASM 版本可以顺利运行! 所以我猜测我转换代码的方式或者我构建最终 EXE 的方式有问题。或者两者兼而有之。
一些附加信息,是否有任何帮助:
- 如果我删除安装命令(
int $0x21
),则不会崩溃,并且程序会等待我按一个键然后退出。 - 如果我保留安装命令,但删除等待键命令(
int $0x16
),程序会立即退出,并且没有崩溃。 - 如果我保留安装命令,并用活动延迟循环(40亿次迭代的简单循环)替换等待键命令,程序也会崩溃当等待键命令到位时,它会这样做,但是是在几秒钟之后,而不是立即。
- 在这两种崩溃的情况下(通过按键或延迟),即使我只删除两个安装命令之一,程序也会崩溃。
预先感谢您提供的所有帮助,并对冗长的帖子表示歉意......
As the title suggests, I'm trying to replace the existing handler for the Timer interrupt in DOS with one of my own.
After searching far and wide for a variety of solutions, I found some Assembly code which does exactly that, and I have even managed to compile and test it, and saw that it works.
The problem now is that the code I found (see further down) is written for TASM, and I wish to use it with some C code that I'm writing, which I compile with GCC.
I've tried to convert the code into GAS (GNU Assembler) syntax, but I can't seem to get it to work (I mostly experienced crashes of one kind or another during my numerous attempts).
I would very much appreciate it if anyone could enlighten me with a solution (be it a working version of the assembly code that GAS can compile, a way to do the entire thing in C -- the "interrupt" keyword doesn't work, and neither does "attribute ((interrupt))" and the like -- or even a way to bridge between TASM and GCC).
I should also probably mention that the DOS system I'm using is actually an OracleVM VirtualBox Manager running a virtual machine with FreeDOS installed on it, and that the compiler I'm using for C is the GCC that is provided with the DJGPP development environment.
This is the working TASM code I have (taken from http://www.programmersheaven.com/mb/x86_asm/276128/276185/re-redefining-the-timer-interrupt-handler/):
_stack SEGMENT STACK
db 32 DUP ('STACK ')
_stack ENDS
_code SEGMENT PARA 'CODE'
ASSUME CS:_code, SS:_stack
Lstart LABEL NEAR
JMP Linstall
;+---------------------------------------------
;| My New 1Ch INT
;| Print 'random' chars to the first video line
new_Int PROC FAR
DEC BYTE PTR CS:Counter
CLD
PUSH AX
MOV AX, 0B800h
MOV ES,AX ; ES = b800h
MOV DI,000h ; DI = 0000h
MOV AH,CS:Counter ; set foreground and background color
MOV AL,CS:Counter ; set char
MOV CX,80
REP STOSW ; From AX to ES:DI
POP AX
STI
IRET
new_Int ENDP
Counter DB 0Fh
;+-----------------------------------------
;| Store old INT and Install the new one
;|
Linstall LABEL NEAR
old_INT DD 00000000h
MOV AL,01Ch ;+-
MOV AH,35h ;| Save old_INT
INT 21h ;|
MOV WORD PTR [old_INT],BX
MOV WORD PTR [old_INT][2],ES
CLI ;+-
PUSH CS ;| Install
POP DS ;|
LEA DX,new_INT
MOV AL,1Ch
MOV AH,25h
INT 21h
MOV AH,0 ;+-
INT 16H ;| Wait for a keypress
;+-----------------------------------------
;| Disinstall and exit
CLI
PUSH DS
LDS DX,CS:[old_INT] ;+-
MOV AL,1Ch ;| Disinstall int
MOV AH,25h ;|
INT 21h ;|
POP DS
STI
MOV AL,0 ;+-
MOV AH,4Ch ;| Exit
INT 21h ;|
_code ENDS
END Lstart
It fully works on my machine. I start the program and see the entire first line of the console replaced by colorful characters that change all the time.
And this is my attempt to convert the above code into GAS syntax:
.file "ttv2.s"
# Define a variable for "randomizing" characters and colors
.globl _MyVar
.section .bss
_MyVar:
.space 1
.section .text
# Define a variable for storing the address of the current ISR
.globl _OldInt
.section .bss
.p2align 2
_OldInt:
.space 4
.section .text
# Program entry point
.text
.globl start
start:
jmp _Install
# This is the new Interrupt Service Routine that is going to be installed
.globl _NewInt
_NewInt:
movb _MyVar, %al
decb %al # Decrement our variable
movb %al, _MyVar
cld
pushw %ax
movw $0xB800, %ax
movw %ax, %es # ES = 0xB800
movw $0, %di # DI = 0
movb _MyVar, %ah # Set the foreground and background colors
movb _MyVar, %al # Set the charater to be displayed
movw $80, %cx # The screen is 80 characters wide
rep stosw # Start copying from AX to AS:DI
popw %ax
sti
iret
.globl _Install
_Install:
# Save old ISR address
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x35, %ah # 0x35 is the code for getting the current ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
movw %es, %dx #
shll $16, %edx # Save the address of the
movw %bx, %dx # old interrupt handler
movl %edx, _OldInt #
# Install the new ISR
cli
pushw %cs
popw %ds
lea _NewInt, %dx # Set the address of the ISR we're installing
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x25, %ah # 0x25 is the code for setting a new ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
# Wait for a key press
movl $0, %eax
int $0x16
.globl _Uninstall
_Uninstall:
cli
pushw %ds
lds %cs:_OldInt, %dx # Install the address of the old ISR
movb $0x1C, %al # Set the code for the Timer interrupt
movb $0x25, %ah # 0x25 is the code for setting a new ISR
int $0x21 # 0x21 is the interrupt fot s/getting ISRs
popw %ds
sti
.globl _End
_End:
# Exit
movb $0, %al
movb $0x4C, %ah # 0x4C is the code for program exit in DOS
int $0x21
.ident "GCC: (GNU) 4.5.2"
I compile my file (called "ttv2.s") with the following commands:
as -o ttv2.o ttv2.s
ld -o ttv2.exe ttv2.o
When I run the resulting EXE file (there are no warnings or errors during the assembly and linkage), the program crashes with the error "Exception 0D in ring 0" (and lots of register values).
The TASM version, however, works without a hitch!
So I'm guessing that there is something wrong either with the way I converted the code, or with the way I'm building the final EXE. Or both.
A bit of additional information, should it help in any way:
- If I remove the installation commands (the
int $0x21
), there is no crash, and the program waits for me to hit a key and then exits. - If I keep the installation commands, but remove the wait-for-key command (the
int $0x16
), the program exits immediately, and there is no crash. - If I keep the installation commands, and replace the wait-for-key command with an active delay loop (a simple loop of 4 billion iterations), the program crashes the same way it did when the wait-for-key command was in place, but after a couple of seconds, rather than immediately.
- In both cases with the crash (with the key press or the delay), the program crashes even if I remove just one of the two installation commands.
Thanks in advance for any and all assistance, and sorry for the lengthy post...
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可能需要指定
.code16
以便它构建 16 位实模式的应用程序。You probably need to specify
.code16
so it builds the application for 16-bit real mode.事实上,您收到有关环的错误意味着您由于某种原因未处于 16 位实模式(如 DOS 运行),而是处于某种形式的保护模式。因此,请确保 1) 您正在将汇编命令编译为 16 位实模式(即,二进制机器代码是 16 位操作码,而不是 32 位操作码),并且 2) 您正在 16 位运行当您尝试运行 EXE 时,位实模式设置。
其次,请注意,在 TASM 版本中,他们已将
Counter
变量放置在代码段中,并通过当前代码段的偏移量来访问Counter
。另一方面,您已将计数器变量_MyVar
放置在 BSS 部分中。根据链接器链接二进制可执行文件的方式,该变量可能无法从中断访问...例如,当中断运行时,可能无法在当前数据段的 64Kb 窗口内访问该变量。因此,我会镜像他们在 TASM 版本中所做的操作,并将计数器变量放在代码段中,并从代码段访问它The fact that you're getting an error about rings means that you are for some reason not in 16-bit real mode (like DOS would be run), but rather are in some form of protected mode. So make sure that 1) you are compiling to 16-bit real mode for your assembly commands (i.e., the binary machine code is 16-bit opcodes, not 32-bit opcodes), and 2) that you are running in a 16-bit real mode setting when you attempt to run your EXE.
Secondly, note that in the TASM version, they have placed the
Counter
variable in the code segment, and are accessing theCounter
via an offset from the current code-segment. You on the other-hand have placed your counter variable_MyVar
in the BSS section. Depending on how the linker links your binary executable, that variable may not be accessible from your interrupt ... for instance, it may not be accessible within the 64Kb window of the current data segment when the interrupt is running. Therefore I would mirror what they did in the TASM version, and place your counter variable in the code-segment, and access it from the code-segment