【问题标题】:Real mode Interrupt handling routine not working as expected实模式中断处理例程未按预期工作
【发布时间】:2015-12-28 07:12:15
【问题描述】:

我设法通过执行远跳转到0x0090:0x0000 的引导加载程序将一个小内核加载到内存中。当我从那里打印一个字符来测试它并且它正常工作时,内核已成功加载。

我想将中断0x08->0x0F0x70->0x77 重新映射到中断0x20->0x2F,因此异常/保留中断不会重叠。到目前为止,我只处理键盘按下并尝试将其打印到屏幕上。
我重复了很多次,出于某种原因,我只是不知道为什么,但是当我按下一个键时没有任何反应.
键盘映射只是一个扫描码到其受尊重的 ASCII 值的数组。

如果这有任何帮助:我测试了运行一个循环并打印一个字符然后HLTing,一旦打印了 2 个字符,它就会挂起。我确保启用中断。
(我正在为引导加载程序加载 4 个扇区(从扇区 2 到扇区 5),这就是我填充它以使其大小为 2048 字节的原因)。

顺便说一句,我不必在我的中断程序中使用CLI,因为它已经为我完成了,对吧?我记得读过这篇文章,但我不太确定。

BITS 16
ORG 0x0000

; Setup Segments ;
cli
cld
mov ax, cs
mov ds, ax              ; this program was far-jumped to (0x0090:0x0000) so ds = cs = 0x0090
mov ax, VIDEO_ORIGIN
mov es, ax

; Remap PIC Interrupt Vector Offsets to 0x20 -> 0x35 ;
remapInterrupts:
    ; Send Initialization Command (expecting ICW4)
    mov al, 0x11
    out 0x20, al
    out 0xA0, al

    ; Remap Vector Offsets (ICW2)
    mov al, 0x20        ; Master IRQ lines mapped to 0x20 -> 0x27
    out 0x21, al
    mov al, 0x28        ; Slave IRQ lines mapped to 0x28 -> 0x2F
    out 0xA1, al

    ; Set Cascade Lines between Master and Slave PICs (ICW3)
    mov al, 0x04        ; 00000100 (line 2)
    out 0x21, al
    mov al, 0x02        ; 00000010 (line 2 in binary, cascade identity)
    out 0xA1, al

    ; Set 80x86 Mode (ICW4)
    mov al, 0x01
    out 0x21, al
    out 0xA1, al

    ; Set Masks
    mov al, 0xFD        ; 11111101 (keyboard)
    out 0x21, al
    mov al, 0xFF        ; 11111111
    out 0xA1, al

setInterrupts:
    push ds
    mov ax, 0x0000
    mov ds, ax
    mov [ds:0x84], word interrupt21     ; 0x84 = 0x21 * 4
    mov [ds:0x86], cs
    pop ds
    jmp start

    interrupt20:                ; Programmable Interval Timer
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0x20, al
        pop ax
        iret
    interrupt21:                ; Keyboard
        push ax
        push bx
        in al, 0x60
        test al, 0x80           ; high-bit set = keyup = don't print
        jnz .finish
        movzx bx, al
        mov al, [keymap + bx]
        mov ah, 0x07
        stosw

        .finish:
        mov al, 0x20
        out 0x20, al

        pop bx
        pop ax
        iret
    interrupt22:                ; Slave Cascade
    interrupt23:                ; COM2 / COM4
    interrupt24:                ; COM1 / COM3
    interrupt25:                ; LPT2
    interrupt26:                ; Floppy controller
    interrupt27:                ; LPT1
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0x20, al
        pop ax
        iret
    interrupt28:                ; RTC
    interrupt29:                ; Unassigned
    interrupt2A:                ; Unassigned
    interrupt2B:                ; Unassigned
    interrupt2C:                ; Mouse Controller
    interrupt2D:                ; Math Coprocessor
    interrupt2E:                ; Hard Disk Controller 1
    interrupt2F:                ; Hard Disk Controller 2
        ; NOT SUPPORTED, place holder
        push ax
        mov al, 0x20
        out 0xA0, al
        out 0x20, al
        pop ax
        iret

start:
    sti
    xor di, di
    jmp $

; --- CONSTANTS --- ;
VIDEO_ORIGIN EQU 0xB800

; --- DATA --- ;
drive db 0
keymap:
    db 00h, 1Bh, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 08h, 09h
    db 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', 00h, 00h
    db 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", '`', 00h, '\'
    db 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 00h, 00h, 00h, ' ', 00h,
    db 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h,
    db '-', 00h, 00h, 00h, '+', 00h, 00h, 00h, 00h, 00h

times 2048 - ($ - $$) db 0

【问题讨论】:

  • 在某些模拟器上(不确定是哪些模拟器),如果您在紧密循环中执行jmp $,则虚拟化视频显示可能无法更新。而不是 jmp $ 使用中断 enabled 执行此操作:jmploop: hltjmp jmploophlt 指令将暂停直到下一个中​​断发生,此时执行将在 HLT 之后继续执行,这是返回并执行 HLT 的 jmp i> 再次。这通常将允许更新虚拟视频内存。这在真实硬件上不是问题,但在某些虚拟化(模拟)环境中可能是个问题
  • 我强烈建议您使用 Bochs 进行测试。您似乎正在处理 16 位实模式代码,并且内置调试器的 Bochs 确实理解 16 位指令并且理解段:偏移量对。您可以在中断例程上设置断点,然后单步执行代码以查看会发生什么。
  • 哦,哇,我肯定会使用 Bochs,实际上能够调试会有很大帮助。是的,我会解决那个 movzx 问题,并确保在调用时不要依赖像 di 这样的寄存器。当我找到解决方案时,我会回来。

