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
| # | Name | Cause | Has Error Code? |
|---|---|---|---|
| 0 | Divide by Zero | Division by zero or overflow | No |
| 6 | Invalid Opcode | Undefined instruction | No |
| 8 | Double Fault | Exception during exception handling | Yes (always 0) |
| 13 | General Protection Fault | Memory access violation, bad segment | Yes |
| 14 | Page Fault | Accessing unmapped memory | Yes (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.