将扇区加载到内存时出现磁盘读取错误

11

我尝试使用这个来开发引导程序,但是运行时出现了以下情况:

disk read error!

如果我忽略它,在后面的部分中,它会显示错误的内存映射。我也遵循了其他一些来源,但徒劳无功。感觉我只是在复制他们正在做的事情。如果我稍微有所不同,每次都会产生新的错误。

我应该使用一个已经构建好的引导程序,还是该怎么做?

磁盘加载错误的代码如下:

[org 0x7c00]

    KERNEL_OFFSET equ 0x1000    
    mov [BOOT_DRIVE], dl        
    mov bp, 0x9000          
    mov sp, bp  
    mov bx, MSG_REAL_MODE       
    call print_string           
    call load_kernel            
    jmp $

print_string:
    pusha
    mov ah, 0x0e

loop:
    mov al,[bx]
    cmp al, 0
    je return
    int 0x10
    inc bx
    jmp loop

return:
    popa
    ret

disk_load: 
    push dx                                              
    mov ah, 0x02                                   
    mov al, dh                                          
    mov ch, 0x00                                    
    mov dh, 0x00                                     
    mov cl, 0x02                                    
    int 0x13                                           
    jc disk_error                                  
    pop dx                                               
    cmp dh, al                                         
    jne disk_error                                 
    ret

 disk_error :
   mov bx, DISK_ERROR_MSG 
   call print_string 
   jmp $

DISK_ERROR_MSG db "Disk read error!", 0

[bits 16]

load_kernel: 
    mov bx, KERNEL_OFFSET       
    mov dh, 15           
    mov dl, [BOOT_DRIVE]                      
    call disk_load                                                  
    ret

; Global variables
BOOT_DRIVE     db 0 
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0 

; Bootsector padding 
times 510-($-$$) db 0 
dw 0xaa55
我使用这个命令来组装和运行我的引导程序:
nasm boot.asm -f bin -o boot.bin && qemu-system-i386 boot.bin
我卡在这一点上了。我的引导加载程序显示 "disk read error"。如果我现在忽略它,那么在执行我的 kernel.c 时会出现问题,似乎使用了错误的内存映射。

我在这个地方卡住了。我的引导程序显示 "disk read error"。如果我现在忽略它,那么在执行 kernel.c 时会出现问题,似乎使用了错误的内存映射。


请检查这段代码并帮我解决问题! - Palvit Garg
在顶部加上[bits 16]也是一个好主意,这样NASM就知道为引导加载程序生成所有的16位代码。 - Michael Petch
我正在使用qemu作为模拟器。 - Palvit Garg
所以看起来你是在 Linux 上进行这个操作?我能看到你加载 bin 文件的方式存在一个问题,那就是这个文件不足够大,无法真正拥有引导加载器加载其他扇区的数据(QEMU 无法从不存在的磁盘镜像中加载扇区)。你真的需要创建一个磁盘镜像(开始时大小为软盘映像的大小即可)。然后你需要将 boot.bin 放置在第一扇区。我假设你根本没有使用 dd 命令? - Michael Petch
2
你可以尝试类似于 nasm -f bin boot.asm -o boot.bin 这样的命令,然后创建一个磁盘映像(以1.44MB软盘为例)使用 dd if=/dev/zero of=disk.img bs=1024 count=1440 命令。接着使用 dd if=boot.bin of=disk.img conv=notrunc 将bin文件放置在磁盘映像的开头。然后尝试这样运行QEMU qemu-system-i386 -fda disk.img。这将启动磁盘映像作为A:软盘。 - Michael Petch
显示剩余6条评论
1个回答

14

"他正在列出清单,他在检查两次......"

  • 引导加载程序以实模式启动,因此最好强制汇编器使用16位代码。您可以在程序顶部写入[bits 16]以实现这一点。

  • 当引导加载程序启动时,BIOS会将其放置在线性地址00007C00h处。它可以通过多种方式来进行,与段和偏移量的组合有关。
    当您明确写入[org 0x7C00]时,您(在某种程度上)期望该组合的段部分等于零。但是,这绝不是BIOS的义务!因此,您需要手动设置段寄存器(DS、ES和SS)。

  • 您在print_string例程中使用的BIOS电传打字机函数使用BL和BH作为参数。因此,您永远不应使用BX寄存器来寻址文本。当然,一些BIOS不再使用这些BL和BH参数了,但请尽力为最广泛的受众开发程序。

  • 当您使用0x9000初始化SP寄存器时,您有效地设置了一个堆栈,它可能很容易、而您却没有注意到地覆盖了其下面的程序!最好选择满足您需求且仅满足您需求的SS和SP组合。一个高度为4608字节的堆栈,它保持在7C00h的引导扇区上方,以9000h结尾,需要:SS=07E0h SP=1200h。为了避免在8086硬件上出现任何问题,在更改SS:SP时最好禁用中断。

  • 您使用了pushapopa指令。这些不是8086硬件上有效的指令。在编写健壮的软件时,我们应该测试硬件是否能够胜任任务。但在这里,最简单的解决方案是只推送/弹出单个寄存器。

  • 您已经解释了从磁盘读取的BIOS函数的返回值,但在传输了错误数量的扇区时,您只是放弃了。这是一种错误的方法。当BIOS告诉您有不完整的传输时(如果您的BIOS未启用多轨道功能,则会发生此情况),您必须重复调用剩余扇区的调用。显然,某些参数将需要进行调整:下一个磁头,也许下一个圆柱体,总是sector=1。 (完美的解决方案涉及从BIOS检索磁盘几何信息或从磁盘上的BPB中读取它)。我假设基本的1.44 MB软盘操作。

  • 当第一次读取磁盘失败时,您应该重试多次。这样的第一次失败是完全正常的。五次重试是一个很好的值。在尝试之间,调用重置磁盘驱动器的BIOS函数。

  • 为了确保QEMU可以实际读取这15个附加扇区,您应该填充此文件,使其总长度达到16个扇区。您链接的文本也这样做了

    [bits 16]
    [org 0x7C00]
    
    KERNEL_OFFSET equ 0x1000
    
    xor  ax, ax
    mov  ds, ax
    mov  es, ax    
    mov  [BOOT_DRIVE], dl
    mov  ax, 0x07E0
    cli
    mov  ss, ax 
    mov  sp, 0x1200
    sti
    mov  si, MSG_REAL_MODE       
    call print_string           
    call load_kernel            
    jmp  $
    
    print_string:
      push ax
      push bx
      push si
      mov  bx, 0x0007  ;BL=WhiteOnBlack BH=Display page 0
      mov  ah, 0x0E    ;Teletype function
     loop:
      mov  al, [si]
      cmp  al, 0
      je return
      int  0x10
      inc  si
      jmp  loop
     return:
      pop  si
      pop  bx
      pop  ax
      ret
    
    disk_load:
      mov  [SECTORS], dh
      mov  ch, 0x00      ;C=0
      mov  dh, 0x00      ;H=0
      mov  cl, 0x02      ;S=2
     next_group:
      mov  di, 5         ;Max 5 tries
     again: 
      mov  ah, 0x02      ;Read sectors
      mov  al, [SECTORS]
      int  0x13
      jc   maybe_retry
      sub  [SECTORS], al ;Remaining sectors
      jz  ready
      mov  cl, 0x01      ;Always sector 1
      xor  dh, 1         ;Next head on diskette!
      jnz  next_group
      inc  ch            ;Next cylinder
      jmp  next_group
     maybe_retry:
      mov  ah, 0x00      ;Reset diskdrive
      int  0x13
      dec  di
      jnz  again
      jmp  disk_error
     ready:
      ret
    
    disk_error:
      mov  si, DISK_ERROR_MSG 
      call print_string 
      jmp  $
    
    DISK_ERROR_MSG db "Disk read error!", 0
    
    load_kernel: 
      mov  bx, KERNEL_OFFSET       
      mov  dh, 15           
      mov  dl, [BOOT_DRIVE]                      
      call disk_load                                                  
      ret
    
    ; Global variables
    BOOT_DRIVE     db 0
    SECTORS        db 0
    MSG_REAL_MODE  db "Started in 16-bit Real Mode", 0 
    
    ; Bootsector padding 
    times 510-($-$$) db 0 
    dw 0xAA55
    
    ; 15 sector padding
    times 15*256 dw 0xDADA
    

关于更新_SS:SP_的注释。你所说的确实是为了兼容最广泛的硬件,但CLI/STI对的主要原因是为了规避1980年代某些有缺陷的8088 CPU上的错误。当您使用MOV指令更新_SS_寄存器时,它应该在_NEXT_指令之后禁用中断。通常这是对_SP_的更新。不幸的是,在某些有缺陷的8088 CPU上,中断没有按预期关闭,因此需要显式使用CLI/STI来避免该错误。 - Michael Petch
2
我已经为你的答案点赞了。还有一个小观察要提出。你依赖于_AL_是读取的扇区数。有一些古老的BIOS只在CF=1时返回_AL_的值(是的,听起来有点疯狂,但确实如此)。大多数引导加载程序(针对更广泛的真实硬件)不会假定可以依赖_AL_的值。因此,通常大多数引导加载程序一次读取一个扇区,或者获取磁盘几何信息(从BPB或BDA中),并且仅进行不跨越轨道(柱面)的多扇区读取。但是你的代码将适用于大多数硬件(以及我所知道的所有模拟器)。 - Michael Petch

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