用 GNU(GCC 和 GAS)替换 DOS 中的定时器中断处理程序

发布于 2024-12-04 04:22:45 字数 6269 浏览 0 评论 0原文

正如标题所示,我正在尝试用我自己的处理程序替换 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

划一舟意中人 2024-12-11 04:22:45

您可能需要指定 .code16 以便它构建 16 位实模式的应用程序。

You probably need to specify .code16 so it builds the application for 16-bit real mode.

七颜 2024-12-11 04:22:45

事实上,您收到有关环的错误意味着您由于某种原因未处于 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 the Counter 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文