【问题标题】:BIOS Always Fails to Perform Disk OperationsBIOS 总是无法执行磁盘操作
【发布时间】:2019-12-11 11:05:25
【问题描述】:

我目前正在编写一个引导加载程序,旨在加载比引导扇区允许的时间更长的程序。但是,每次运行程序(我在Virtualbox和QEMU中都测试过),磁盘读取失败,磁盘重置也失败。

引导加载程序被设计为加载它之后的扇区(这是一个 FAT16 卷,所以我在磁盘描述中将它设为保留扇区),然后立即运行程序。但是,磁盘读取总是失败(CF 设置为 1),磁盘重置也是如此。这在 Virtualbox 和 QEMU 中都会发生。

这是我的引导扇区和第二扇区的完整代码:

BITS 16

jmp strict short main               ; Jump to main bootloader
nop                                 ; Pad out remaining bytes until boot descriptor

; Disk descriptor

OEM_name            db "HOUSE   "   ; Disk label
bytes_sector        dw 0x0200       ; Bytes per sector
sectors_cluster     db 0x01         ; Sectors per cluster
sectors_reserved    dw 0x0002       ; Number of sectors reserved (in this case 1 for MARBLE, the rest for R)
fats                db 0x02         ; Number of file allocation tables
max_root_entries    dw 0x0200       ; Max number of root entries
sectors             dw 0x1040       ; Number of sectors
medium_type         db 0xF0         ; Type of medium (removable or fixed?)
sectors_fat         dw 0x0010       ; Sectors per file allocation table
sectors_track       dw 0x0020       ; Sectors per track
heads               dw 0x0002       ; Number of heads
hidden_sectors      dd 0x00000000   ; Number of sectors before partition
total_sectors       dd 0x00000000   ; Number of sectors in medium (zero because 2B != 0)
drive_number        db 0x00         ; Drive number (for BIOS int 0x13)
drive_signature     db 0x00         ; NOT USED
ext_signature       db 0x29         ; Extended boot signature
volume_serial       dd 0x00000000   ; Volume's serial number
volume_label        db "HOUSEHOUSE "; Volume label
fs_type             db "FAT16   "   ; Filesystem type

main:
    mov sp, 0x1000                  ; 4K of stack

    mov ax, word [sectors_reserved] ; Read all reserved sectors
    sub al, 0x01                    ; Except this one
    mov ah, 0x02                    ; Read disk sectors
    mov bx, r_buffer                ; Read contents into our buffer
    mov ch, 0x00                    ; Cylinder 0
    mov cl, 0x02                    ; Sector 2
    mov dh, 0x00                    ; Head 0
    jmp .read                       ; Read the disk

.reset:
    pusha                           ; Push register states to stack
    call lps
    mov ah, 0x00                    ; Reset disk
    int 0x13                        ; BIOS disk interrupt
    jnc .read                       ; If successsul, read again
    call lps
    mov ah, 0x00                    ; Otherwise, prepare to reboot
    int 0x19                        ; Reboot

.read:
    call lps
    int 0x13                        ; BIOS disk interrupt
    jc .reset                       ; If failed, reset disk
    jmp r_buffer                    ; Otherwise, jump to R

lps:                                ; Debug message
    pusha
    mov ah, 0x0E
    mov al, 0x52
    mov bh, 0x00
    int 0x10
    mov ah, 0x00
    int 0x16
    popa
    ret

    times 510-($-$$) db 0x00        ; Pad remainder of boot sector with zeros
    sig             dw 0xAA55       ; Boot signature

r_buffer:                           ; Space in memory for loading R

r_start:                            ; Beginning of second sector
    mov ax, 0x07C0
    add ax, 0x0220
    mov ss, ax
    mov ax, 0x07C0
    mov ds, ax

    mov si, success                 ; Successful
    call print_str                  ; Print!
    hlt                             ; Halt here

print_str:                          ; Prints string pointed to by REGISTER SI to cursor location (si=str)
    pusha                           ; Push register states to stack
    mov ah, 0x0E                    ; Print in teletype mode
    mov bh, 0x00                    ; Page 0

.repeat:
    lodsb                           ; Load next character from si
    cmp al, 0x00                    ; Is this a null character?
    je .ret                         ; If it is, return to caller
    int 0x10                        ; Otherwise, BIOS interrupt
    jmp .repeat                     ; Do the same thing

