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:
%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:
%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:
%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!