I'm developing a boot loader, which will boot into a simple kernel after switching into protected mode. I used this paper as a tutorial, somewhere in chapter four or five. In theory it is supposed to start in 16-bit real mode, load the kernel into memory, switch to 32-bit protected mode and start executing the kernel code.
However, when I switch into protected mode and far jump or jump to another segment, it triple faults. Here is the main boot sector code:
[org 0x7c00]
KERNEL_OFFSET equ 0x1000
mov [BOOT_DRIVE], dl ;Get the current boot drive from the BIOS
mov bp, 0x9000 ;Set up stack, with enough room to grow downwards
mov sp, bp
mov bx, REAL_MODE_MSG
call print_string
call load_kernel
call switch_to_pm
jmp $ ;Jump to current position and loop forever
%include "boot/util/print_string.asm"
%include "boot/util/disk.asm"
%include "boot/gdt/gdt.asm"
%include "boot/util/print_string_pm.asm"
%include "boot/switch_to_pm.asm"
[bits 16]
load_kernel:
mov bx, LOAD_KERNEL_MSG ;Print a message saying we are loading the kernel
call print_string
mov bx, KERNEL_OFFSET ;Set up disk_load routine parameters
mov dh, 15
mov dl, [BOOT_DRIVE]
call disk_load ;Call disk_load
ret
[bits 32]
BEGIN_PM:
mov ebx, PROT_MODE_MSG
call print_string_pm
call KERNEL_OFFSET
jmp $
; Data
BOOT_DRIVE: db 0
REAL_MODE_MSG: db "Started in real mode.", 0
PROT_MODE_MSG: db "Successfully entered 32-bit protected mode.", 0
LOAD_KERNEL_MSG: db "Loading Kernel into memory", 0
; Bootsector padding
times 510-($-$$) db 0
dw 0xaa55
Here is the GDT:
;Global Descriptor Table
gdt_start:
gdt_null: ; We need a null descriptor at the start (8 bytes)
dd 0x0
dd 0x0
gdt_code: ; Code segment descriptor
; Base=0x0, Limit=0xfffff
; 1st flags : (present)1 (privilege)00 (descriptor type)1 -> 1001b
; type flags : (code)1 (conforming)0 (readable)1 (accessed)0 -> 1010b
; 2nd flags : (granularity)1 (32 - bit default)1 (64 - bit seg)0 (AVL)0 -> 1100b
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (0-15)
dw 0x0 ; Base (16-23)
db 10011010b ; 1st flags and type flags
db 11001111b ; 2nd flags and Limit (16-19)
db 0x0 ; Base (24-31)
gdt_data: ; Data segment descriptor
;Same as CSD except for type flags
; (code)0 (expand down)0 (writable)1 (accessed)0 -> 0010b
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (0-15)
dw 0x0 ; Base (16-23)
db 10010010b ; 1st flags and type flags
db 11001111b ; 2nd flags and Limit (16-19)
db 0x0 ; Base (24-31)
gdt_end:
;GDT Descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
;Some Constants
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
Here is the code for switching into protected mode, where it triple faults:
[bits 16]
switch_to_pm:
cli
lgdt [gdt_descriptor] ; load the gdt
mov eax, cr0 ; turn pm on
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm ; THIS IS WHERE THE PROBLEM IS!
[bits 32]
init_pm:
mov ax, DATA_SEG ; Point segment registers to the data
mov ds, ax ; selector defined in the gdt
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x90000 ; Update our stack
mov esp, ebp
call BEGIN_PM ;Move on
When I place a jmp $
instruction to idle at a certain spot, right before the jmp CODE_SEG:init_pm
instruction, it idles there and does not triple fault. When I place it after that instruction, within the label init_pm
, it does triple fault. So I am fairly sure that it is the cause. I'm not too sure why, maybe it's an issue with the GDT. I am new to operating system development and boot loaders. Any suggestions on how to solve this problem?