(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)
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
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
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
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