1

I'm encountering a double fault in my kernel when loading the Interrupt Descriptor Table (IDT). The interrupt handler outputs the following error:

Got Signal: Double Fault (4144)

Since a double fault should always have an error code of 0, the non-zero value 4144 suggests there may be issues with my ISR routines.

Relevant Code

kinit.S

[extern kmain]
[extern kernel_stack]
[global _start]

KERNEL_STACK_SIZE equ 4096
DATA_SEG equ 0x10

section .kinit

_start:
  mov ax, DATA_SEG
  mov ds, ax
  mov es, ax
  mov ss, ax
  lea ebp, [kernel_stack + KERNEL_STACK_SIZE] 
  mov esp, ebp

  cld
  call kmain

  jmp $

kmain.c

#include <stddef.h>
#include <stdint.h>

#include "idt.h"
#include "vga_screen.h"

#define KERNEL_STACK_SIZE 4096
__attribute__((aligned(16))) uint8_t kernel_stack[KERNEL_STACK_SIZE];

void kmain() {
  clear_screen();
  init_idt();
  for (;;);
}

isr.S

[extern irq_handler]
[global isr0]
[global isr1]
[global isr2]
[global isr3]
[global isr4]
[global isr5]
[global isr6]
[global isr7]
[global isr8]
[global isr9]
[global isr10]
[global isr11]
[global isr12]
[global isr13]
[global isr14]
[global isr15]
[global isr16]
[global isr17]
[global isr18]
[global isr19]
[global isr20]
[global isr21]
[global isr22]
[global isr23]
[global isr24]
[global isr25]
[global isr26]
[global isr27]
[global isr28]
[global isr29]
[global isr30]
[global isr31]

isr_common_stub:
    pusha

    mov ax, ds
    push eax

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    cld

    lea eax, [esp]
    push eax
    call irq_handler
    add esp, 4

    pop eax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    popa

    add esp, 8
    iret

%macro isr_with_err 1
isr%+%1:
    push %1
    jmp isr_common_stub
%endmacro

%macro isr_no_err 1
isr%+%1:
    push 0
    push %1
    jmp isr_common_stub
%endmacro

; Division Error
isr_no_err 0
; Debug
isr_no_err 1
; Non-Maskable Interrupt
isr_no_err 2
; Breakpoint
isr_no_err 3
; Overflow
isr_no_err 4
; Bound Range Exceeded
isr_no_err 5
; Invalid Opcode
isr_no_err 6
; Device Not Available
isr_no_err 7
; Double Fault
isr_with_err 8
; Coprocessor Segment Overrun (Deprecated)
isr_no_err 9
; Invalid TSS
isr_with_err 10
; Segment Not Present
isr_with_err 11
; Stack-Segment Fault
isr_with_err 12
; General Protection Fault
isr_with_err 13
; Page Fault
isr_with_err 14
; Reserved
isr_no_err 15
; x87 Floating-Point Exception
isr_no_err 16
; Alignment Check
isr_with_err 17
; Machine Check
isr_no_err 18
; SIMD Floating-Point Exception
isr_no_err 19
; Virtualization Exception
isr_no_err 20
; Control Protection Exception
isr_with_err 21
; Reserved
isr_no_err 22
; Reserved
isr_no_err 23
; Reserved
isr_no_err 24
; Reserved
isr_no_err 25
; Reserved
isr_no_err 26
; Reserved
isr_no_err 27
; Hypervisor Injection Exception
isr_no_err 28
; VMM Communication Exception
isr_with_err 29
; Security Exception
isr_with_err 30
; Reserved
isr_no_err 31

idt.c

#include "idt.h"

#include <stdint.h>

#include "irq_handler.h"

#define CODE_SEG 0x8

__attribute__((aligned(16))) idt_entry_t idt[IDT_ENTRIES_COUNT];
idt_register_t idt_reg;

idt_entry_t make_idt_entry(uintptr_t irq_handler_addr) {
  return (idt_entry_t){.low_addr = irq_handler_addr & 0xFFFF,
                       .selector = CODE_SEG,
                       .always0 = 0,
                       .flags = 0x8e,
                       .high_addr = irq_handler_addr >> 16};
}

void load_idt() {
  idt_reg.base = (uintptr_t)&idt;
  idt_reg.limit = IDT_ENTRIES_COUNT * sizeof(idt_entry_t) - 1;

  __asm__ __volatile__("lidtl (%0)" : : "r"(&idt_reg));
  __asm__ __volatile__("sti");
}

void init_idt() {
  install_irq_handlers();
  load_idt();
}

the interrupt frame struct

typedef struct {
  uint32_t user_ds;
  uint32_t edi;
  uint32_t esi;
  uint32_t ebp;
  uint32_t ignored_val;
  uint32_t ebx;
  uint32_t edx;
  uint32_t ecx;
  uint32_t eax;
  uint32_t int_num;
  uint32_t err_code;
  uint32_t eip;
  uint32_t cs;
  uint32_t eflags;
} interrupt_frame_t;

For a minimal reproducible example, please refer to my GitHub repository: SafeMemoryZone/ssos

Question

Why am I receiving a double fault with a non-zero error code when loading the IDT, and how can I fix potential issues in my ISR routines?

2
  • 4
    You are not getting a double fault. You are getting a timer interrupt which is by default mapped to irq #8. You should remap the hardware interrupts. See also osdev, in particular: "Some interrupts mapped by the 8259 by default overlap with some of the processor's exception handlers. These can be remapped via the 8259's IO ports." Commented Nov 27, 2024 at 12:27
  • 2
    related: How does the BIOS distinguish Interrupt(08h-12h) from INT instructions, vs. actual exceptions inside the CPU? re: the bad design decision in the IBM-PC of using interrupt numbers that Intel's 8086 manual documented as reserved for future use. Like 8 for double-fault, which wasn't a thing in 8086 but was later. (@Jester) Commented Nov 27, 2024 at 19:58

0

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.