Page 1 of 1

[Solved] BIOS INT 13 throws error after functioning properly

Posted: Thu Apr 21, 2022 5:55 pm
by SimpleTarpShelter
Edit: marking this as solved. The solution was to rewrite the code while giving much more attention to register and operand sizes. Who would have thought

Hi all, I'm writing a custom bootloader and was wondering if I could get some help with an error that the BIOS is giving me, as described by the title of this post. I've successfully implemented stage 1 and loaded stage 2 into memory, and the next step is to load the kernel image into memory so I can begin parsing its ELF header. Here's the code I have so far for stage 2. The error occurs right as I try to read the FAT-formatted drive's root directory table into memory at 0x500 (the hard-coded values in DAPACK are 100% correct, I assure you):

Code: Select all

%define USRMEM          0x500
%define KERNEL_ADDR     0x10000
%define STAGE2_ADDR     0x8000

org STAGE2_ADDR

	jmp main

%include "io.s"
%include "hdd.s"

main:
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov sp, STAGE2_ADDR
	mov si, LoadingMsg
	call print16

	;mov ebx, KERNEL_ADDR
	;mov si, KernelImage
	;call load_image

; DEBUG - Load root dir table at 0x500
DAPACK:
	db 0x10
	db 0
cnt:	dw 0x20    ; 32 entries in root dir
add:	dw 0x500  ; load at 0x500
	dw 0
lba:	dd 0x44    ; 4 reserved + 2 FATs of size 32 each
	dd 0

	mov si, DAPACK
	mov ah, 0x42
	mov dl, [DriveNumber]    ; 0x80, as defined by the BPB
	int 0x13
	jc error

	mov si, DebugMsg
	call print16
	hlt

error:
	mov [0x7000], ah
errl:
	mov si, errmsg
	call print16
	jmp errl
....
You may be thinking to yourself "hey, that's the x86 example code that's in the wiki page about memory access using INT 13!", and you would be correct. I have a separate routine load_image that works perfectly fine when loading stage 2, but as I'm taking it apart step-by-step, the interrupt no longer works even as I try to read in the root directory table into memory. The error code is 1, which is defined as "invalid function in AH or invalid parameter", but I just don't see how that's possible.

Here's my source code for stage 1:

Code: Select all

%define STAGE2_ADDR    0x8000

bits 16
org 0x7c00

	jmp end_bpb
	nop
	times 0x3b db 0x00    ; skip over BIOS parameter block
                              ; BPB macros defined in hdd.s
end_bpb:
	cli
	cld
	jmp 0x00:start      ; some BIOSes try and set cs because they think
                            ; they're smarter than us

%include "hdd.s"
%include "io.s"

start:	
	xor ax, ax
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov sp, 0x7c00

; Load stage 2
	mov ebx, STAGE2_ADDR
	mov si, Stage2Img
	call load_image
	jnc .stage2_init
	mov si, FileNotFoundMsg
	call print16

.die:	hlt    ; load_image failed
	jmp .die

.stage2_init:
	jmp 0x00:STAGE2_ADDR

; Shouldn't ever get here
	jmp .die

; Data
Stage2Img: db "STAGE2  SYS"

	times 510 - ($-$$) db 0x00
	dw 0xaa55    ; boot sig at end of sector

and for hdd.s (which contains load_image):

Code: Select all

%define BPB_ADDR           0x7c00
%define BytesPerSector     BPB_ADDR+0x0b
%define SectorsPerCluster  BPB_ADDR+0x0d
%define ReservedSectors    BPB_ADDR+0x0e
%define NumberOfFATs       BPB_ADDR+0x10
%define RootEntries        BPB_ADDR+0x11
%define SectorsPerFAT      BPB_ADDR+0x16
%define DriveNumber        BPB_ADDR+0x24

; ebx - buffer to write to
; si  - name of binary file to load
; Sets carry on fail
load_image:
	clc
	shr ebx, 4    ; we will be loading into segments, not offsets.
	push ebx
	push si

