Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ jobs:
- name: Install QEMU
run: sudo apt-get update && sudo apt-get install -y qemu-system-x86
- name: Build boot image
run: cargo build --features exception-self-test --verbose
run: cargo build --features "exception-self-test divide-error-self-test" --verbose
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = ["kernel"]

[features]
exception-self-test = ["kernel/exception-self-test"]
divide-error-self-test = ["kernel/divide-error-self-test"]

[package]
name = "rustalis"
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The x86_64 interrupt layer now installs a static IDT with handlers for:
- Vector 3: breakpoint (`#BP` / `INT3`), which logs the saved interrupt frame and resumes execution.
- Vector 6: invalid opcode (`#UD`), which logs the faulting instruction pointer and enters the kernel panic path.

The `exception-self-test` Cargo feature triggers `INT3` during boot so the breakpoint path can be exercised without making the normal boot path intentionally fault.
The `exception-self-test` Cargo feature triggers `INT3` during boot so the breakpoint path can be exercised without making the normal boot path intentionally fault. The `divide-error-self-test` feature deliberately executes an integer divide by zero to exercise the #DE path; it is opt-in because the handler intentionally terminates through the kernel panic path.

## Boot invariant

Expand Down
1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0"

[features]
exception-self-test = []
divide-error-self-test = []

[dependencies]
bootloader_api = "0.11.17"
Expand Down
31 changes: 31 additions & 0 deletions kernel/src/arch/x86_64/idt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ static IDT: Once<InterruptDescriptorTable> = Once::new();
pub fn init() {
let idt = IDT.call_once(|| {
let mut idt = InterruptDescriptorTable::new();
idt.divide_error.set_handler_fn(divide_error_handler);
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.invalid_opcode.set_handler_fn(invalid_opcode_handler);
idt
Expand All @@ -29,6 +30,34 @@ pub fn self_test() {
x86_64::instructions::interrupts::int3();
}

/// Trigger a divide-error exception for the optional integration self-test.
///
/// This deliberately executes an integer divide by zero. It must only be
/// called after the IDT has been loaded and only from the self-test path.
#[cfg(feature = "divide-error-self-test")]
pub fn divide_error_self_test() {
// SAFETY: This inline assembly intentionally triggers #DE to verify the
// installed handler. The feature is opt-in and the handler terminates
// through the kernel panic path instead of returning to this instruction.
unsafe {
core::arch::asm!(
"div rcx",
inlateout("rax") 1u64 => _,
inlateout("rdx") 0u64 => _,
in("rcx") 0u64,
options(nostack),
);
}
}

/// Handle integer divide errors as a controlled kernel failure.
extern "x86-interrupt" fn divide_error_handler(frame: InterruptStackFrame) -> ! {
crate::logger::error("DIVIDE ERROR exception (#DE)");
crate::logger::info_u64("Divide error RIP", frame.instruction_pointer.as_u64());
crate::logger::info_u64("Divide error CS", frame.code_segment.bits() as u64);
panic!("divide error exception");
}

/// Handle INT3/#BP without modifying the saved execution state.
extern "x86-interrupt" fn breakpoint_handler(frame: InterruptStackFrame) {
crate::logger::info("BREAKPOINT exception (#BP)");
Expand Down Expand Up @@ -58,9 +87,11 @@ mod tests {
#[test]
fn idt_registers_exception_handlers() {
let mut idt = InterruptDescriptorTable::new();
idt.divide_error.set_handler_fn(divide_error_handler);
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.invalid_opcode.set_handler_fn(invalid_opcode_handler);

assert_ne!(idt.divide_error.handler_addr().as_u64(), 0);
assert_ne!(idt.breakpoint.handler_addr().as_u64(), 0);
assert_ne!(idt.invalid_opcode.handler_addr().as_u64(), 0);
}
Expand Down
6 changes: 6 additions & 0 deletions kernel/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
arch::x86_64::idt::self_test();
logger::info("Breakpoint exception self-test passed");
}
#[cfg(feature = "divide-error-self-test")]
{
logger::info("Running divide-error exception self-test");
arch::x86_64::idt::divide_error_self_test();
logger::info("Divide-error exception self-test passed");
}
let summary = memory::summarize(&boot_info.memory_regions);
logger::info_u64("Memory regions", summary.region_count as u64);
logger::info_u64("Usable memory bytes", summary.usable_bytes);
Expand Down