I am working on an assembly kernel, using the OSDev Wiki and a PDF from Nick Blundell. I have set up my GDT and it seems to work fine. I am also able to load the IDTR as well. However, when I try to call sti to reenable interrupts, the system triple faults and crashes. (I use QEmu to run my OS)
Here is the the entire code base:
boot_sect.asm
bits 16
KERNEL_OFFSET equ 0x1000
CODE_SEGMENT equ 0x0ffe
DATA_SEGMENT equ 0x0ffc
org 0x7c00 ; BIOS loads us at this position, offsets all offsets by this value automatically
jmp bootstrap
; GDT
gdt_start: ; The basic flat model, according to Intel Docs
gdt_null: ; Null descriptor
dd 0x0
dd 0x0
gdt_code: ; Code Segment
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10011010b ; Flags for present (1), priviledge (00 : Ring 0), descriptor type (1), code (1), conforming (0), readable (1) and accessed (0)
db 11001111b ; Flags for granularity (1), 32-bit (1, ignored here anyways), 64-bit (0, ignored again), AVL (0, can be used by developer for debugging purposes), Limit (bits 16-19)
db 0x0 ; Base (bits 24-31)
gdt_data: ; Data Segment
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 16-23)
db 10010010b ; Flags for present (1), priviledge (00 : Ring 0), descriptor type (1), code (0), conforming (0), readable (1) and accessed (0)
db 11001111b ; Flags for granularity (1), 32-bit (1, ignored here anyways), 64-bit (0, ignored again), AVL (0, can be used by developer for debugging purposes), Limit (bits 16-19)
db 0x0 ; Base (bits 24-31)
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
; We start off in 16x Real mode, we must first bootstrap the kernel, then switch to 32x protected mode
bootstrap:
mov bx, KERNEL_OFFSET ; Write Kernel to address KERNEL_OFFSET in memory
mov ah, 0x02 ; BIOS read sector operation
mov al, 31 ; No. of sectors
mov ch, 0x00 ; Cylinder 0
mov dh, 0x00 ; Head 0
mov cl, 0x02 ; Start from 2nd sector (BIOS already loaded first one as MBR)
int 0x13 ; Interrupt (BIOS stores where we booted from in dl, we also need to specify which disk to load data from in dl. By not changing it, we are loading from boot disk)
jc bootstrap_failure ; Has the BIOS flagged any errors while reading disk? (If an error occurs, BIOS sets carry flag)
cmp al, 31 ; Have we actually read 31 sectors? (No. of sectors read stored in al)
jne bootstrap_failure
jmp switch_to_32x
bootstrap_failure: ; Alerts the user that bootstrapping failed
mov ah, 0x0e ; BIOS Tele Type Output
mov bx, bootstrap_failure_msg ; Point to start of msg
print_bootstrap_failure:
mov al, [bx] ; Load data at pointer
cmp al, 0x0 ; Check if end of string
je print_bootstrap_failure_end
int 0x10 ; Interrupt
add bx, 1 ; Advance pointer location
jmp print_bootstrap_failure ; Loop
print_bootstrap_failure_end:
jmp $ ; Halt if bootstrap fails
bootstrap_failure_msg:
db "Bootstrap failure, system halted...", 0
switch_to_32x:
cli ; Switch off interrupts for now
lgdt [gdt_descriptor] ; Point to the GDT Descriptor
mov eax, cr0
or eax, 0x1
mov cr0, eax ; Set the bit in cr0 register to complete the switch (Cannot be set directly)
jmp CODE_SEG:init_pm ; Long jump to flush pipeline of processed x16 real mode instructions
bits 32
; Setting up 32x Protected Mode
init_pm:
mov ax, DATA_SEG ; Set segment registers
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x7ffff ; Instantiate stack
mov esp, ebp
mov dx, 0x3d4 ; Disable haradware cursor (We'll be using a software cursor instead)
mov al, 0xa
out dx, al
inc dx
mov al, 0x20
out dx, al
mov [CODE_SEGMENT], WORD CODE_SEG ; Storing GDT Config for the kernel to use
mov [DATA_SEGMENT], WORD DATA_SEG
; Main OS (Now in 32x Protected Mode, calling Kernel
jmp KERNEL_OFFSET
; Padding and MBR Identifier
times 510-($-$$) db 0
dw 0xaa55
kernel.asm
bits 32
CODE_SEGMENT equ 0x0ffe
DATA_SEGMENT equ 0x0ffc
org 0x1000
jmp kernel ; This is the where the boot sector jumps to
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
; Prints a null terminated string, inserting at the second last line and automatically scrolling all earlier text as well
; esp + 36 = style
; esp + 40 = string pointer
print:
pushad
call scroll
mov eax, [esp + 40]
mov ebx, [esp + 36]
push eax
push 0x00000000
push 0x00000017
push ebx
call print_text_at
popad
retn 8
; Prints a null terminated string at a particular position on the screen (Used internally)
; esp + 36 = style
; esp + 40 = y coordinate
; esp + 44 = x coordinate
; esp + 48 = string pointer
print_text_at:
pushad
; edx = VIDEO_MEMORY + 160 * Y + 2 * x
; 160 is written as 2^7 + 2^5 (For performance reasons)
mov edx, VIDEO_MEMORY
mov eax, [esp + 40]
shl eax, 7
add edx, eax
mov eax, [esp + 40]
shl eax, 5
add edx, eax
mov eax, [esp + 44]
shl eax, 1
add edx, eax
mov ebx, [esp + 48]
mov ah, [esp + 36]
print_loop:
mov al, [ebx]
cmp al, 0
je print_done
mov [edx], ax
add ebx, 1
add edx, 2
jmp print_loop
print_done:
popad
retn 16
; Clears the screen
clear:
pushad
mov edx, VIDEO_MEMORY
mov ax, 0x0720 ; Same as ah <- 0x07 (Style) and al <- ' '
clear_loop: ; Essentially a while loop that goes to each cell and sets it to a blank cell
mov [edx], ax
add edx, 2
cmp edx, VIDEO_MEMORY + 3840 ; Not clearing command line
je clear_done
jmp clear_loop
clear_done:
popad
ret
; Clears the command line
clear_cl:
pushad
mov edx, VIDEO_MEMORY
add edx, 3840
mov ax, 0x7020 ; Same as ah <- 0x70 (Style) and al <- ' ', we are inverting the color scheme for stylistic effect
clear_cl_loop:
mov [edx], ax
add edx, 2
cmp edx, VIDEO_MEMORY + 4000
je clear_cl_done
jmp clear_cl_loop
clear_cl_done:
popad
ret
; Scrolls all lines upward, clearing the second last line
scroll:
pushad
mov edx, VIDEO_MEMORY
scroll_loop:
mov al, [edx + 160]
mov [edx], al
add edx, 1
cmp edx, VIDEO_MEMORY + 3680
je clear_last_line
jmp scroll_loop
clear_last_line:
mov ax, 0x0720 ; 0x07 is style while 0x20 represents ' '
clear_last_line_loop:
mov [edx], ax
add edx, 2
cmp edx, VIDEO_MEMORY + 3840
je scroll_done
jmp clear_last_line_loop
scroll_done:
popad
ret
; IDT
; An entry must contain :
; Lower 16 bits of ISR pointer (16 bits)
; Code segment (16 bits)
; Reserved (Should always be zero) (8 bits)
; ISR Attributes (Present (1 bit), DataPrivilegeLevel (2 bits), 0 (1 bit), 16 bit for a value of (0) or 32 bit for a value of (1) (1 bit), 1 (1 bit), Task gate for a value of (0) or not for a value of (1) (1 bit), Task gate if other bit is set otherwise trap gate for a value of (1) or interrupt gate for a value of (0) (1 bit)) (8 bits)
; Higher 16 bits of ISR pointer (16 bits)
idt_start:
times 256 dq 0 ; Reserving space for IDT entries
idt_end:
idt_descriptor:
dw idt_end - idt_start - 1
dd idt_start
initialized_interrupts:
db 0
; Registers interrupts
; esp + 36 = ISR attributes
; esp + 40 = Code segment
; esp + 44 = ISR pointer
register_interrupt:
pushad
; This generates where in memory to write the next registered interrupt's descriptor to the IDT
mov eax, 0
mov al, [initialized_interrupts]
shl eax, 3
add eax, idt_start
mov ebx, [esp + 44]
mov [eax], bx ; Lower 16 bits of ISR pointer
add eax, 2
mov cx, [esp + 40]
mov [eax], cx ; Code segment
add eax, 2
mov dl, [esp + 36]
mov [eax], dl ; ISR Attributes
add eax, 1
mov dl, 0
mov [eax], dl ; Reserved (Zeroed out as specified)
add eax, 1
shr ebx, 16
mov [eax], bx ; Higher 16 bits of ISR pointer
mov al, [initialized_interrupts]
inc al ; Increment the memory location that counts the number of initialized interrupts
mov [initialized_interrupts], al
popad
retn 12
initialize_interrupts:
lidt [idt_descriptor]
sti
ret
kernel:
initialize_interrupts_loop:
mov eax, simple_isr
push eax
mov eax, 0
mov ax, [CODE_SEGMENT]
push eax
mov eax, 0
mov al, 11101110b
push eax
call register_interrupt
mov al, [initialized_interrupts]
cmp al, 255
je finished_registering_interrupts
jmp initialize_interrupts_loop
finished_registering_interrupts:
call initialize_interrupts
; Serves to clear all text printed by BIOS as well as to ready the UI
call clear
call clear_cl
push boot_text
push 0x00000007
call print
jmp $
; End of kernel
simple_isr:
push interrupt_text
push 0x00000007
call print
jmp $ ; I have to implement checks for whether the interrupt is an interrupt or an exception, since we shouldn't return after encountering an exception, i have just made the system hang here
boot_text:
db "OS Online", 0
interrupt_text:
db "An interrupt has occurred", 0
These two files are compiled using nasm and then catted together along with a padding file (Full of 0s) that make the final file 32 sectors (16 kB) long. The system does not triple fault if I comment out the sti instruction. In that state, I went into QEmu's 2nd analog monitor and used info registers to get the value of the idtr, (Usually at 0x10xx). Then I use x /4hx 0x10xx to get the first entry in the IDT. It shows 0x19xx 0008 00ee 0000 (Which is correct, as far as I have seen in the Docs) (0x19xx because the simple_isr label may move around as I cause changes to the code). I use the same simple_isr routine to handle all 256 IRQs, although I don't think that would cause an issue here as I am just getting the system to treat all interrupts as an exception by not returning from the interrupt, if it does occur. Can someone with more experience just double check whether I am doing something wrong? I thank you in advance.
info idtandinfo gdtcommands that allow you to view these structure in a more human readable format. The output ofinfo idtwould have looked wrong and you probably would have realized there was an error. As for the stack alignment you could just change0x7ffffto0x80000