; Load Root
	xor cx, cx
	mov ax, 0x20
	mul word [RootEntries]
	div word [BytesPerSector]    ; root size = (entries*32) / sector size
	mov [DataSector], ax
	mov [DiskAddressPacket.Count], ax

	xchg ax, cx
	mov ax, [NumberOfFATs]
	mul word [SectorsPerFAT]
	add ax, [ReservedSectors]    ; root loc = reserved + (num FAT * FAT size)
	add [DataSector], ax
	mov [DiskAddressPacket.LBA], ax
	
	mov ax, USRMEMSEG
	mov [DiskAddressPacket.Segment], ax

	call read_sectors

; Find File -- loop through root directory
	mov cx, [RootEntries]
	mov di, USRMEMSEG
	shl di, 4
	pop si
.root_loop:    ; ha
	push cx
	push di
	push si
	mov cx, 0xb
	rep cmpsb
	pop si
	pop di
	pop cx
	je .load_fat
	add di, 0x20
	loop .root_loop
	stc
	pop ebx
	ret
	
.load_fat:
	mov dx, [di+0x1a]    ; save starting cluster
	push dx

; Load FAT table (only 1, since the second is a copy)
	xor ax, ax
	mov ax, 1
	mul word [SectorsPerFAT]
	mov [DiskAddressPacket.Count], ax

	mov ax, [ReservedSectors]
	mov [DiskAddressPacket.LBA], ax

	mov ax, USRMEMSEG
	mov [DiskAddressPacket.Segment], ax

	call read_sectors

; Load Image
.img_loop:
	xor ax, ax
	mov al, [SectorsPerCluster]
	mov [DiskAddressPacket.Count], ax

	pop cx
	mov ax, cx
	call cluster2lba
	mov [DiskAddressPacket.LBA], ax

	pop ebx
	mov [DiskAddressPacket.Segment], bx

	call read_sectors

; Compute next segment
	xor eax, eax
	mov ax, [DiskAddressPacket.Count]
	mul dword [BytesPerSector]
	shr eax, 4
	add ebx, eax
	push ebx

; Compute next cluster	
	shl cx, 1
	mov bx, USRMEMSEG
	shl bx, 4
	add bx, cx
	mov dx, [bx]
	push dx
	shr dx, 3
	cmp dx, 1111111111111b    ; high 13 bits set -> EOF
	jb .img_loop

	pop dx
	pop ebx
	shl ebx, 4
	ret

; Read sectors from disk
; DiskAddressPacket must be properly filled out
; Sets carry on fail
read_sectors:
	mov si, DiskAddressPacket
	mov dl, [DriveNumber]
	mov ah, 0x42
	int 0x13
	ret

; Convert a cluster into a linear block address
; ax = ((ax - 2) * SectorsPerCluster) + DataSector
cluster2lba:
	sub ax, 2
	mul byte [SectorsPerCluster]
	add ax, [DataSector]
	ret
	
; Data
FileNotFoundMsg: db "ERROR: file not found", 0xd, 0xa, 0x0
DataSector: dw 0x00

DiskAddressPacket:
.Size:     db 0x10
.Reserved: db 0x00
.Count:    dw 0x0000
.Offset:   dw 0x0000    ; heuristic: we will never touch this ever
.Segment:  dw 0x0000
.LBA:      dd 0x00000000
.LBAHigh:  dd 0x00000000
Any help would be greatly appreciated! I've done my best to make sure there's nothing wrong with my segment registers or stack--there were a couple issues I found but they didn't solve the bug!

Re: BIOS INT 13 throws error after functioning properly

Posted: Fri Apr 22, 2022 8:55 am
by Klakap
One thing that immediately jump on me is that you are not jumping over DAPACK, so after call print16 processor is going to execute DAPACK, what can cause errors.

Re: BIOS INT 13 throws error after functioning properly

