当磁盘不是硬盘驱动器时读取磁盘时出错。内部 0x13 啊 0x02
Error reading disk when disk is not a hard drive. Int 0x13 ah 0x02
我正在编写一个简单的操作系统,但在从磁盘读取时遇到了很多问题。我使用 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,这是一个 "controller failure" 错误。这没有多大意义,因为它 运行 在虚拟机中运行,所以我很确定是我的代码错了。
当我将启动映像写入磁盘(dd 到闪存驱动器上的分区)时,它会启动并成功启动我的程序,但在同一磁盘加载时失败,ah 为 0x01。同一站点说这是一个 "invalid function in AH or invalid parameter" 错误,这进一步证实了问题出在我的代码中。我不得不拼凑出一个糟糕的 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,即 "unsupported track or invalid media"。
有可能是硬盘驱动器的结构问题或其他问题。我正在使用 cat
到 assemble 我的最终 os.bin 文件,这对我来说没有多大意义。这是我的 Makefile 行(如果有帮助,我可以 post 整个 makefile)
os.bin : build/boot.bin build/kernal.bin
cat build/boot.bin build/kernal.bin > $@
build/boot.bin 是我在前 512 个字节中加载的所有程序集。 kernal.bin 是我应该从磁盘加载的 C 代码
你没有显示你的内核,但我可以做出一些有根据的猜测。虽然它可能在某些版本的 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 个扇区中至少 1 个字节)= 15873 个字节。我希望如果您的图像文件小于 15873 字节,从 CHS(0,0,2)/LBA(Logical Block Address)=1 读取 31 个扇区将会失败。这可能就是您收到错误的原因:
unsupported track or invalid media
修复相当简单。确保您的 os.bin
至少有 32 个扇区(引导扇区 + 内核最多 31 个扇区)或文件大小为 32*512=16384。您可以使用 DD 程序构建一个 16384 字节的图像,然后使用 DD 将 boot.bin
和 kernel.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
文件。当使用 Int 13h/AH=2 时,无论它是作为软盘还是硬盘映像读取,这都应该让 QEMU 满意。
使用 USB FDD 仿真在真实硬件上启动
在使用软盘驱动器 (FDD) 仿真的真机上作为 USB 启动时,您可能会发现启动加载程序 运行 但似乎无法正常工作。这是因为许多将 USB 介质作为软盘引导的 BIOS 假定存在 Bios 参数块 (BPB),并在引导扇区被读入内存后和控制权转移到物理地址 0x07c00 之前盲目更新驱动器几何字段。如果没有 BPB,这些更改可能会覆盖数据 and/or 指令,导致代码无法按预期工作。有关此现象的更多信息和示例 BPB 可以在我的另一个 Whosebug .
中找到
如果磁盘操作失败,在真实硬件上重试几次也是一个好主意。您可以在再次重试该操作之前调用 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,而不设置 SS。 SS:SP 组合创建堆栈指针。由于 SS 尚未设置,我们真的不知道堆栈在内存中的位置。无法保证 SS 在进入我们的引导加载程序时为 0(有关更多信息和建议,请参阅我的 Whosebug 答案)。由于您的代码似乎甚至没有使用 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
我正在编写一个简单的操作系统,但在从磁盘读取时遇到了很多问题。我使用 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,这是一个 "controller failure" 错误。这没有多大意义,因为它 运行 在虚拟机中运行,所以我很确定是我的代码错了。
当我将启动映像写入磁盘(dd 到闪存驱动器上的分区)时,它会启动并成功启动我的程序,但在同一磁盘加载时失败,ah 为 0x01。同一站点说这是一个 "invalid function in AH or invalid parameter" 错误,这进一步证实了问题出在我的代码中。我不得不拼凑出一个糟糕的 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,即 "unsupported track or invalid media"。
有可能是硬盘驱动器的结构问题或其他问题。我正在使用 cat
到 assemble 我的最终 os.bin 文件,这对我来说没有多大意义。这是我的 Makefile 行(如果有帮助,我可以 post 整个 makefile)
os.bin : build/boot.bin build/kernal.bin
cat build/boot.bin build/kernal.bin > $@
build/boot.bin 是我在前 512 个字节中加载的所有程序集。 kernal.bin 是我应该从磁盘加载的 C 代码
你没有显示你的内核,但我可以做出一些有根据的猜测。虽然它可能在某些版本的 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 个扇区中至少 1 个字节)= 15873 个字节。我希望如果您的图像文件小于 15873 字节,从 CHS(0,0,2)/LBA(Logical Block Address)=1 读取 31 个扇区将会失败。这可能就是您收到错误的原因:
unsupported track or invalid media
修复相当简单。确保您的 os.bin
至少有 32 个扇区(引导扇区 + 内核最多 31 个扇区)或文件大小为 32*512=16384。您可以使用 DD 程序构建一个 16384 字节的图像,然后使用 DD 将 boot.bin
和 kernel.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
文件。当使用 Int 13h/AH=2 时,无论它是作为软盘还是硬盘映像读取,这都应该让 QEMU 满意。
使用 USB FDD 仿真在真实硬件上启动
在使用软盘驱动器 (FDD) 仿真的真机上作为 USB 启动时,您可能会发现启动加载程序 运行 但似乎无法正常工作。这是因为许多将 USB 介质作为软盘引导的 BIOS 假定存在 Bios 参数块 (BPB),并在引导扇区被读入内存后和控制权转移到物理地址 0x07c00 之前盲目更新驱动器几何字段。如果没有 BPB,这些更改可能会覆盖数据 and/or 指令,导致代码无法按预期工作。有关此现象的更多信息和示例 BPB 可以在我的另一个 Whosebug
如果磁盘操作失败,在真实硬件上重试几次也是一个好主意。您可以在再次重试该操作之前调用 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,而不设置 SS。 SS:SP 组合创建堆栈指针。由于 SS 尚未设置,我们真的不知道堆栈在内存中的位置。无法保证 SS 在进入我们的引导加载程序时为 0(有关更多信息和建议,请参阅我的
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