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

Panic!

Now that our panic functions are growing, it’s time to create a dedicated panic.rs instead of leaving everything scattered in lib.rs. We also need to properly display registers on kernel panic, then clear them before halting — leaving no sensitive data lingering in CPU registers.

Capturing the Registers

When the kernel panics via Rust’s panic!() macro, there’s no InterruptFrame being passed in — unlike exception handlers which receive one from the assembly stubs. So we need to capture the current register state ourselves.

The trick is pusha — we let the CPU push all registers onto the stack, then copy them directly into an InterruptFrame struct using rep movsd (repeat move string doubleword), which copies 8 x 4-byte values in one shot:

#![allow(unused)]
fn main() {
pub fn capture_registers() -> InterruptFrame {
    let mut frame = InterruptFrame::new();
    unsafe {
        core::arch::asm!(
            "pusha",
            "mov esi, esp",
            "mov edi, {0}",
            "mov ecx, 8",
            "rep movsd",
            "popa",
            in(reg) &mut frame as *mut InterruptFrame as u32,
            out("edi") _,
            out("ecx") _,
        );
    }
    frame
}
}

pusha dumps all general purpose registers onto the stack. esp then points directly to them, so we use it as the source (esi = esp) and copy 8 dwords into our frame. popa restores everything afterward so execution continues normally.

Note: EIP, CS and EFLAGS are left as zero — EIP isn’t directly readable as a general purpose register, and the others aren’t captured by pusha. For a software panic this is acceptable; hardware exceptions get the real values via the CPU’s automatic stack push.

We tried capturing registers one at a time with individual asm! blocks first, but i386 simply doesn’t have enough free registers to hold all the output values simultaneously — the compiler uses the same registers as scratch and corrupts the values. The pusha approach sidesteps this entirely.

Cleaning the Registers

After displaying the register state, we clear all general purpose registers using XOR operations. XORing a register with itself is the fastest way to zero it and leaves no sensitive data behind:

#![allow(unused)]
fn main() {
pub fn clear_registers() {
    unsafe {
        core::arch::asm!(
            "xor eax, eax",
            "xor ebx, ebx",
            "xor ecx, ecx",
            "xor edx, edx",
            "xor esi, esi",
            "xor edi, edi",
            "xor ebp, ebp",
            options(nomem, nostack)
        );
    }
}
}

Note: we don’t zero ESP — that’s the stack pointer. Zeroing it would instantly corrupt the stack and cause a triple fault before we even reach the loop {}.

Updating kernel_panic

With capture and clear available, kernel_panic becomes a proper crash handler:

#![allow(unused)]
fn main() {
pub fn kernel_panic(msg: &str, frame: &InterruptFrame) -> ! {
    writer().printc("\n\n!!! KERNEL PANIC !!!", Color::White, Color::Red);
    println!("\nError: {}", msg);
    println!("Register state at panic:");
    frame.print();
    clear_registers();
    loop {}
}
}

The #[panic_handler] hooks into this by calling capture_registers() first:

#![allow(unused)]
fn main() {
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    let frame = capture_registers();
    kernel_panic(info.message().as_str().unwrap_or("unknown panic"), &frame);
}
}

This means every Rust panic!() now automatically captures and displays the register state — no manual frame passing needed at the call site.

kernel_warn

For non-fatal errors, kernel_warn just displays a warning and returns — no register dump, no halt:

#![allow(unused)]
fn main() {
pub fn kernel_warn(msg: &str) {
    writer().printc("\n\n!!! KERNEL WARNING !!!", Color::Black, Color::Yellow);
    println!("\nError: {}", msg);
}
}

Testing

Added sys panic to NPS to test both paths:

> sys panic -w

!!! KERNEL WARN !!!

Error: Test


> sys panic -p

!!! KERNEL PANIC !!!

Error: Test

Register state at panic:
  EAX: 0x0025AD7C   EBX: 0x0024AA1C
  ECX: 0x0025AD7C   EDX: 0x00000001
  ESI: 0x00000000   EDI: 0x00001000
  EBP: 0x00000000   ESP: 0x0025AD64
  EIP: 0x00000000   EFL: 0x00000000

The warn returns to the shell. The panic halts the kernel.