Posted: Fri Apr 22, 2022 9:08 am
by SimpleTarpShelter
Klakap wrote:One thing that immediately jump on me is that you are not jumping over DAPACK, so after call print16 processor is going to execute DAPACK, what can cause errors.
Ah indeed.....I fixed that but it didn't fix the error. Nor did I expect it to though, since the same error occurs when I use DiskAddressPacket as defined in hdd.s. Thanks though!

Re: BIOS INT 13 throws error after functioning properly

Posted: Fri Apr 22, 2022 9:39 am
by linuxyne
If you can reproduce the error on qemu, then there are debugging statements for both qemu's emulation (assuming ATA, though other types of block devices may also support debugging) and seabios's BIOS that can be enabled.

https://www.seabios.org/Debugging

qemu needs an additional command line param: -trace enable=ide_*
In its monitor console, you can check the supported traces: help trace

Edit: With qemu, there's gdb debugging possible too.

Re: BIOS INT 13 throws error after functioning properly

Posted: Fri Apr 22, 2022 11:27 am
by Octocontrabass
SimpleTarpShelter wrote:The error occurs right as I try to read the FAT-formatted drive's root directory table into memory at 0x500
The BDA may occupy a handful of bytes at that address. You should use 0x600 instead.

Code: Select all

	mov ss, ax
	mov sp, STAGE2_ADDR
You have 512 bytes of stack before you start to overwrite stage 1. Aren't you still using data from stage 1 in stage 2?

Code: Select all

	mov si, DebugMsg
	call print16
	hlt

error:
	mov [0x7000], ah
errl:
	mov si, errmsg
	call print16
	jmp errl
If interrupts are enabled (by INT 0x13), this code will always print the error message.

Code: Select all

	mov ax, [NumberOfFATs]
	mul word [SectorsPerFAT]
The BPB field for the number of FATs is a byte. You're loading some extra garbage into AX and then doing calculations that can be affected by it.

Code: Select all

	xor ax, ax
	mov ax, 1
	mul word [SectorsPerFAT]
But why?

Code: Select all

	mul dword [BytesPerSector]
	shr eax, 4
The BPB field for the bytes per sector is a word. You're multiplying by some extra garbage and then doing calculations that can be affected by it.

Code: Select all

; ax = ((ax - 2) * SectorsPerCluster) + DataSector
cluster2lba:
	sub ax, 2
	mul byte [SectorsPerCluster]
The comment is incorrect. MUL with a byte operand uses AL as its implied input operand, not AX.

Re: BIOS INT 13 throws error after functioning properly

Posted: Fri Apr 22, 2022 11:34 am
by SimpleTarpShelter
Octocontrabass wrote:
SimpleTarpShelter wrote:The error occurs right as I try to read the FAT-formatted drive's root directory table into memory at 0x500
The BDA may occupy a handful of bytes at that address. You should use 0x600 instead.

Code: Select all

	mov ss, ax
	mov sp, STAGE2_ADDR
You have 512 bytes of stack before you start to overwrite stage 1. Aren't you still using data from stage 1 in stage 2?

Code: Select all

	mov ax, [NumberOfFATs]
	mul word [SectorsPerFAT]
The BPB field for the number of FATs is a byte. You're loading some extra garbage into AX and then doing calculations that can be affected by it.

Code: Select all

	xor ax, ax
	mov ax, 1
	mul word [SectorsPerFAT]
But why?

Code: Select all

	mul dword [BytesPerSector]
	shr eax, 4
The BPB field for the bytes per sector is a word. You're multiplying by some extra garbage and then doing calculations that can be affected by it.

Code: Select all

; ax = ((ax - 2) * SectorsPerCluster) + DataSector
cluster2lba:
	sub ax, 2
	mul byte [SectorsPerCluster]
The comment is incorrect. MUL with a byte operand uses AL as its implied input operand, not AX.
Thanks for these notes. I decided to rewrite everything from scratch and noticed recognized a lot of what you had pointed out while doing it, and it's working fine now. Not sure where exactly it went wrong though but I consider the issue resolved.

Code: Select all

	xor ax, ax
	mov ax, 1
	mul word [SectorsPerFAT]
Not sure how that slipped in there, ha...