1

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.

5
  • 2
    Actually 0x19xx 0008 00ee 0000 is very much incorrect. It should 0x19xx 0008 ee00 0000. Going low to high, it should be low16 of EIP (correct), selector (correct), reserved byte (NOT FLAGS), then flags (NOT RESERVED BYTE), then high16 of EIP (correct). Also you've got misaligned stack (not a fatal error, just very unusal), and misaligned IDT (again, not an error just unusual). Commented Oct 27 at 3:42
  • 1
    Thankyou, that appears to have been the problem. I still have no idea how I managed to mess that up. Commented Oct 27 at 18:16
  • As for the aligning thing, I am just experimenting, I will make it better as I learn more and more about this Commented Oct 27 at 18:22
  • 3
    I'd recommend using BOCHS for this. It has an info idt and info gdt commands that allow you to view these structure in a more human readable format. The output of info idt would have looked wrong and you probably would have realized there was an error. As for the stack alignment you could just change 0x7ffff to 0x80000 Commented Oct 27 at 18:31
  • Isn't 0x80000 Reserved for the EBDA? Can I instead use 0x40000? To align the GDT and IDT, I guess I can copy the data in the GDT to an appropriate location instead of using it from there itself and I can also register the IDT to another location instead of initializing it within the kernel memory area as well, I think? Commented Oct 29 at 1:29

1 Answer 1

2

I had made a mistake in the IDT registry part, where I managed to switch the reserved and flags parts around, fixing it seems to resolve the issue

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.