I have a simplistic bootloader comprised of two parts – the actual bootloader that loads the second ‘stage’ into memory, and executes it.
In that second stage, I’m trying to switch to 32-bit protected mode, but I end up with a triple-fault, with the following error in bochs:
Below is the code for both boot assembly files, as well as the Makefile that I am compiling them with, in case there’s some error there.
From what I can tell, there seems to be some issue with the descriptors in the GDT, but I’ve checked them multiple times and they seem to be correct. I wonder if it’s something to do with loading the code into memory at a different address than expected maybe? I’m unsure. Any advice would be greatly appreciated!
Makefile
BUILD_DIR?=build
FLOPPY_OUTPUT?=$(BUILD_DIR)/floppy.img
.PHONY: all bootloader kernel run-bochs run-qemu build-dir
all: $(FLOPPY_OUTPUT)
run-bochs: all
bochs -f bochsrc.txt
run-qemu: all
qemu-system-i386 -monitor stdio -drive file=$(FLOPPY_OUTPUT),format=raw
# TODO: Make the drive a hard drive with FAT32.
$(FLOPPY_OUTPUT): build-dir bootloader kernel
dd if=/dev/zero of=$(FLOPPY_OUTPUT) bs=512 count=2880
mkfs.fat -F12 -n "AURORAOS" $(FLOPPY_OUTPUT)
dd if=$(BUILD_DIR)/boot.bin of=$(FLOPPY_OUTPUT) conv=notrunc
mcopy -i $(FLOPPY_OUTPUT) build/lstage.bin "::lstage.bin"
bootloader: build-dir bootloader/boot.asm bootloader/late_stage/lstage.asm
nasm -f bin bootloader/boot.asm -o $(BUILD_DIR)/boot.bin
nasm -felf bootloader/late_stage/lstage.asm -o $(BUILD_DIR)/lstage.o
ld -melf_i386 -T bootloader/late_stage/linker.ld $(BUILD_DIR)/lstage.o -o $(BUILD_DIR)/lstage.bin --oformat=binary
kernel:
build-dir:
mkdir -p $(BUILD_DIR)
linker.ld
ENTRY(_start)
SECTIONS
{
. = 0x0000;
.text : {
*(.text)
}
.rodata : {
*(.rodata)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
lstage.asm
– Where the triple fault occurs:
[bits 16]
global _start
_start:
mov si, msg_hello
call puts
cli
call enable_a20
call load_gdt
mov eax, cr0
or eax, 1
mov cr0, eax
jmp dword CODE_SEGMENT:pmode_start
puts:
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp .loop
.done:
ret
enable_a20:
; DataPort = 60
; CommandPort = 64
; DisableKeyboard = AD
; EnableKeyboard = AE
; ReadCtrl = D0
; WriteCtrl = D1
call a20_wait_input
mov al, 0xAD ; Disable keyboard
out 0x64, al
call a20_wait_input
mov al, 0xD0 ; Read control output port
out 0x64, al
call a20_wait_output
in al, 0x60
push eax
call a20_wait_input
mov al, 0xD1
out 0x64, al
call a20_wait_input
pop eax
or al, 2
out 0x60, al
call a20_wait_input
mov al, 0xAE ; Enable keyboard
out 0x64, al
call a20_wait_input
ret
a20_wait_input:
push ax
in al, 0x64
test al, 2
jnz a20_wait_input
pop ax
ret
a20_wait_output:
push ax
in al, 0x64
test al, 1
jz a20_wait_output
pop ax
ret
load_gdt:
lgdt [gdt_descriptor]
ret
gdt_start:
null_descriptor: dq 0
code_descriptor: dw 0xFFFF ; Limit (0-15)
dw 0x0000 ; Base (0-15)
db 0x0 ; Base (16-23)
db 10011010b ; Access
db 11001111b ; Flags + Limit (16-19)
db 0x0 ; Base (24-31)
data_descriptor: dw 0xFFFF ; Limit (0-15)
dw 0x0000 ; Base (0-15)
db 0x0 ; Base (16-23)
db 10010010b ; Access
db 11001111b ; Flags + Limit (16-19)
db 0x0 ; Base (24-31)
gdt_end:
gdt_descriptor: dw gdt_descriptor - gdt_start - 1
dd gdt_start
CODE_SEGMENT equ code_descriptor - gdt_start
DATA_SEGMENT equ data_descriptor - gdt_start
msg_hello: db "Hello from late-stage boot code!", 0x0D, 0x0A, 0x00
[bits 32]
pmode_start:
cli
hlt
boot.asm
– The actual bootloader that loads lstage.bin
into memory.
[org 0x7C00]
[bits 16]
jmp strict short _start
nop
oem_identifier: db "MSWIN4.1"
bytes_per_sector: dw 0x0200 ; 512
sectors_per_cluster: db 0x01
reserved_sectors: dw 0x0001
fat_count: db 0x02
root_dir_entries: dw 0x00E0
total_sectors: dw 0x0B40 ; 2880 * 512 = 1.44MB
media_descriptor_type: db 0xF0 ; 3.5" floppy
sectors_per_fat: dw 0x0009
sectors_per_track: dw 0x0012 ; 18
head_count: dw 0x0002
hidden_sectors: dd 0x0000 0000
large_sector_count: dd 0x0000 0000
; Extended Boot Partition
drive_number: db 0x00 ; 0x00 = floppy, 0x80 = hard disk
_reserved: db 0x00
signature: db 0x29
serial_number: dd 0x12345678 ; Unused
volume_label: db "AURORAOS "
system_ident_string: db "FAT12 "
_start:
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
mov [drive_number], dl
; 07C0:0000 -> 0000:07C0
push es
push word .after
retf
.after:
mov si, msg_hello
call puts
; Read drive params rather than relying on hard-coded values
; INT 13/AH=08h -> http://www.ctyme.com/intr/rb-0621.htm
push es
mov ah, 0x08
int 0x13
jc floppy_error
pop es
; CH = low eight bits of max cylinder
; CL [bits 7-6] = high two bits of max cylinder
; CL [bits 5-0] = max sector number
; DH = max head number
; DL = number of drives
and cl, 0x3F
xor ch, ch
; Now, cx is just sector count, as we removed bits [7-0]
mov [sectors_per_track], cx
; Heads are indexed from 0, so max head number
; is one less than the count of heads.
inc dh
mov [head_count], dh
; Compute LBA of root dir = reserved + fats * sectors per fat
mov ax, [sectors_per_fat]
mov bl, [fat_count]
xor bh, bh
mul bx
add ax, [reserved_sectors]
push ax ; LBA
; Compute sector size of root dir = (32 * number of entries) / bytes_per_sector
mov ax, [root_dir_entries]
shl ax, 5
xor dx, dx
div word [bytes_per_sector]
test dx, dx
jz .root_dir_size_no_inc
inc ax ; % bytes_per_sector > 0 => we need to round up
.root_dir_size_no_inc:
mov cl, al ; cl = # of sectors to read
pop ax ; LBA
mov dl, [drive_number]
mov bx, buffer
call disk_read
; Search for LSTAGE BIN
xor bx, bx
mov di, buffer
.search_lstage:
mov si, file_lstage_bin
mov cx, 11 ; 11 bytes
push di
repe cmpsb
pop di
je .found_lstage
add di, 32
inc bx
cmp bx, [root_dir_entries]
jl .search_lstage
jmp lstage_not_found_error
.found_lstage:
; di has address to the entry
mov ax, [di + 26] ; First logical cluster field at offset 26
mov [lstage_cluster], ax
; Load first FAT into memory
mov ax, [reserved_sectors]
mov bx, buffer
mov cl, [sectors_per_fat]
mov dl, [drive_number]
call disk_read
mov bx, LSTAGE_LOAD_SEGMENT
mov es, bx
mov bx, LSTAGE_LOAD_OFFSET
.load_lstage_loop:
mov ax, [lstage_cluster]
; TODO: unhardcode this
add ax, 31 ; ((lstage_cluster - 2) * sectors_per_cluster) + reserved + fats + root_dir_size
mov cl, 1
mov dl, [drive_number]
call disk_read
add bx, [bytes_per_sector]
mov ax, [lstage_cluster]
mov cx, 3
mul cx
mov cx, 2
div cx ; ax = index of entry in FAT, dx = cluster % 2
mov si, buffer
add si, ax
mov ax, [ds:si] ; read entry from FAT table at index ax
or dx, dx
jz .even
; Align and get 12 bits (1.5 bytes)
.odd:
shr ax, 4
jmp .next_cluster_after
.even:
and ax, 0x0FFF
.next_cluster_after:
cmp ax, 0x0FF8 ; end of chain
jge .read_finish
mov [lstage_cluster], ax
jmp .load_lstage_loop
.read_finish:
mov dl, [drive_number]
mov ax, LSTAGE_LOAD_SEGMENT
mov ds, ax
mov es, ax
jmp LSTAGE_LOAD_SEGMENT:LSTAGE_LOAD_OFFSET
jmp halt
halt:
cli
hlt
jmp halt
lstage_not_found_error:
mov si, msg_lstage_not_found
call puts
jmp halt
floppy_error:
mov si, msg_floppy_error
call puts
jmp halt
;
; Convert LBA to CHS
;
; Parameters:
; - ax: LBA
; Returns:
; - cx [bits 0-5]: sector number
; - cx [bits 6-15]: cylinder
; - dh: head
;
lba_to_chs:
push ax
push dx
xor dx, dx ; dx = 0
div word [sectors_per_track] ; ax = LBA / sectors_per_track
; dx = LBA % sectors_per_track
inc dx ; dx = sector
mov cx, dx
xor dx, dx
div word [head_count] ; ax = (LBA / sectors_per_track) / heads = cylinder
; dx = (LBA / sectors_per_track) % heads = head
mov dh, dl ; dh = head
mov ch, al ; cylinder [15-8]
shl ah, 6
or cl, ah ; last 2 bits
pop ax
mov dl, al
pop ax
ret
;
; Read sectors from disk
;
; Parameters:
; - ax: LBA
; - cl: number of sectors to read
; - dl: drive number
; - es:bx: buffer
;
disk_read:
pusha
push cx
call lba_to_chs
pop ax ; al = sectors to read
mov ah, 0x02
mov di, 0x05 ; retry count
.retry:
pusha
stc
int 0x13
jnc .done
popa
call disk_reset
dec di
test di, di
jnz .retry
.fail:
jmp floppy_error
.done:
popa
popa
ret
;
; Reset disk controller
;
; Parameters:
; - dl: drive number
;
disk_reset:
pusha
mov ah, 0x00
stc
int 0x03
jc floppy_error
popa
ret
puts:
pusha
.loop:
lodsb
or al, al
jz .done
mov ah, 0x0E
int 0x10
jmp .loop
.done:
popa
ret
msg_hello: db "Hello, world!", 0x0D, 0x0A, 0x00
msg_floppy_error: db "Read error.", 0x0D, 0x0A, 0x00
msg_lstage_not_found: db "LSTAGE.BIN not found.", 0x0D, 0x0A, 0x00
lstage_cluster: dw 0x0000
LSTAGE_LOAD_SEGMENT equ 0x2000
LSTAGE_LOAD_OFFSET equ 0x0000
file_lstage_bin: db "LSTAGE BIN"
times 510-($-$$) db 0
dw 0AA55h
buffer: