【问题标题】:Error reading disk when disk is not a hard drive. Int 0x13 ah 0x02磁盘不是硬盘驱动器时读取磁盘时出错。诠释 0x13 啊 0x02
【发布时间】:2020-01-03 22:27:07
【问题描述】:

我正在编写一个简单的操作系统,但在从磁盘读取时遇到了很多问题。我使用 int 0x13 和 ah=0x02 从驱动器读取数据,我收到了几个不同的错误消息。当我跑步时

$ qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw

效果很好。当我这样做时

$ qemu-system-x86_64 -drive file=os.bin,format=raw

进位标志已设置,ah 为 0x20。根据http://www.ctyme.com/intr/rb-0606.htm#Table234,这是一个“控制器故障”错误。这没有多大意义,因为它是在虚拟机中运行的,所以我很确定是我的代码错了。

当我将启动映像写入磁盘(dd 到闪存驱动器上的分区)时,它会启动并成功启动我的程序,但在相同的磁盘加载时失败,ah 为 0x01。同一个站点说这是“AH 中的无效函数或无效参数”错误,这进一步证实了问题出在我的代码中。我不得不拼凑一个糟糕的 print_hex 解决方案,它可以打印出很多个 X,因为我没有动力去拼凑更好的东西。

这是我的 load_disk.asm 文件:

disk_load:
  pusha
  push bx
  mov bx, DISK_START
  call print_string
  pop bx   

  push dx
  mov ah, 0x02
  mov al, dh
  mov cl, 0x02
  mov ch, 0x00
  mov dh, 0x00


  int 0x13
  jc disk_error0
  pop dx
  cmp dh, al
  jne disk_error1

  push bx
  mov bx, DISK_SUCC
  call print_string
  pop bx  
  popa
  ret

disk_error0:
  loopY:
  cmp ah, 0x0
  je cont
  mov bx, STARTING_DISK_ERROR
  call print_string  
  sub ah, 1
  jmp loopY
cont:
; print a nice little counter
  mov bx,NEWLINE
  call print_string 
  mov ah,8 ; 80 character screen, 10 characters
loopS:
  cmp ah,0x0
  je cont2
  mov bx, NUMBERS
  call print_string
  sub ah, 1
  jmp loopS

cont2:
  mov bx,NEWLINE
  call print_string
  mov bx, DISK_ERROR_0
  call print_string
  jmp $

disk_error1:
  mov bx, DISK_ERROR_1
  call print_string
  jmp $

STARTING_DISK_ERROR : db "X",0
NEWLINE: db 10,13,0
NUMBERS: db "1234567890",0
DISK_ERROR_0 : db "Error0",10,13, 0
DISK_ERROR_1 : db "Error1",10,13, 0
DISK_START : db "Startingdisk", 10,13, 0
DISK_SUCC : db "Loadeddisk", 10,13,0

我已经截断了我的字符串,以便在 512 字节中为调试代码腾出空间。这段代码是从boot.asm调用的,就是

[bits 16]
[org 0x7c00]
  jmp 0x0000:main_entry    ; ensures cs = 0x0000

main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  KERNAL_OFFSET equ 0x1000

  mov [BOOT_DRIVE], dl

  mov bp, 0x9000
  mov sp, bp


  mov bx, MSG_REAL_MODE
  call print_string



  call load_kernal
  call switch_to_pm 

  ;this line will never execute
  jmp $

%include "src/print_string.asm"
%include "src/disk_load.asm"
%include "src/gdt.asm"
%include "src/print_string_pm.asm"
%include "src/switch_to_pm.asm"
%include "src/print_hex.asm" ; this is broken, don't use

[bits 16]
load_kernal:
  mov bx, MSG_LOAD_KERNAL
  call print_string

  mov bx, KERNAL_OFFSET

  mov dh, 31            ; load 31 sectors. gives plenty of room
  mov dl, [BOOT_DRIVE]
  call disk_load 
;  mov bx, MSG_LOAD_DISK
;  call print_string
  ret

[bits 32]

BEGIN_PM:
  mov ebx, MSG_PROT_MODE
  call print_string_pm
  call KERNAL_OFFSET 
  mov ebx, 0x5000
  call print_string_pm
  jmp $ ; if the kernal returns, stay here



BOOT_DRIVE      db 0
MSG_REAL_MODE   db "Started in 16-bit", 10, 13, 0
MSG_PROT_MODE   db "Sted in 32-bit", 0
;MSG_SHOULD_NEVER_PRINT db "Frack",10,13, 0
MSG_LOAD_KERNAL db "Loding kernal",10,13, 0
;MSG_LOAD_DISK   db "Loaded disk!", 10,13,0
MSG_KERNAL_EXIT db "kernal has exited",10,13,0



times 510-($-$$) db 0

dw 0xaa55

我一直在寻找 https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf 作为这个项目的基础。但是,它假定为软盘,因此在这种情况下它的帮助有限。

编辑:我以为我得到了所有相关文件,但似乎我没有:( 这里是print_string.asm

; prints the string at location BX
print_string:
  push ax
  push bx
  push dx ; NEW
  mov ah, 0x0e
  loop1:
    mov al, [bx]
    int 0x10
    add bx, 1
    mov dl, [bx]
    cmp dl, 0x0
    jne loop1
  pop dx ; NEW
  pop bx
  pop ax
  ret

在评论提到它之后,我向该文件添加了 push dx/pop dx。 ah 现在是 12 或 0x0C,即“不支持的轨道或无效媒体”。

有可能是硬盘驱动器的结构或其他问题。我正在使用cat 来组装我的最终 os.bin 文件,这对我来说没有多大意义。这是我的 Makefile 行(如果有帮助,我可以发布整个 makefile)

os.bin : build/boot.bin build/kernal.bin
    cat build/boot.bin build/kernal.bin > $@

build/boot.bin 是我在前 512 个字节中加载的所有程序集。 kernal.bin 是我应该从磁盘加载的 C 代码

【问题讨论】:

  • 请发布您的完整代码。例如,我看不到您的 print_string 例程是否会破坏 dl
  • @fuz 编辑了额外的细节。我现在将尝试使用该编辑从闪存驱动器启动。
  • 我很好奇你用来写入 USB 驱动器的 DD 命令到底是什么。
  • 不幸的是,我还没来得及做这件事,就离开了周末。您的回答看起来很棒,并且可能解决了我遇到的所有主要问题,非常感谢。我会及时通知你。

标签: assembly x86 x86-16 bootloader osdev


【解决方案1】:

你没有展示你的内核,但我可以做出一些有根据的猜测。尽管在某些版本的 QEMU 上可能会有所不同,但您会发现,当从磁盘映像作为软盘启动时,您可以读取文件末尾之后的扇区,但作为硬盘驱动器启动时就不那么宽容了。

您的代码在加载内核时从 CHS(0,0,2) 开始读取 31 个扇区。您没有显示您的内核 (kernel.bin),但我怀疑它的大小小于 31 个扇区。

当你这样做时:

qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw

您作为第一张软盘启动。由于 QEMU 通常允许您读取软盘映像的末尾,因此 Int 13h/AH=2 成功。

当你这样做时:

qemu-system-x86_64 -drive file=os.bin,format=raw

您作为第一个硬盘启动。 QEMU 可能会抱怨,因为您已请求读取 31 个扇区的数据,但磁盘映像 os.bin 中没有那么多数据。我相信一般规则是 QEMU 的硬盘读取工作必须在一个扇区中至少有 1 个字节的数据才能成功读取。这意味着您至少必须有一个os.bin,即至少 512 字节(引导扇区)+ 30 * 512 字节(内核)+ 1(在第 31 个扇区)= 15873 字节大小。我希望如果您的图像文件小于 15873 字节,从 CHS(0,0,2)/LBA(Logical Block Address)=1 读取 31 个扇区将失败。这可能就是您收到错误的原因:

不支持的曲目或无效的媒体

修复相当简单。确保您的os.bin 至少有 32 个扇区(引导扇区 + 内核最多 31 个扇区)或文件大小为 32*512=16384。您可以使用 DD 程序构建一个 16384 字节的图像,然后使用 DD 将 boot.binkernel.bin 文件放入其中。

您用于构建 os.bin 的 Makefile 条目可能如下所示:

os.bin : build/boot.bin build/kernal.bin    
    dd if=/dev/zero of=$@ bs=512 count=32
    dd if=build/boot.bin of=$@ bs=512 conv=notrunc
    dd if=build/kernal.bin of=$@ bs=512 seek=1 conv=notrunc

第一个命令使用 512 的块大小 (bs) 创建一个名为 os.bin 的零填充文件,并生成一个包含 32 个块的文件。 32 * 512 = 16384。第二个命令将boot.bin 写入文件开头的块0(第一个块)。 conv=notrunc 表示在将 boot.bin 写入 os.bin 之后,我们不希望文件被截断。最后一行类似,但它将kernal.bin写入os.bin,但告诉DD在磁盘上寻找块1并写入文件,而不是在完成时截断os.bin

这个 Makefile 配方完成后,您应该有一个 16384 字节长的 os.bin 文件,其中包含您的引导加载程序和内核。这应该让 QEMU 在使用 Int 13h/AH=2 时无论是作为软盘还是硬盘映像读取都很满意。


使用 USB FDD 仿真在真实硬件上启动

在使用软盘驱动器 (FDD) 仿真在真机上以 USB 启动时,您可能会发现引导加载程序开始运行,但似乎无法正常工作。这是因为许多将 USB 介质作为软盘引导的 BIOS 假定存在 Bios 参数块 (BPB),并在将引导扇区读入内存之后和将控制转移到物理地址 0x07c00 之前盲目地更新驱动器几何字段。如果没有 BPB,这些更改可能会覆盖数据和/或指令,从而导致代码无法按预期工作。有关此现象的更多信息和示例 BPB 可以在我的另一个 Stackoverflow answers 中找到。

如果磁盘操作失败,在真实硬件上重试几次磁盘操作也是一个好主意。您可以通过调用 BIOS 函数Int 13h/AH=0(磁盘重置)来实现,然后再次重试操作。如果多次失败,则可以中止。虽然我不相信这是你的问题,但为了完整起见,我提到它。


其他观察

您的引导加载程序以:

main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  KERNAL_OFFSET equ 0x1000

  mov [BOOT_DRIVE], dl

  mov bp, 0x9000
  mov sp, bp

您的代码只设置 SP,而不设置 SSSS:SP 结合创建堆栈指针。由于 SS 尚未设置,我们真的不知道堆栈在内存中的哪个位置。无法保证 SS 在进入我们的引导加载程序时为 0(有关更多信息和建议,请参阅我的 general bootloader tips Stackoverflow 答案)。由于您的代码似乎甚至没有使用 BP(通常是堆栈帧指针),因此无需将其设置为 0x9000。只需将 SS:SP 设置为 0x0000:0x9000。代码可能如下所示:

main_entry:
  xor ax, ax
  mov ds, ax
  mov es, ax
  mov ss, ax               ; Set SS to 0
  mov sp, 0x9000           ; Set SP right after SS (see my bootloader tips for reason why) 

  mov [BOOT_DRIVE], dl

  KERNAL_OFFSET equ 0x1000

【讨论】:

    猜你喜欢
    • 2017-01-27
    • 2015-10-15
    • 2014-11-29
    • 2011-04-26
    • 1970-01-01
    • 1970-01-01
    • 2012-08-07
    • 1970-01-01
    • 2010-10-05
    相关资源
    最近更新 更多