OS in Rust
double type.On x86, there are about 20 different CPU exception types.
8 and we can define a normal handler function for it in the IDT.unsafe to write to a literal.0xdeadbeef, which causes a page fault.src/interrupts.rs
#![allow(static_mut_refs)]
static mut IDT: x86_64::structures::idt::InterruptDescriptorTable =
x86_64::structures::idt::InterruptDescriptorTable::new();
pub fn init_idt() {
unsafe {
IDT.breakpoint.set_handler_fn(breakpoint_handler);
IDT.load();
}
}
// Love these `cargo fmt` newlines
extern "x86-interrupt" fn breakpoint_handler(
stack_frame: x86_64::structures::idt::InterruptStackFrame,
) {
crate::println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}cargo fmtshould_panic.rs.test/double.rs
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(osirs::_test_runner)]
#[panic_handler]
fn test_panic(_info: &core::panic::PanicInfo) -> ! {
osirs::serial_println!("[Pass]");
osirs::qemu_quit(osirs::QEMU_PASS);
loop {}
}
fn bad() {
assert!(false);
}
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
osirs::_test_runner(&[&bad]);
osirs::qemu_quit(osirs::QEMU_FAIL);
loop {}
}serial_println!init and then modify the panic handler.
badpanictest/should_panic.rs
_starttest/should_panic.rs
src/lib.rs0xdeadbeef, which causes a page fault.A double fault is a special exception that occurs when the CPU fails to invoke an exception handler.
a double fault exception can occur when a second exception occurs during the handling of a prior (first) exception handler
| First Exception | Second Exception |
|---|---|
| Divide-by-zero, Invalid TSS, Segment Not Present, Stack-Segment Fault, General Protection Fault |
Invalid TSS, Segment Not Present, Stack-Segment Fault, General Protection Fault |
| Page Fault | Page Fault, Invalid TSS, Segment Not Present, Stack-Segment Fault, General Protection Fault |
Let’s look at the fourth question:
What happens if our kernel overflows its stack and the guard page is hit?
option would’ve been great here!_startstack_pointers field in the corresponding IDT entry.| Field | Type |
|---|---|
| (reserved) | u32 |
| Privilege Stack Table | [u64; 3] |
| (reserved) | u64 |
| Interrupt Stack Table | [u64; 7] |
| (reserved) | u64 |
| (reserved) | u16 |
| I/O Map Base Address | u16 |
x86_64 crate which contains a TaskStateSegment struct.src/gdt.rs
pub mod this in src/lib.rsinit function too.static mut to solve a problem wherein a language-level alias is used for actually existing, persistent, mutable hardware.static mut (which requires unsafe)src/gdt.rs
src/gdt.rs
#![allow(static_mut_refs)]
// GDT
static mut GDT: x86_64::structures::gdt::GlobalDescriptorTable =
x86_64::structures::gdt::GlobalDescriptorTable::new();
// TSS
const STACK_SIZE: usize = 4096 * 5;
static mut TSS: x86_64::structures::tss::TaskStateSegment =
x86_64::structures::tss::TaskStateSegment::new();
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
pub const DOUBLE_FAULT_IST_INDEX: usize = 0;
pub fn init_gdt() {
unsafe {
TSS.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] =
x86_64::VirtAddr::from_ptr(&raw const STACK) + STACK_SIZE;
let kcs = GDT.add_entry(x86_64::structures::gdt::Descriptor::kernel_code_segment());
let tss = GDT.add_entry(x86_64::structures::gdt::Descriptor::tss_segment(&TSS));
GDT.load();
use x86_64::instructions::segmentation::Segment;
x86_64::instructions::segmentation::CS::set_reg(kcs);
x86_64::instructions::tables::load_tss(tss);
}
}