BIOS磁盘-将扇区读入内存(int 0x13,ah = 0x02)阻塞

5
我正在编写MBR,并使用QEMU进行测试。
当使用将扇区读入内存(int 0x13,ah = 0x02时,int指令似乎会阻止程序的执行,并导致程序挂起。我已经使用各种打印语句进行了测试,以确认是这个特定的指令在阻塞。
什么可能会导致中断阻塞?我认为这只能通过cli指令来完成,即使如此,它也不会阻止int指令。

为了更好地理解,这是在read_sectors_16中引导阻塞中断的代码:

        [bits 16]        
        [ORG 0x7C00]
        jmp 0x000:start_16      ; ensure cs == 0x0000

        reset_failure_str db 'error: boot disk reset', 13, 10, 0
        read_failure_str db 'error: boot disk read', 13, 10, 0
        boot_segaddr dw 0x7E00

        read_attempt_str db 'read attempt', 13, 10, 0
        end_read_attempt_str db 'end read attempt', 13, 10, 0

start_16:
        ;; Initialize segment registers
        mov ax, cs
        mov ds, ax
        mov es, ax

        jmp load_bootsector_2            


load_bootsector_2:              ; Read program from disk
        ;; dl set by BIOS to the drive number MBR was loaded from
        mov cx, 0x0002          ; cylinder 0, sector 2
        xor dh, dh              ; head 0
        mov al, 0x01            ; load 1 sector
        mov bx, boot_segaddr    ; destination - load right after the boot loader
        call read_sectors_16
        jnc .success
        mov si, read_failure_str
        call print_string_16        
        jmp halt                ; halt

        .success:        
        jmp boot_segaddr:0000   ; jump to program

这是带有阻塞中断的函数:

;;; read_sectors_16
;;;
;;; Read sectors from disk in memory using BIOS services
;;;
;;; input:      dl      = drive
;;;             ch      = cylinder[7:0]        
;;;             cl[7:6] = cylinder[9:8]
;;;             dh      = head
;;;             cl[5:0] = sector (1-63)
;;;             es:bx   -> destination
;;;             al      = number of sectors
;;;
;;; output:     cf (0 = success, 1 = failure)
read_sectors_16:
        pusha
        mov di, 0x02            ; set attempts (max attempts - 1)

        .attempt:
        mov ah, 0x02            ; read sectors into memory (int 0x13, ah = 0x02)
        int 0x13                ; TODO: this call is not returning!
        jnc .end                ; exit if read succeeded
        dec di                  ; record attempt
        test di, di
        jz .end                 ; end if no more attempts
        xor ah, ah              ; reset disk (int 0x13, ah = 0x00)
        int 0x13
        jnc .attempt            ; retry if reset succeeded, otherwise exit
        jmp .end

        .end:
        popa
        ret

2
你试过使用BOCHS而不是qemu吗?它有一个内置的调试器,可以让你检查寄存器和单步执行,甚至在MBR代码中也可以。几乎可以确定你的程序并没有真正卡在中断处理程序中(除非你传递了虚假参数),很可能只是存在一个bug。 - Peter Cordes
1
还有一件有趣的事情(与您的问题无关,但在某些环境中可能是一个问题)。mov ax,cs mov ds,ax mov es,ax。实际上,CS 可能没有一个零值。一些 BIOS 使用 CS=0x07C0:iP=0x0000 将控制传递给引导加载程序,这也是物理地址 0x07c00。由于您使用 ORG 0x7C00,因此您的代码假定 DSES 寄存器将为零。我会通过执行 xor ax,ax mov ds,ax mov es,ax 明确地将它们设置为零。因此,我们不是将 CS 复制到 DS 和 _ES_,而是将 DS=ES=0。 - Michael Petch
1
同时,设置自己的堆栈SS:SP可能是个好主意。原因在于你不知道BIOS设置了哪里,你可能会无意中在自己的代码中覆盖它(例如:读取扇区到内存时会覆盖堆栈)。如果你设置自己的堆栈,你就知道它的确切位置,并可以避免意外破坏它。 - Michael Petch
1个回答

6
你的代码中最引人注目的是你的段落。首先,你的代码将boot_segaddr定义为:
boot_segaddr dw 0x7E00

这将在内存中创建一个16位字,其值为0x7E00。然后您有以下两行:

mov bx, boot_segaddr
[snip]
jmp boot_segaddr:0000 

在这两种情况下,boot_segaddr 被用作立即数。您使用的是 boot_segaddr 的地址,而不是 boot_segaddr 指向的值。

我建议将 boot_segaddr dw 0x7E00 改为常量值(使用 EQU),并将其重命名为:

BOOT_OFFSET EQU 0x7E00

您可以将mov bx, boot_segaddr修改为:

mov bx, BOOT_OFFSET

这将使0x7E00移动到BX。对Int 13/AH=2的调用将读取从ES:BX = 0x0000:0x7E00开始的扇区,这正是您想要的。

下一个问题是如果我们重复使用相同的常数来进行FAR JMP,就像这样:

jmp BOOT_OFFSET:0000 

这将导致一个FAR JMP到0x7E00:0x0000。不幸的是,这是物理地址(0x7E00<<4)+0x0000=0x7E000,而你想要跳转的地方并不在那里。你想要跳转到物理地址0x07E00。你真正想要的是一个FAR JMP到0x07E0:0x0000,它将是物理地址(0x07E0<<4)+0x0000=0x7E00。为了使FAR JMP正常工作,我们可以将BOOT_OFFSET向右移动4位。你可以将该行更改为:
jmp (BOOT_OFFSET>>4):0000 

做出这些更改应该使您的引导程序工作。在您的原始代码中存在两个错误:
  • 使用变量的地址而不是该地址处的值
  • 使用了错误的段值进行FAR JMP

显然的停顿可能是由于读取从内存地址boot_segaddr开始的扇区造成的,该地址位于您的引导程序中。很可能您覆盖了引导程序中的所有代码,使其在最终返回int 13h时运行不稳定。


正如Peter所指出的那样,使用像BOCHS这样的模拟器及其内部调试器将允许您逐步执行16位实模式代码。您可能已经发现了这些问题。


1
没错,这解决了我的问题。当尝试复制到0x7E000:0x0000并失败时,int不能将控制权返回给我的程序。 - Stewart Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接