.ret:
    popa                            ; Restore register states
    ret                             ; Return to caller

.data:
    success         db "!", 0x00    ; Success!

    times 1024-($-$$) db 0x00       ; Pad remainder of reserved sectors with zeros

正如我所说,第二个扇区中的代码应该运行,但这不会发生,因为磁盘重置失败。

【问题讨论】:

  • 你应该在main的开头设置段寄存器(ds,es,ss)。在 .reset 之后,您也应该将 ax 重置为 02xxh。您应该在顶部放置“org 7C00h”。在您拥有 hlt 的第二个扇区中,您应该在 hlt 之后添加一个跳转,它会跳回 hlt。请指定您现在得到的确切输出。
  • 谢谢!我这样做了,但仍然得到相同的结果。当我在 QEMU 中运行它时,我得到第一个“R”,我按下一个键,另一个 R,我按下一个键,最后一个 R 出现。这就是发生的一切。
  • 你也有一个无与伦比的 pusha,虽然这似乎不太可能是你的问题的原因。
  • 您是否正在将此引导扇区安装到软盘类型的设备上?如果它在分区设备上,那么您应该设置隐藏扇区字段并将其用作寻址第二个扇区的基础。 (这需要适当的 CHS 到 LBA 编号计算。)
  • 实际上,如果你像这里一样使用sectors_reserved,你不需要org 7C00h,将ds初始化为7C0h就足够了(就像你在第二阶段所做的那样)。

标签: assembly nasm x86-16 bootloader bios


【解决方案1】:

@ecm 了解了我看到的大部分内容。在访问任何数据之前,您需要设置段寄存器并确保您有一个适合您在段寄存器中加载的值的ORG(原点)(尤其是 DS)。默认情况下,当没有 ORG 指令(或等效指令)时,默认值为 0x0000。

在实模式下,每个逻辑地址都由两个部分组成——一个段和一个偏移量。 CPU计算的物理地址是根据公式(segment*16)+offset。将ORG 视为起始偏移量。如果您使用 0x0000 的段,那么您需要 0x7c00 的偏移量。 (0x0000*16)+0x7c00=0x7c00。 0x7c00 是引导加载程序启动的位置。由于您正在将扇区写入 0x7e00 以上的内存,因此将堆栈设置为 0x0000:0x7c00 以从引导加载程序的正下方向内存的开头向下增长会更简单。

由于您使用像LODSB 这样的字符串指令,您应该确保使用CLD 指令清除方向标志(DF),以便字符串操作在内存中前进。当您的引导加载程序开始执行时,您不能依赖 DF 被清除。

读取磁盘时Int 13h/AH=2 clobbers AX。如果出现错误,您需要用 2 重新加载 AH 并用要读取的扇区数重新加载 AL。如果您重新编写代码,您可以使用 SI 来存储要临时读取的扇区数。通常您不必检查磁盘重置是否失败,只需重新执行读取操作即可。尝试操作几次,然后进入失败状态/重新启动。我修改了您的代码以在 DI 中添加重试计数。每次重置时,重试计数减 1。如果重试计数 >= 0,则尝试读取。如果重试次数

当跳转到内存地址 0x7e00 处的代码时,您可以借此机会使用 FAR JMP 确保 CS 也设置为 0x0000。

如果您在引导扇区中将段设置为 0x0000,则可以在第二阶段重复使用它们。如果您打算从引导加载程序进入保护模式,我不建议使用 0x0000 以外的段值,以便前 64KiB 中的逻辑地址和物理地址相同。这在加载 GDT 并跳转到保护模式时非常有用。

使用HLT 时,最好确保中断已关闭(使用CLI),否则HLT 将在下一个中断时退出,处理器将继续执行之后发生在内存中的内容.在真实硬件上,即使中断关闭也可能发生不可屏蔽中断 (NMI),因此最好将 HLT 置于循环中。

考虑到这些变化,这段代码应该可以工作:

BITS 16

org 0x7c00

jmp strict short main               ; Jump to main bootloader
nop                                 ; Pad out remaining bytes until boot descriptor

; Disk descriptor

OEM_name            db "HOUSE   "   ; Disk label
bytes_sector        dw 0x0200       ; Bytes per sector
sectors_cluster     db 0x01         ; Sectors per cluster
sectors_reserved    dw 0x0002       ; Number of sectors reserved (in this case 
                                    ; 1 for MARBLE, the rest for R)
