I'm writing a simple operating system and I'm having a lot of problems with reading from the disk. I use int 0x13 and ah=0x02 to read data from the drive and I've been getting several different error messages. When I run with
$ qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw
it works great. When I do
$ qemu-system-x86_64 -drive file=os.bin,format=raw
the carry flag is set and ah is 0x20. According to http://www.ctyme.com/intr/rb-0606.htm#Table234, it's a "controller failure" error. This doesn't make much sense, as it's running in a vm, so I'm pretty sure that it's my code that is wrong.
When I write my boot image to a disk (dd to a partition on a flashdrive) it boots and successfully starts my program but fails at the same disk load, with ah being 0x01. The same site says that this is a "invalid function in AH or invalid parameter" error, which further confirms that the problem is in my code. I've had to throw together a poor print_hex solution that prints ah number of X's, because I haven't had the motivation to put something better together.
Here is my load_disk.asm file:
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
I've truncated my strings to make room in the 512 bytes for debug code. This code is called from boot.asm, which is
[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
I've been looking through https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf as my foundation for this project. However, it assumes a floppy disk, so it is of limited help in this situation.
Edit: I thought I got all the relevant files, and it appears I didn't :(
Here's 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
After a comment mentioned it, I added a push dx/pop dx to that file. ah
is now 12, or 0x0C, which is "unsupported track or invalid media".
There's a chance that it's an issue with how hard drives are structured or something. I'm using cat
to assemble my final os.bin file, which doesn't make that much sense to me. Here's my Makefile line (I can post the entire makefile if that would be helpful)
os.bin : build/boot.bin build/kernal.bin
cat build/boot.bin build/kernal.bin > $@
build/boot.bin is all of my assembly that is loaded in the first 512 bytes. kernal.bin is my C code that I should be loading from the disk
You don't show your kernel but I can make some educated guesses. Although it may vary on some versions of QEMU, you will find when booting as floppy from disk images that you are allowed to read sectors past the end of the file, but booting as a hard drive is less forgiving.
Your code reads 31 sectors starting from from CHS(0,0,2) when it loads the kernel. You don't show your kernel (kernel.bin
) but I suspect that it's less than 31 sectors in size.
When you do:
qemu-system-x86_64 -drive file=os.bin,if=floppy,index=0,media=disk,format=raw
you boot as the first floppy disk. Since QEMU generally allows you to read past the end of a floppy disk image the Int 13h/AH=2 succeeds.
When you do:
qemu-system-x86_64 -drive file=os.bin,format=raw
you boot as the first hard disk. QEMU is likely complaining because you have requested to read 31 sectors worth of data but there isn't that much data in the disk image os.bin
. I believe the general rule is that for QEMU's hard disk read to work there has to be at least 1 byte of data in a sector for the read to succeed. That would mean that at a minimum you'd have to have an os.bin
that is at least 512 bytes (bootsector) + 30 * 512 bytes (kernel) + 1 (at least 1 byte in the 31st sector) = 15873 bytes in size. I would expect then that if your image file is less than 15873 bytes, reading 31 sectors from CHS(0,0,2)/LBA(Logical Block Address)=1 will fail. That is likely why you are getting the error:
unsupported track or invalid media
The fix is rather simple. Make sure your os.bin
is at least 32 sectors (boot sector + maximum of 31 sectors for the kernel) or a file size of 32*512=16384. You can use the DD program to build a 16384 byte image and then use DD to place the boot.bin
and kernel.bin
files inside of it.
Your Makefile entry for building os.bin
could probably look like:
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
The first command creates a zero filled file called os.bin
using a block size (bs
) of 512 and generating a file with 32 blocks. 32 * 512 = 16384. The second command writes boot.bin
to the beginning of the file to block 0 (first block). The conv=notrunc
says that after writing boot.bin
to os.bin
that we don't want the file to be truncated. The last line is similar but it writes kernal.bin
to os.bin
but tells DD to seek to block 1 on disk and write the file and not to truncate os.bin
when finished.
After this Makefile recipe is complete you should have an os.bin
file that is 16384 bytes long containing your bootloader and kernel. This should keep QEMU happy whether it is reading as a floppy or hard disk image when using Int 13h/AH=2.
When it comes to booting as USB on a real machine using Floppy Disk Drive (FDD) emulation you may find a bootloader starts running but doesn't appear to work correctly. This is because that many BIOSes that boot USB media as a floppy assume that a Bios Parameter Block (BPB) is present and blindly updates drive geometry fields after your boot sector is read into memory and before control is transferred to physical address 0x07c00. Without a BPB those changes could overwrite data and/or instructions causing the code to not work as expected. More information on this phenomenon and a sample BPB can be found in one of my other Stackoverflow answers.
It is also a good idea on real hardware to retry a disk operation a few times if it fails. You can do that by calling BIOS function Int 13h/AH=0 (Disk Reset) before retrying the operation again. If it fails more than a few times then you can abort. I don't believe this is your problem though, but I mention it for completeness.
Your bootloader starts with:
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
Your code only sets SP, and not SS. SS:SP combined create the stack pointer. Since SS hasn't been set we really don't know where in memory the stack is. There is no guarantee SS is 0 upon entry to our bootloader (see my general bootloader tips Stackoverflow answer for more information and suggestions). Since your code doesn't even seem to use BP (usually a stack frame pointer) there is no need to set it to 0x9000. Just set SS:SP to 0x0000:0x9000. The code could look like:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With