Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Exception Handlers

Exception handlers catch CPU faults before they become triple faults.

Why They’re Critical

Without exception handlers:

CPU exception occurs (e.g. divide by zero)
    ↓
No handler → CPU fires interrupt 8 (Double Fault)
    ↓
No double fault handler → CPU fires interrupt 8 again
    ↓
Triple Fault → CPU resets → Machine reboots

With exception handlers:

CPU exception occurs
    ↓
Handler catches it
    ↓
Display error message in red
    ↓
Halt gracefully (no reboot!)

The difference between “the kernel crashed and showed an error” and “the kernel crashed and silently rebooted” is entirely the exception handlers.

Common Exceptions

#NameCauseHas Error Code?
0Divide by ZeroDivision by zero or overflowNo
6Invalid OpcodeUndefined instructionNo
8Double FaultException during exception handlingYes (always 0)
13General Protection FaultMemory access violation, bad segmentYes
14Page FaultAccessing unmapped memoryYes (CR2 = fault address)

Implementation

Rust Handlers

#![allow(unused)]
fn main() {
unsafe fn write_error(row: usize, msg: &str, color: u8) {
    for (i, byte) in msg.bytes().enumerate() {
        let offset = (row * 80 + i) * 2;
        *VGA_BUFFER.add(offset)     = byte;
        *VGA_BUFFER.add(offset + 1) = color;
    }
}

#[no_mangle]
pub extern "C" fn rust_general_protection_fault() {
    unsafe {
        write_error(10, "EXCEPTION #13: GENERAL PROTECTION FAULT", 0x4F);
        loop {}
    }
}
}

Colour 0x4F = white text on red background. Unmistakable.

Assembly Wrappers

Exceptions with error codes require the wrapper to pop the error code off the stack before calling the Rust handler:

; Exception #13: General Protection Fault (has error code)
general_protection_fault_handler:
    cli
    add esp, 4      ; Remove error code pushed by CPU
    pusha
    call rust_general_protection_fault
    popa
    iretd

Why remove the error code?

When an exception has an error code, the CPU pushes it onto the stack before the return address. Our Rust handler uses the C calling convention, which doesn’t expect this extra value. If we don’t remove it, the stack is misaligned and the handler reads garbage as its “return address.”

Exceptions without error codes (like divide by zero) don’t need this:

divide_by_zero_handler:
    cli
    pusha
    call rust_divide_by_zero
    popa
    iretd

Installing in the IDT

#![allow(unused)]
fn main() {
IDT.entries[0].set_handler(divide_by_zero_handler);
IDT.entries[6].set_handler(invalid_opcode_handler);
IDT.entries[8].set_handler(double_fault_handler);
IDT.entries[13].set_handler(general_protection_fault_handler);
IDT.entries[14].set_handler(page_fault_handler);
}

These must be installed before enabling interrupts. The double fault handler is especially important — without it, any exception causes a second exception which triple-faults.