fats                db 0x02         ; Number of file allocation tables
max_root_entries    dw 0x0200       ; Max number of root entries
sectors             dw 0x1040       ; Number of sectors
medium_type         db 0xF0         ; Type of medium (removable or fixed?)
sectors_fat         dw 0x0010       ; Sectors per file allocation table
sectors_track       dw 0x0020       ; Sectors per track
heads               dw 0x0002       ; Number of heads
hidden_sectors      dd 0x00000000   ; Number of sectors before partition
total_sectors       dd 0x00000000   ; Number of sectors in medium (zero because 2B != 0)
drive_number        db 0x00         ; Drive number (for BIOS int 0x13)
drive_signature     db 0x00         ; NOT USED
ext_signature       db 0x29         ; Extended boot signature
volume_serial       dd 0x00000000   ; Volume's serial number
volume_label        db "HOUSEHOUSE "; Volume label
fs_type             db "FAT16   "   ; Filesystem type

main:
    xor ax, ax                      ; AX = 0
    mov ds, ax                      ; DS = ES = 0
    mov es, ax
    mov ss, ax                      ; SS:SP = 0x0000:0x7c00 (grows down below bootloader)
    mov sp, 0x7c00
    cld                             ; Set forward direction for string instructions

    mov si, word [sectors_reserved] ; Read all reserved sectors
    dec si                          ; Except this one. SI = sectors to read
    mov di, 3                       ; retry count of 3 and then give up

    mov bx, r_buffer                ; Read contents into our buffer
    mov ch, 0x00                    ; Cylinder 0
    mov cl, 0x02                    ; Sector 2
    mov dh, 0x00                    ; Head 0
    jmp .read                       ; Read the disk

.reset:
    call lps
    dec di                          ; Reduce retry count
    jge .read                       ; If retry >= 0 then try again

                                    ; Otherwise retry count exceeded - reboot
    mov ah, 0x00
    int 0x19                        ; Reboot

.read:
    mov ax, si                      ; Transfer sector read count to AX
    mov ah, 0x02                    ; Read disk sectors
    call lps
    int 0x13                        ; BIOS disk interrupt
    jc  .reset                      ; If failed, reset disk
    jmp 0x0000:r_start              ; Otherwise, jump to r_start. Set CS=0

lps:                                ; Debug message
    pusha
    mov ah, 0x0E
    mov al, 0x52
    mov bh, 0x00
    int 0x10
    mov ah, 0x00
    int 0x16
    popa
    ret

    times 510-($-$$) db 0x00        ; Pad remainder of boot sector with zeros
    sig             dw 0xAA55       ; Boot signature

r_buffer:                           ; Space in memory for loading R

r_start:                            ; Beginning of second sector
    mov si, success                 ; Successful
    call print_str                  ; Print!

    cli
.endloop:
    hlt                             ; Halt here
    jmp .endloop

print_str:                          ; Prints string pointed to by REGISTER SI 
                                    ;     to cursor location (si=str)
    pusha                           ; Push register states to stack
    mov ah, 0x0E                    ; Print in teletype mode
    mov bh, 0x00                    ; Page 0

.repeat:
    lodsb                           ; Load next character from si
    cmp al, 0x00                    ; Is this a null character?
    je .ret                         ; If it is, return to caller
    int 0x10                        ; Otherwise, BIOS interrupt
    jmp .repeat                     ; Do the same thing

.ret:
    popa                            ; Restore register states
    ret                             ; Return to caller

.data:
    success         db "!", 0x00    ; Success!

    times 1024-($-$$) db 0x00       ; Pad remainder of reserved sectors with zeros

观察和建议

  • 对于引导加载程序开发,我强烈建议使用 BOCHS 进行初始测试。内置调试器是实模式感知和理解实模式分段,而 QEMU/GDB 没有。学习使用调试工具是一项宝贵的技能。

【讨论】:

  • 代码的第一个版本我将重试计数设置为 0 用于测试目的。更新根据描述和 cmets 将其设置为 3。
猜你喜欢
  • 1970-01-01
  • 2015-05-29
  • 1970-01-01
  • 2015-08-01
  • 2015-10-04
  • 2019-07-03
  • 2012-02-25
  • 2014-07-23
  • 2012-07-20
相关资源
最近更新 更多