我正在编写一个简单的操作系统,但从磁盘读取时遇到很多问题。我使用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。根据{{3}},这是“控制器故障”错误。这没有多大意义,因为它是在vm中运行的,所以我很确定这是我的代码错了。
当我将启动映像写入磁盘(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
我一直在寻找http://www.ctyme.com/intr/rb-0606.htm#Table234作为该项目的基础。但是,由于它是一张软盘,因此在这种情况下它的帮助有限。
编辑:我以为我得到了所有相关文件,但看来我没有:(
这是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代码
答案 0 :(得分: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个字节的数据才能成功读取。这意味着您至少必须拥有一个{strong>至少的os.bin
512字节(引导扇区)+ 30 * 512字节(内核)+1(至少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.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
第一个命令使用块大小(os.bin
)为512创建一个名为bs
的零填充文件,并生成一个包含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保持满意。
在使用软盘驱动器(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
您的代码仅设置 SS ,而不设置 SP 。结合 SS: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