Page 1 of 2

Bios disk read error help

Posted: Tue Oct 13, 2020 1:25 am
by abstractmath
Hello, over the past few hours I've been looking at a weird bug that happens in my bootloader whenever I attempt to read more than 40 sectors of a hard disk. From what debugging I have done, the bios interrupt 0x13 is giving me an error code of 0x000C, which according to https://stanislavs.org/helppc/int_13-1.html, says "unsupported track/invalid media". At this point, I'm unsure how to fix the actual issue, and I would appreciate any insight. Here is all the relevant code in my bootloader:

(Note that this isn't the entirety of the code itself, only the code that is necessary to recreate the error, and build the bootloader)

Makefile:

Code: Select all

BOOTOUTPUT = boot.bin
OSOUTPUT = os.bin
SRCS = $(shell find . -name '*.c')
CINC = $(shell find . -name '*.h')
COBJS = $(patsubst %.c, %.o, $(SRCS))
OBJDIR = build

$(OSOUTPUT): $(BOOTOUTPUT)
	mv $(BOOTOUTPUT) $(OSOUTPUT)

$(BOOTOUTPUT): boot/bootsector.asm
	nasm -f bin boot/bootsector.asm -o $(BOOTOUTPUT)

run: 
	qemu-system-x86_64 $(OSOUTPUT)

clean:
	rm -f *.bin *.o $(COBJS)

boot/bootsector.asm:

Code: Select all

org 0x7c00
bits 16
mov ax, HELLO_MSG ;Print a simple hello message :D
call _printString
xor ax, ax
;Here, we'll load the kernel into RAM
call LoadKernel
;Enter protected mode
call EnterProtMode

EnterProtMode:
    cli ;Disable interrupts
    lgdt [gdt_pointer] ;Load the GDT register with the start address of the GDT
    mov eax, cr0
    or al, 1 ;Set PE (protection enable) bit in CR0
    mov cr0, eax
    jmp 08h:Stage2 ;Jump to stage 2

LoadKernel:
    mov bx, KERNEL_OFFSET ;Load the kernel offset into bx
    mov dh, 42 ;Load 16 sectors 
    mov dl, [BOOT_DRIVE] ;The disk to read from
    call diskload ;Load the kernel
    ret

bits 32
temp: dw 0x0000
KERNEL_OFFSET equ 0x1000
BOOT_DRIVE: db 0x80 ;Read from hard drive

Stage2:
    ;Initialize the FPU
    mov eax, cr0
    or eax, 2 ;Set cr0.mp
    or eax, 1 << 5 ;Set cr0.ne
    and eax, -1 - (4 + 8)
    mov cr0, eax
    fninit
    ;Initialize the GDT, and the stack
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov ebp, 0x90000
    mov esp, ebp

    ;fninit
    ;Kernel entry here
    jmp $
    ;jmp KERNEL_OFFSET ;Call the kernel finally

%include 'boot/printUtils.asm'
%include 'boot/gdt.asm'
%include 'boot/diskload.asm'

HELLO_MSG: db "Booted successfully, loading kernel.", 0xD, 0xA, 0
NO_FPU_MSG: db "No FPU detected!", 0
times 510 - ($ - $$) db 0
dw 0xaa55
boot/diskload.asm

Code: Select all

;Load DH sectors to ES:BX from drive DL
diskload:
    push dx ;Store dx on the stack so we can later recall how many sectors we wanted to read so we can detect errors
    mov ax, 0
    mov es, ax ;Zero es
    mov ds, ax ;Zero ds
    mov ah, 0x02 ;BIOS read sector function
    mov al, dh ;Read DH sectors
    mov ch, 0x0 ;Select cylinder 0
    mov dh, 0x0 ;Select head 0
    mov cl, 0x02 ;Start reading from second sector (i.e. after the boot sector)
    int 0x13 ;BIOS interrupt

    jc diskError ;Jump if error (carry flag is set)

    pop dx ;Restore DX
    cmp dh, al ;if al (sectors read) != dh (sectors expected)
    jne diskError
    ret

diskError:
    mov dl, dh
    mov dh, 0
    mov cl, ah
    mov ax, DISKERRORMSG
    call _printString
    call _printHex
    mov ax, DISKERRORMSG2
    call _printString
    mov ax, DISKERRORMSG3
    call _printString
    mov dx, cx
    call _printHex
    jmp $

DISKERRORMSG db "Disk read error! Loaded ", 0
DISKERRORMSG2 db " Sectors.", 0
DISKERRORMSG3 db " Error code: ", 0
boot/printUtils.asm

Code: Select all

bits 16

;Expects the pointer to the string to be inside of ax
_printString:
    push bx ;Push bx to the stack so it can be restored later
    mov bx, ax ;Move the contents of ax into bx
    mov ah, 0x0e ;Move the interrupt code into ah
_printStringLoop:
    mov al, [bx] ;Move current character at bx into al to print
    inc bx ;increment the counter
    cmp al, 0 ;Compare al to 0, as this string is null terminating
    je _printStringExit ;jump if al is not 0
    int 0x10 ;Print the character
    jmp _printStringLoop
_printStringExit:
    pop bx ;Restore bx
    ret

;Expects the number to be printed to be in ax
;Only prints positive integers
;This code is taken from https://www.youtube.com/watch?v=WNCW39LmC_A&t=1489s&ab_channel=QuesoFuego
_printHex:
    pusha
    mov cx, 0
_printHexLoop:
    cmp cx, 4
    je _printHexEnd

    mov ax, dx
    and ax, 0x000F
    add al, 0x30
    cmp al, 0x39
    jle _moveIntoBX
    add al, 0x7

_moveIntoBX:
    mov bx, hexString + 5
    sub bx, cx
    mov [bx], al
    ror dx, 4
    add cx, 1
    jmp _printHexLoop

_printHexEnd:
    mov ax, hexString
    call _printString
    popa
    ret

hexString: db '0x0000', 0
boot/gdt.asm

Code: Select all

;Table given by http://3zanders.co.uk/2017/10/16/writing-a-bootloader2/
;The data and code segment here will lay on top of each other within the same memory space
;Hardcode this for now. I'll add methods later on to create them from registers
;For real operating systems, use long mode, and use memory paging, according to OSDev
bits 16
gdt_start:
gdt_null:
    dq 0x0
gdt_code_segment:
    dw 0xffff ;Define the lower two bytes of the limit 
    dw 0x0 ;Define the base ptr. This will start at memory address 0
    db 0x0 ;Define the last byte of the base ptr
    db 10011010b ;Define the access byte:
    ;Pr = 1 -> valid sector 
    ;Privil = 00 -> ring 0 access
    ;S = 1 -> this is a code/data segment
    ;E = 1 -> Executable code
    ;D = 0 -> Segment grows up
    ;RW = 1 -> Read and write permissions
    ;AC = 0 -> Always set this to 0
    db 11001111b ;Define the flags, and the last four bits of the limit
    ;GR = 1 -> The limit should be in 4KB blocks 
    ;SZ = 1 -> Set to protected 32 bit mode
    db 0x0 ;Last byte of the base ptr
gdt_data_segment:
    dw 0xffff;Define the lower two bytes of the limit
    dw 0x0 ;Define the base ptr. This will start at memory address 0
    db 0x0 ;Define the last byte of the base ptr
	db 10010010b ;Define the access byte
    ;PR = 1 -> valid sector
    ;Privil = 00 -> ring 0 access
    ;S = 1 -> this is a code/data segment
    ;E = 0 -> this code is not executable 
    ;D = 0 -> Segment grows up
    ;RW = 1 -> Read and write permissions
    db 11001111b ;Define the flags, and the last four bits of the limit
    ;This byte is exactly the same as the code segment
    db 0x0 ;Last byte of the base ptr
gdt_end:
gdt_pointer:
	dw gdt_end - gdt_start - 1;Define the size of the GDT table
	dd gdt_start ;Define the base of the GDT table

CODE_SEG equ gdt_code_segment - gdt_start ;Offset of the code segment
DATA_SEG equ gdt_data_segment - gdt_start ;Offset of the data segment

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 2:34 am
by Octocontrabass
INT 0x13 AH=0x02 can't read across cylinder boundaries. Depending on the BIOS, it may not be able to read across track boundaries either. What's the geometry of the hard drive you're using?

I noticed a couple unrelated problems as well: you're using uninitialized segment registers to access memory, and you're not setting up your own real mode stack. (You don't know what values the BIOS left in the segment registers. You don't know how much stack space the BIOS gave you.)

There may be further issues that I didn't spot. Writing a good bootloader is very difficult.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 4:31 am
by abstractmath
I'm not entirely sure what the geometry of the device is tbh. I would assume it's whatever qemu uses by default when you run the command to launch the emulator.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 4:53 am
by iansjack
If you don't know the geometry of the drive, and can't determine it, would it perhaps be better to use a function that supports LBA addressing? Otherwise your boot loader will only work with a specific hard disk.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 4:56 am
by abstractmath
I don't think it's a matter of not being able to determine the geometry, I just don't know how I would go about determining it. I'm sure Qemu knows the geometry of the hard drive it's emulating. Is there a way to specify the geometry of the hard disk that Qemu emulates?

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 5:14 am
by iansjack
I don't think you understand what I mean. If you ever wanted to run your boot sector on a different machine, or with a different hard disk, the geometry would almost certain be different. A boot loader that only works with one particular computer/drive combination is rather limited. To be useful your program has to determine the geometry (or use LBA). The latter is easier.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 5:17 am
by abstractmath
Ah okay, you're right I wasn't understanding correctly. So, right now I'm just using a BIOS interrupt to read the kernel into memory, so I'm not sure how I would go about using a function that uses LBA instead.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 5:32 am
by abstractmath
It just occurred to me that I may actually want a more advanced bootloader that has a harddisk driver.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 10:13 am
by nexos
abstractmath wrote:It just occurred to me that I may actually want a more advanced bootloader that has a harddisk driver.
If you mean not using the BIOS in the bootloader, don't do it. Use BIOS function INT 13h AH = 42h. If you don't use the BIOS you would have to write an ATA disk driver, which isn't to hard. But to work on newer machines, you would also need a SATA driver, and on the SSD based machines, an NVMe driver, which are very difficult. If that is not what you meant, please disregard what I said :D .

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 10:55 am
by abstractmath
Okay, so I've messed with the bios extension for LBA, and I can't seem to get it to work. I used some code from here: https://wiki.osdev.org/ATA_in_x86_RealMode_(BIOS)

This time, I get an error code of 1, which means that I'm performing an invalid command of some kind, but I'm not sure how to debug this either. I've changed my code as follows:

boot/diskload.asm

Code: Select all

;Load DH sectors to ES:BX from drive DL
diskload:
    push dx ;Store dx on the stack so we can later recall how many sectors we wanted to read so we can detect errors
    mov ax, 0
    mov es, ax ;Zero es
    mov ds, ax ;Zero ds
    mov ah, 0x02 ;BIOS read sector function
    mov al, dh ;Read DH sectors
    mov ch, 0x0 ;Select cylinder 0
    mov dh, 0x0 ;Select head 0
    mov cl, 0x02 ;Start reading from second sector (i.e. after the boot sector)
    int 0x13 ;BIOS interrupt

    jc diskError ;Jump if error (carry flag is set)

    pop dx ;Restore DX
    cmp dh, al ;if al (sectors read) != dh (sectors expected)
    jne diskError
    ret

diskError:
    mov dl, dh
    mov dh, 0
    mov cl, ah
    mov ax, DISKERRORMSG
    call _printString
    call _printHex
    mov ax, DISKERRORMSG2
    call _printString
    mov ax, DISKERRORMSG3
    call _printString
    mov dx, cx
    call _printHex
    jmp $

LBANotSupported:
    mov ax, NOTSUPPORTEDMSG
    call _printString
    jmp $

diskload_lba:
    mov ah, 0x41
    mov bx, 0x55AA
    int 0x13
    jc LBANotSupported
    xor bx, bx
    mov ax, 0
    mov ah, 42
    int 0x13

    jc diskError
    ret

NOTSUPPORTEDMSG db "Lba unsupported", 0
DISKERRORMSG db "Disk read error! Loaded ", 0
DISKERRORMSG2 db " Sectors.", 0
DISKERRORMSG3 db " Error code: ", 0
boot/bootsector.asm

Code: Select all

org 0x7c00
bits 16
mov ax, HELLO_MSG ;Print a simple hello message :D
call _printString
xor ax, ax
;Here, we'll load the kernel into RAM
call LoadKernel
;Enter protected mode
call EnterProtMode

EnterProtMode:
    cli ;Disable interrupts
    lgdt [gdt_pointer] ;Load the GDT register with the start address of the GDT
    mov eax, cr0
    or al, 1 ;Set PE (protection enable) bit in CR0
    mov cr0, eax
    jmp 08h:Stage2 ;Jump to stage 2

LoadKernel:
    ;mov bx, KERNEL_OFFSET ;Load the kernel offset into bx
    ;mov dh, 40 ;Load 16 sectors 
    mov dl, [BOOT_DRIVE] ;The disk to read from
    mov si, DAPACK
    call diskload_lba ;Load the kernel
    ret

DAPACK:
        db 0x10
        db 0
blkcnt: dw 16
db_add: dw 0x7C00
        dw 0
d_lba:  dd 2
        dd 0

bits 32
temp: dw 0x0000
KERNEL_OFFSET equ 0x1000
BOOT_DRIVE: db 0x80 ;Read from hard drive

Stage2:
    ;Initialize the FPU
    mov eax, cr0
    or eax, 2 ;Set cr0.mp
    or eax, 1 << 5 ;Set cr0.ne
    and eax, -1 - (4 + 8)
    mov cr0, eax
    fninit
    ;Initialize the GDT, and the stack
    mov ax, DATA_SEG
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov ebp, 0x90000
    mov esp, ebp

    ;fninit
    ;Kernel entry here
    jmp $
    ;jmp KERNEL_OFFSET ;Call the kernel finally



%include 'boot/printUtils.asm'
%include 'boot/gdt.asm'
%include 'boot/diskload.asm'



HELLO_MSG: db "Booted successfully, loading kernel.", 0xD, 0xA, 0
NO_FPU_MSG: db "No FPU detected!", 0
times 510 - ($ - $$) db 0
dw 0xaa55

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 10:58 am
by sj95126
abstractmath wrote:It just occurred to me that I may actually want a more advanced bootloader that has a harddisk driver.
As nexos said, you really don't want to do that. LBA reads (function 42h) is pretty easy to implement. There are some gotchas to watch for: older BIOSes may not support it, so you have to check for it to be supported first, some BIOSes don't support LBA reads for certain device types, the sector size may vary based on device so you have to take that into account, the number of sectors you can read at once may vary by implementation, and you have to pre-populate a small data structure you pass as an argument, but all of that is better than doing CHS calculations. And it's a LOT easier than driving a device driver in a bootloader.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 11:05 am
by abstractmath
Yes, I've taken this advice into account, but I am having issues actually using the bios extension. I've tried putting something together that uses these extensions, however I am getting an error. Please read my latest post detailing my problems.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 11:54 am
by Octocontrabass
abstractmath wrote:I don't think it's a matter of not being able to determine the geometry, I just don't know how I would go about determining it.
INT 0x13 AH=0x08. (LBA is better though.)
abstractmath wrote:This time, I get an error code of 1, which means that I'm performing an invalid command of some kind,

Code: Select all

    mov ah, 42
INT 0x13 AH=0x2A is not a standard function.

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 11:58 am
by abstractmath
Whoops, okay I changed it to

Code: Select all

mov ah, 0x42
and I'm still getting the same error code

Re: Bios disk read error help

Posted: Tue Oct 13, 2020 12:11 pm
by Octocontrabass
abstractmath wrote:

Code: Select all

db_add: dw 0x7C00
        dw 0
Are you sure that's what you want?