3

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

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Carson Graham
  • 519
  • 4
  • 15
  • 1
    Please post your full code. For example, I can't see if your `print_string` routine trashes `dl`. – fuz Aug 30 '19 at 16:59
  • @fuz edited with extra detail. I'm going to try booting off of a flash drive with that edit right now. – Carson Graham Aug 30 '19 at 17:12
  • I'd be curious exactly what the DD command you use to write to the USB drive. – Michael Petch Aug 30 '19 at 21:27
  • 1
    unfortunately I left for the weekend before I could work on this. Your answer looks great and addresses probably all of the major problems I've been having, thank you so much. I'll keep you posted. – Carson Graham Aug 31 '19 at 20:17

1 Answers1

4

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.


Booting on Real Hardware using USB FDD Emulation

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.


Other Observations

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
ecm
  • 2,583
  • 4
  • 21
  • 29
Michael Petch
  • 46,082
  • 8
  • 107
  • 198