标签: assembly x86 virtualbox nasm interrupt-handling


【解决方案1】:

必须开发实模式中断例程,就好像除了 CS 寄存器是已知的(并且中断标志被清除)。 CS:IP 在我们获得硬件中断时通过中断向量设置。 CS 将是我们写入中断向量表的段。在您的情况下,它是 0x0090,因为您这样做(使用 DS=0x0000)来更新中断向量表:

mov [ds:0x86], cs

由于在调用中断处理程序时我们不能依赖 DS 是我们想要的,我们可以将 DS 压入堆栈,复制 CS 到 DS 并通过 DS 访问我们的内存变量,然后恢复 DS。或者,我们可以修改内存操作数,使它们显式使用 CS 寄存器。我们可以将中断处理程序修改为如下所示:

interrupt21:                ; Keyboard
    push ax
    push bx
    push di                 ; Save DI
    push es                 ; Save ES

    mov  ax, VIDEO_ORIGIN
    mov  es, ax             ; Set ES to video memory segment
    mov  di, [cs:videopos]  ; Get last videopos into DI
    in al, 0x60
    test al, 0x80           ; high-bit set = keyup = don't print
    jnz .finish
    xor bh, bh              ; set high byte of BX to zero
    mov bl, al              ; low byte of BX is scancode
    mov al, [cs:keymap + bx]; Reference keymap via CS segment (not DS)
    mov ah, 0x07
    cld                     ; Set the direction flag forward
    stosw
    mov [cs:videopos], di   ; Save current video position

.finish:
    mov al, 0x20
    out 0x20, al

    pop es                 ; Restore ES
    pop di                 ; Restore DI
    pop bx
    pop ax
    iret

我已经记录了我添加的行。但重要的是:

  • 我们不能保证 ES 会是我们想要的,所以我们需要将其显式设置到视频内存段。
  • 寄存器必须恢复到中断前的相同状态。我们将修改 ES 寄存器和 DI 寄存器,因此我们应该将它们保存在堆栈中(并在最后恢复它们)。
  • 我们不能依赖 DI 实际上是我们期望的值,所以我们必须在中断调用之间保存它的值,以便我们可以正确地前进到屏幕上的下一个单元格进行写入。
  • 已重写内存操作数以使用 CS 寄存器而不是 DS 寄存器。进行段覆盖可以避免将 CS 复制到 DS 并在我们的中断处理程序中保存/恢复 DS
  • 我们不能依赖方向标志是我们想要的。由于您在中断处理程序中使用了 STOSW,我们希望确保它被 CLD 清除,以便向前推进 DI
  • 我们可以简单地将BX寄存器的上部清零,而不是使用movzxmovzx 仅在 386 处理器上可用。如果您使用 386+,则可以保留该指令,但如果您打算针对 8086/8088/80188/80286,则无法使用它。

将您的启动程序修改为:

start:
    mov word [videopos], 0x0000 ; Initialize starting video position
    sti
.progloop:
    hlt
    jmp .progloop

如果您使用jmp $ 进行紧密循环,某些模拟器并不总是进行屏幕刷新。更好的方法是使用 HLT 指令。当中断发生时,CPU 将停止处理器,直到下一个中​​断发生。当确实发生时,它将由中断处理程序提供服务,并最终落入下一条指令。在这种情况下,我们跳回并再次执行 HLT 等待下一次中断。

由于我们添加了一个新变量来跟踪我们正在写入的屏幕单元格,因此我们需要将 videopos 添加到您的 .data 段:

; --- DATA --- ;
drive db 0
videopos dw 0

这些更改似乎确实适用于 BochsQEMUVirtualBox。如果这对您不起作用,那么您可能没有将第二阶段(价值 4 个扇区)正确加载到 0x0090:0x0000 中?由于我看不到您的第一阶段代码,因此我无法确定。

【讨论】:

  • @Matthew:我在我的 Bochs 上看不到这种行为。您是按原样使用我提供的代码,还是使用修改后的代码?
  • @Matthew 在 Bochs 中,如果您连续按住一个字符 5-10 秒(即使使用我的代码),它可能会告诉您缓冲区已满,然后短暂停止将使其赶上并您可以继续输入而不会出现新的警告。基本上,由于您的中断处理程序中的所有错误,他们可能会将系统拖到中断处理程序不再工作的地步。这就是做错事的问题——它们可能以不同寻常的方式表现出来。
  • @Matthew:我有机会查看了对您不起作用的代码。我相信我知道为什么它会以这种方式失败。您应该注意的一件事是您在光标更新过程中执行此操作 mov ax, di mov bl, 2 div bl mov bx, ax。您应该注意它每次都停止在同一点打印字符。碰巧是当您按第 256 个字符时。 DI=512(十进制)。您将其复制到 AXdiv blAX 并将其除以 BLBUT 如果结果为除法不适合 8 位值。
  • @Matthew 512/2=256 。 256(商)不能放在寄存器 AL 中,因为它大于 255(8 位寄存器只能在 0-255 之间保存)。接下来会发生一个除法异常(实际上是除法溢出),然后您似乎没有处理它的处理程序,所以 Bochs 进入了拉拉地。
  • 是的,我实际上在几分钟前将其更改为那个。还要感谢您解释为什么会发疯。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-11
  • 2019-09-17
  • 2013-05-08
  • 1970-01-01
  • 1970-01-01
  • 2012-06-18
  • 1970-01-01
相关资源
最近更新 更多