From 431bb39fdb23b6167a486fc0c744789f1f5a3b29 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 13 May 2016 15:54:19 +0200 Subject: [PATCH 01/23] Begin work for exceptions post --- blog/post/exceptions.md | 19 +++++++++++++++++++ src/interrupts/idt.rs | 26 ++++++++++++++++++++++++++ src/interrupts/mod.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 ++++++++++ 4 files changed, 96 insertions(+) create mode 100644 blog/post/exceptions.md create mode 100644 src/interrupts/idt.rs create mode 100644 src/interrupts/mod.rs diff --git a/blog/post/exceptions.md b/blog/post/exceptions.md new file mode 100644 index 00000000..8f4bac82 --- /dev/null +++ b/blog/post/exceptions.md @@ -0,0 +1,19 @@ ++++ +title = "CPU Exceptions" +date = "2016-05-10" ++++ + +## Interrupts +Whenever a device (e.g. the keyboard contoller) needs + +## Exceptions +An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception when it should divide by 0. When an exception occurs, the CPU immediately calls a specific exception handler function, depending on the exception type. + +We've already seen several types of exceptions in our kernel: + +- **Illegal instruction**: TODO +- **Page Fault**: The CPU tried to perform an illegal read or write. +- **Double Fault**: TODO +- **Triple Fault**: + +The full list of diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs new file mode 100644 index 00000000..b8c68197 --- /dev/null +++ b/src/interrupts/idt.rs @@ -0,0 +1,26 @@ +use x86::irq::IdtEntry; + +pub struct Idt([IdtEntry; 16]); + +impl Idt { + pub fn new() -> Idt { + Idt([IdtEntry::missing(); 16]) + } + + pub fn set_handler(&mut self, entry: usize, handler: extern fn()->!) { + let ptr = handler as usize; + self.0[entry] = IdtEntry::interrupt_gate(0x8, ptr as *const _); + } + + pub unsafe fn load(&self) { + use x86::dtables::{DescriptorTablePointer, lidt}; + use core::mem::size_of; + + let ptr = DescriptorTablePointer{ + base: self as *const _ as u64, + limit: (size_of::() - 1) as u16, + }; + + lidt(&ptr); + } +} diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs new file mode 100644 index 00000000..95b8711d --- /dev/null +++ b/src/interrupts/mod.rs @@ -0,0 +1,41 @@ +mod idt; + +lazy_static! { + static ref IDT: idt::Idt = { + let mut idt = idt::Idt::new(); + + idt.set_handler(0, divide_by_zero_handler); + idt.set_handler(8, double_fault_handler); + idt.set_handler(13, general_protection_fault_handler); + idt.set_handler(14, page_fault_handler); + + idt + }; +} + +pub fn init() { + assert_has_not_been_called!(); + + unsafe { IDT.load() } +} + + +pub extern fn divide_by_zero_handler() -> ! { + println!("EXCEPTION: DIVIDE BY ZERO"); + loop {} +} + +pub extern fn double_fault_handler() -> ! { + println!("EXCEPTION: DOUBLE FAULT"); + loop {} +} + +pub extern fn general_protection_fault_handler() -> ! { + println!("EXCEPTION: GENERAL PROTECTION FAULT"); + loop {} +} + +pub extern fn page_fault_handler() -> ! { + println!("EXCEPTION: PAGE FAULT"); + loop {} +} diff --git a/src/lib.rs b/src/lib.rs index 9b131ead..a0c75964 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ #![feature(lang_items)] #![feature(const_fn, unique)] #![feature(alloc, collections)] +#![feature(asm)] #![no_std] extern crate rlibc; @@ -29,6 +30,7 @@ extern crate collections; #[macro_use] mod vga_buffer; mod memory; +mod interrupts; #[no_mangle] pub extern "C" fn rust_main(multiboot_information_address: usize) { @@ -50,6 +52,14 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { format!("Some String"); } + interrupts::init(); + + unsafe { *(0xdeadbeaf as *mut u32) = 42}; + + unsafe { + asm!("xor eax, eax; idiv eax" :::: "intel"); + } + println!("It did not crash!"); loop {} From 78655d6bef85b325cc056d01d0124009b9734a0c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 23 May 2016 09:01:52 +0200 Subject: [PATCH 02/23] Add bit_field dependency --- Cargo.toml | 6 +++++- src/lib.rs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d0cb6f3c..a6358aa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,14 @@ name = "blog_os" version = "0.1.0" [dependencies] +bitflags = "0.7.0" once = "0.2.1" rlibc = "0.1.4" spin = "0.3.4" -bitflags = "0.7.0" + +[dependencies.bit-field] +git = "https://github.com/phil-opp/rust-bit-field.git" +optional = false [dependencies.hole_list_allocator] path = "libs/hole_list_allocator" diff --git a/src/lib.rs b/src/lib.rs index a0c75964..7b7fa9a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,8 @@ extern crate alloc; #[macro_use] extern crate collections; +extern crate bit_field; + #[macro_use] mod vga_buffer; mod memory; From 74f33c0f44e3991c4fbca42227b9a91d8807ec2e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 23 May 2016 09:02:32 +0200 Subject: [PATCH 03/23] Use own Itd entry type --- src/interrupts/idt.rs | 92 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index b8c68197..93ab6d75 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -1,22 +1,24 @@ -use x86::irq::IdtEntry; -pub struct Idt([IdtEntry; 16]); +pub struct Idt([Entry; 16]); impl Idt { pub fn new() -> Idt { - Idt([IdtEntry::missing(); 16]) + Idt([Entry::missing(); 16]) } - pub fn set_handler(&mut self, entry: usize, handler: extern fn()->!) { - let ptr = handler as usize; - self.0[entry] = IdtEntry::interrupt_gate(0x8, ptr as *const _); + pub fn set_handler(&mut self, entry: usize, handler: extern "C" fn() -> !) { + self.0[entry] = Entry::new(EntryType::InterruptGate, 0x8, handler); } - pub unsafe fn load(&self) { + pub fn set_interrupt_stack(&mut self, entry: usize, stack_index: u16) { + self.0[entry].options.set_stack_index(stack_index); + } + + pub unsafe fn load(&'static self) { use x86::dtables::{DescriptorTablePointer, lidt}; use core::mem::size_of; - let ptr = DescriptorTablePointer{ + let ptr = DescriptorTablePointer { base: self as *const _ as u64, limit: (size_of::() - 1) as u16, }; @@ -24,3 +26,77 @@ impl Idt { lidt(&ptr); } } + +use bit_field::BitField; + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct Entry { + target_low: u16, + gdt_selector: u16, + options: EntryOptions, + target_middle: u16, + target_high: u32, + reserved: u32, +} + +impl Entry { + pub fn missing() -> Entry { + Entry { + gdt_selector: 0, + target_low: 0, + target_middle: 0, + target_high: 0, + options: MISSING, + reserved: 0, + } + } + + pub fn new(ty: EntryType, gdt_selector: u16, handler: extern "C" fn() -> !) -> Entry { + let target = handler as u64; + + Entry { + gdt_selector: gdt_selector, + target_low: target as u16, + target_middle: (target >> 16) as u16, + target_high: (target >> 32) as u32, + options: EntryOptions::new(ty), + reserved: 0, + } + } +} + +const MISSING: EntryOptions = EntryOptions(BitField::new(0)); + +#[derive(Debug, Clone, Copy)] +pub struct EntryOptions(BitField); + +impl EntryOptions { + pub fn new(ty: EntryType) -> Self { + let mut flags = BitField::new(0); + match ty { + EntryType::InterruptGate => flags.set_range(8..12, 0b1110), + EntryType::TrapGate => flags.set_range(8..12, 0b1111), + } + // set present bit + flags.set_bit(15); + + EntryOptions(flags) + } + + pub fn set_privilege(&mut self, dpl: u16) { + assert!(dpl < 4); + self.0.set_range(13..15, dpl); + } + + pub fn set_stack_index(&mut self, index: u16) { + assert!(index < 8); + self.0.set_range(0..3, index); + } +} + +#[allow(dead_code)] +pub enum EntryType { + InterruptGate, + TrapGate, +} From 59382699d03729e600d8339df40de9ec4116a6e3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 25 May 2016 12:03:56 +0200 Subject: [PATCH 04/23] wip --- Cargo.toml | 5 +- blog/post/exceptions.md | 268 ++++++++++++++++++++++++++++++++++++++-- src/interrupts/idt.rs | 91 +++++++------- src/interrupts/mod.rs | 70 +++++++++-- src/lib.rs | 26 ++-- src/vga_buffer.rs | 12 ++ 6 files changed, 397 insertions(+), 75 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6358aa1..35c3afea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,15 +4,12 @@ name = "blog_os" version = "0.1.0" [dependencies] +bit_field = "0.1.0" bitflags = "0.7.0" once = "0.2.1" rlibc = "0.1.4" spin = "0.3.4" -[dependencies.bit-field] -git = "https://github.com/phil-opp/rust-bit-field.git" -optional = false - [dependencies.hole_list_allocator] path = "libs/hole_list_allocator" diff --git a/blog/post/exceptions.md b/blog/post/exceptions.md index 8f4bac82..847d927d 100644 --- a/blog/post/exceptions.md +++ b/blog/post/exceptions.md @@ -1,19 +1,271 @@ +++ -title = "CPU Exceptions" +title = "Catching Exceptions" date = "2016-05-10" +++ +TODO We will catch page faults, + + + ## Interrupts -Whenever a device (e.g. the keyboard contoller) needs +Whenever a device (e.g. the keyboard contoller) needs ## Exceptions -An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception when it should divide by 0. When an exception occurs, the CPU immediately calls a specific exception handler function, depending on the exception type. +An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type. We've already seen several types of exceptions in our kernel: -- **Illegal instruction**: TODO -- **Page Fault**: The CPU tried to perform an illegal read or write. -- **Double Fault**: TODO -- **Triple Fault**: +- **Invalid Opcode**: This exception occurs when the current instruction is invalid. For example, this exception occurred when we tried to use SSE instructions before enabling SSE. Without SSE, the CPU didn't know the `movups` and `movaps` instructions, so it throws an exception when it stumbles over them. +- **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page. +- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered. +- **Triple Fault**: If another exception occurs when the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. This causes the bootloops we experienced in the previous posts. -The full list of +For the full list of exceptions check out the [OSDev wiki][exceptions]. + +[exceptions]: http://wiki.osdev.org/Exceptions + +### The Interrupt Descriptor Table +In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure: + +Bits | Name | Description +--------|-----------------------------------|----------------------------------- +0-15 | Function Pointer [0:15] | The lower bits of the pointer to the handler function. +16-31 | GDT selector | Selector of a code segment in the GDT. +32-34 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called. +35-39 | Reserved (ignored) | +40 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. +41-43 | must be one | +44 | must be zero | +45-46 | Descriptor Privilege Level (DPL) | The minimal required privilege level required for calling this handler. +47 | Present | +48-95 | Function Pointer [16:63] | The remaining bits of the pointer to the handler function. +95-127 | Reserved (ignored) | + +Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column. + +When an exception occurs, the CPU roughly does the following: + +1. Read the corresponding entry from the Interrupt Descriptor Table (IDT). For example, the CPU reads the 14-th entry when a page fault occurs. +2. Check if the entry is present. Raise a double fault if not. +3. Push some registers on the stack, including the instruction pointer and the [EFLAGS] register. (We will use these values in a future post.) +4. Disable interrupts if the entry is an interrupt gate (bit 40 not set). +5. Load the specified GDT selector into the CS segment. +6. Jump to the specified handler function. + +[EFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register + +## Handling Exceptions +Let's try to catch and handle CPU exceptions. We start by creating a new `interrupts` module with an `idt` submodule: + +``` rust +// in src/lib.rs +... +mod interrupts; +... +``` +``` rust +// src/interrupts/mod.rs + +mod idt; +``` + +Now we create types for the IDT and its entries: + +```rust +// src/interrupts/idt.rs + +use x86::segmentation::SegmentSelector; + +pub struct Idt([Entry; 16]); + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct Entry { + pointer_low: u16, + gdt_selector: SegmentSelector, + options: EntryOptions, + pointer_middle: u16, + pointer_high: u32, + reserved: u32, +} +``` + +The IDT is variable sized and can have up to 256 entries. We only need the first 16 entries in this post, so we define the table as `[Entry; 16]`. The remaining 240 handlers are treated as non-present by the CPU. + +The `Entry` type is the translation of the above table to Rust. The `repr(C, packed)` attribute ensures that the compiler keeps the field ordering and does not add any padding between them. Instead of describing the `gdt_selector` as a plain `u16`, we use the `SegmentSelector` type of the `x86` crate. We also merge bits 32 to 47 into an `option` field, because Rust has no `u3` or `u1` type. The `EntryOptions` type is described below: + +### Entry Options +The `EntryOptions` type has the following skeleton: + +``` rust +#[derive(Debug, Clone, Copy)] +pub struct EntryOptions(u16); + +impl EntryOptions { + fn new() -> Self {...} + + fn set_present(&mut self, present: bool) {...} + + fn disable_interrupts(&mut self, disable: bool) {...} + + fn set_privilege_level(&mut self, dpl: u16) {...} + + fn set_stack_index(&mut self, index: u16) {...} +} +``` + +The implementations of these methods need to modify the correct bits of the `u16` without touching the other bits. For example, we would need the following bit-fiddling to set the stack index: + +``` rust +self.0 = (self.0 & 0xfff8) | stack_index; +``` + +Or alternatively: + +``` rust +self.0 = (self.0 & (!0b111)) | stack_index; +``` + +Or: + +``` rust +self.0 = ((self.0 >> 3) << 3) | stack_index; +``` + +Well, none of these variants is really _readable_ and it's very easy to make mistakes somewhere. Therefore I created a `BitField` type with the following API: + +``` rust +self.0.set_range(0..3, stack_index); +``` + +I think it is much more readable, since we abstracted away all bit-masking details. The `BitField` type is contained in the [bit_field] crate. (It's pretty new, so it might still contain bugs.) To add it as dependency, we run `cargo add bit_field` and add `extern crate bit_field;` to our `src/lib.rs`. + +[bit_field]: TODO + +Now we can use the crate to implement the methods of `EntryOptions`: + +```rust +// in src/interrupts/idt.rs + +use bit_field::BitField; + +#[derive(Debug, Clone, Copy)] +pub struct EntryOptions(BitField); + +impl EntryOptions { + fn minimal() -> Self { + let mut options = BitField::new(0); + options.set_range(9..12, 0b111); // required 'one' bits + EntryOptions(options) + } + + pub fn new() -> Self { + let mut options = Self::minimal(); + options.set_present(true).disable_interrupts(true); + options + } + + fn set_present(&mut self, present: bool) -> &mut Self { + self.0.set_bit(15, present); + self + } + + fn disable_interrupts(&mut self, disable: bool) -> &mut Self { + self.0.set_bit(8, !disable); + self + } + + fn set_privilege_level(&mut self, dpl: u16) -> &mut Self { + self.0.set_range(13..15, dpl); + self + } + + fn set_stack_index(&mut self, index: u16) -> &mut Self { + self.0.set_range(0..3, index); + self + } +} +``` +Note that the ranges are _exclusive_ the upper bound. The bit indexes are different from the values in the [above table], because the `option` field starts at bit 32. Thus the privilege level bits are bits 13 (`=45‑32`) and 14 (`=46‑32`). + +The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers are interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`. + +[above table]: {{% relref "#the-interrupt-descriptor-table" %}} + +### Creating IDT Entries +Now we can add a function to create new IDT entries: + +```rust +type HandlerFunc = extern "C" fn() -> !; + +impl Entry { + pub fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { + let pointer = handler as u64; + Entry { + gdt_selector: gdt_selector, + pointer_low: pointer as u16, + pointer_middle: (pointer >> 16) as u16, + pointer_high: (pointer >> 32) as u32, + options: EntryOptions::new(), + reserved: 0, + } + } +} +``` +We chose `extern "C" fn() -> !` as handler function type, which is a function using the C [calling convention]. It takes no arguments and is [diverging] \(indicated by the `!` return type). TODO why? + +[calling convention]: https://en.wikipedia.org/wiki/Calling_convention +[diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions + +### IDT methods +TODO + +```rust +impl Idt { + pub fn new() -> Idt { + Idt([Entry::missing(); 16]) + } + + pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) { + self.0[entry as usize] = Entry::new(segmentation::cs(), handler); + } + + pub fn options(&mut self, entry: u8) -> &mut EntryOptions { + &mut self.0[entry as usize].options + } +} + +impl Entry { + fn missing() -> Self { + Entry { + gdt_selector: 0, + pointer_low: 0, + pointer_middle: 0, + pointer_high: 0, + options: EntryOptions::minimal(), + reserved: 0, + } + } +} +``` + +### A static IDT +TODO lazy_static etc + +### Loading the IDT +TODO + +### Testing it +TODO page fault, some other fault to trigger double fault, kernel stack overflow + +## Switching stacks + +### The Interrupt Stack Table + +### The Task State Segment + +### The Global Descriptor Table (again) + +### Putting it together + +## What's next? diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index 93ab6d75..d507293a 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -6,12 +6,15 @@ impl Idt { Idt([Entry::missing(); 16]) } - pub fn set_handler(&mut self, entry: usize, handler: extern "C" fn() -> !) { - self.0[entry] = Entry::new(EntryType::InterruptGate, 0x8, handler); + pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) { + let segment: u16; + unsafe { asm!("mov %cs, $0" : "=r" (segment) ) }; + let code_segment = SegmentSelector::from_raw(segment); + self.0[entry as usize] = Entry::new(code_segment, handler); } - pub fn set_interrupt_stack(&mut self, entry: usize, stack_index: u16) { - self.0[entry].options.set_stack_index(stack_index); + pub fn options(&mut self, entry: u8) -> &mut EntryOptions { + &mut self.0[entry as usize].options } pub unsafe fn load(&'static self) { @@ -28,75 +31,77 @@ impl Idt { } use bit_field::BitField; +use x86::segmentation::SegmentSelector; #[derive(Debug, Clone, Copy)] #[repr(C, packed)] pub struct Entry { - target_low: u16, - gdt_selector: u16, + pointer_low: u16, + gdt_selector: SegmentSelector, options: EntryOptions, - target_middle: u16, - target_high: u32, + pointer_middle: u16, + pointer_high: u32, reserved: u32, } impl Entry { - pub fn missing() -> Entry { + fn missing() -> Self { Entry { - gdt_selector: 0, - target_low: 0, - target_middle: 0, - target_high: 0, - options: MISSING, + gdt_selector: SegmentSelector::new(0), + pointer_low: 0, + pointer_middle: 0, + pointer_high: 0, + options: EntryOptions::minimal(), reserved: 0, } } - pub fn new(ty: EntryType, gdt_selector: u16, handler: extern "C" fn() -> !) -> Entry { - let target = handler as u64; - + pub fn new(gdt_selector: SegmentSelector, handler: extern "C" fn() -> !) -> Self { + let pointer = handler as u64; Entry { gdt_selector: gdt_selector, - target_low: target as u16, - target_middle: (target >> 16) as u16, - target_high: (target >> 32) as u32, - options: EntryOptions::new(ty), + pointer_low: pointer as u16, + pointer_middle: (pointer >> 16) as u16, + pointer_high: (pointer >> 32) as u32, + options: EntryOptions::new(), reserved: 0, } } } -const MISSING: EntryOptions = EntryOptions(BitField::new(0)); - #[derive(Debug, Clone, Copy)] pub struct EntryOptions(BitField); impl EntryOptions { - pub fn new(ty: EntryType) -> Self { - let mut flags = BitField::new(0); - match ty { - EntryType::InterruptGate => flags.set_range(8..12, 0b1110), - EntryType::TrapGate => flags.set_range(8..12, 0b1111), - } - // set present bit - flags.set_bit(15); - - EntryOptions(flags) + pub fn minimal() -> Self { + let mut options = BitField::new(0); + options.set_range(9..12, 0b111); // required 'one' bits + EntryOptions(options) } - pub fn set_privilege(&mut self, dpl: u16) { - assert!(dpl < 4); + pub fn new() -> Self { + let mut options = Self::minimal(); + options.set_present(true); + options + } + + pub fn set_present(&mut self, present: bool) -> &mut Self { + self.0.set_bit(15, present); + self + } + + pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self { + self.0.set_bit(8, !disable); + self + } + + pub fn set_privilege_level(&mut self, dpl: u16) -> &mut Self { self.0.set_range(13..15, dpl); + self } - pub fn set_stack_index(&mut self, index: u16) { - assert!(index < 8); + pub fn set_stack_index(&mut self, index: u16) -> &mut Self { self.0.set_range(0..3, index); + self } } - -#[allow(dead_code)] -pub enum EntryType { - InterruptGate, - TrapGate, -} diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index 95b8711d..c701d09d 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -1,4 +1,8 @@ +use x86::task::{load_ltr, TaskStateSegment}; +use vga_buffer::print_error; + mod idt; +mod gdt; lazy_static! { static ref IDT: idt::Idt = { @@ -9,33 +13,79 @@ lazy_static! { idt.set_handler(13, general_protection_fault_handler); idt.set_handler(14, page_fault_handler); + idt.options(8).set_stack_index(1); + idt }; + + static ref TSS: TaskStateSegment = { + let mut tss = TaskStateSegment::new(); + + let stack_size = 1024 * 3; // 3KiB + let stack_bottom = unsafe { + ::alloc::heap::allocate(stack_size, 16) as usize // TODO + }; + let stack_top = stack_bottom + stack_size; + + tss.ist[0] = stack_top as u64; + + tss + }; + + static ref GDT: Gdt = { + let mut table = gdt::Gdt::new(); + + let selectors = GdtSelectors { + code: table.add_entry(gdt::Entry::code_segment()), + data: table.add_entry(gdt::Entry::data_segment()), + tss: table.add_entry(gdt::Entry::tss_segment(&TSS)), + }; + + Gdt { + table: table, + selectors: selectors, + } + }; +} + +struct Gdt { + table: gdt::Gdt, + selectors: GdtSelectors, +} + +struct GdtSelectors { + code: gdt::Selector, + data: gdt::Selector, + tss: gdt::Selector, } pub fn init() { assert_has_not_been_called!(); - unsafe { IDT.load() } + unsafe { + GDT.table.load(); + gdt::reload_segment_registers(GDT.selectors.code, GDT.selectors.data); + gdt::load_ltr(GDT.selectors.tss); + IDT.load(); + } } - -pub extern fn divide_by_zero_handler() -> ! { - println!("EXCEPTION: DIVIDE BY ZERO"); +pub extern "C" fn divide_by_zero_handler() -> ! { + unsafe { print_error(format_args!("EXCEPTION: DIVIDE BY ZERO")) }; loop {} } -pub extern fn double_fault_handler() -> ! { - println!("EXCEPTION: DOUBLE FAULT"); +pub extern "C" fn double_fault_handler() -> ! { + unsafe { print_error(format_args!("EXCEPTION: DOUBLE FAULT")) }; loop {} } -pub extern fn general_protection_fault_handler() -> ! { - println!("EXCEPTION: GENERAL PROTECTION FAULT"); +pub extern "C" fn general_protection_fault_handler() -> ! { + unsafe { print_error(format_args!("EXCEPTION: GENERAL PROTECTION FAULT")) }; loop {} } -pub extern fn page_fault_handler() -> ! { - println!("EXCEPTION: PAGE FAULT"); +pub extern "C" fn page_fault_handler() -> ! { + unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; loop {} } diff --git a/src/lib.rs b/src/lib.rs index 7b7fa9a9..89fc5a86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ #![feature(const_fn, unique)] #![feature(alloc, collections)] #![feature(asm)] +#![feature(drop_types_in_const)] +#![feature(heap_api)] #![no_std] extern crate rlibc; @@ -47,16 +49,17 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { // set up guard page and map the heap pages memory::init(boot_info); - use alloc::boxed::Box; - let heap_test = Box::new(42); - - for i in 0..10000 { - format!("Some String"); - } - interrupts::init(); - unsafe { *(0xdeadbeaf as *mut u32) = 42}; + //println!("{:?}", unsafe { *(0xdeadbeaf as *mut u32) }); + //unsafe { *(0xdeadbeaf as *mut u32) = 42 }; + + fn recursive() { + recursive(); + } + recursive(); + + unsafe { *(0xdeadbeaf as *mut u32) = 42 }; unsafe { asm!("xor eax, eax; idiv eax" :::: "intel"); @@ -91,8 +94,11 @@ extern "C" fn eh_personality() {} #[cfg(not(test))] #[lang = "panic_fmt"] extern "C" fn panic_fmt(fmt: core::fmt::Arguments, file: &str, line: u32) -> ! { - println!("\n\nPANIC in {} at line {}:", file, line); - println!(" {}", fmt); + use vga_buffer::print_error; + unsafe { + print_error(format_args!("\n\nPANIC in {} at line {}:", file, line)); + print_error(format_args!(" {}", fmt)); + } loop {} } diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 2546fdd5..6bd5781b 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -135,3 +135,15 @@ struct ScreenChar { struct Buffer { chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], } + +pub unsafe fn print_error(fmt: fmt::Arguments) { + use core::fmt::Write; + + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Red, Color::Black), + buffer: unsafe { Unique::new(0xb8000 as *mut _) }, + }; + writer.new_line(); + writer.write_fmt(fmt); +} From c961fdc32bb1edd69ddfb3ed964d1d4990701f37 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 25 May 2016 14:03:43 +0200 Subject: [PATCH 05/23] wip --- src/interrupts/gdt.rs | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/interrupts/gdt.rs diff --git a/src/interrupts/gdt.rs b/src/interrupts/gdt.rs new file mode 100644 index 00000000..930b1caf --- /dev/null +++ b/src/interrupts/gdt.rs @@ -0,0 +1,115 @@ +use bit_field::BitField; +use collections::vec::Vec; +use core::ops::Range; +use x86::task::TaskStateSegment; + +pub struct Gdt(Vec>); + +impl Gdt { + pub fn new() -> Gdt { + let zero_entry = BitField::new(0); + Gdt(vec![zero_entry]) + } + + pub fn add_entry(&mut self, entry: Entry) -> Selector { + use core::mem::size_of; + let index = self.0.len() * size_of::>(); + + match entry { + Entry::UserSegment(entry) => self.0.push(entry), + Entry::SystemSegment(entry_low, entry_high) => { + self.0.push(entry_low); + self.0.push(entry_high); + } + } + + Selector(index as u16) + } + + pub unsafe fn load(&'static self) { + use x86::dtables::{DescriptorTablePointer, lgdt}; + use core::mem::{self, size_of}; + + let ptr = DescriptorTablePointer { + base: self.0.as_ptr() as u64, + limit: (self.0.len() * size_of::() - 1) as u16, + }; + + lgdt(&ptr); + } +} + +pub enum Entry { + UserSegment(BitField), + SystemSegment(BitField, BitField), +} + +impl Entry { + pub fn code_segment() -> Entry { + let flags = DESCRIPTOR_TYPE | PRESENT | READ_WRITE | EXECUTABLE | LONG_MODE; + Entry::UserSegment(BitField::new(flags.bits())) + } + + pub fn data_segment() -> Entry { + let flags = DESCRIPTOR_TYPE | PRESENT | READ_WRITE; + Entry::UserSegment(BitField::new(flags.bits())) + } + + pub fn tss_segment(tss: &'static TaskStateSegment) -> Entry { + use core::mem::size_of; + + let ptr = BitField::new(tss as *const _ as u64); + + let mut low = BitField::new(PRESENT.bits()); + low.set_range(0..16, (size_of::() - 1) as u64); + low.set_range(16..40, ptr.get_range(0..24)); + low.set_range(40..44, 0b1001); // type: available 64-bit tss + + let mut high = BitField::new(0); + high.set_range(0..32, ptr.get_range(32..64)); + + Entry::SystemSegment(low, high) + } +} + +bitflags! { + flags EntryFlags: u64 { + const READ_WRITE = 1 << 41, + const CONFORMING = 1 << 42, + const EXECUTABLE = 1 << 43, + const DESCRIPTOR_TYPE = 1 << 44, + const PRESENT = 1 << 47, + const LONG_MODE = 1 << 53, + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Selector(u16); + +pub fn reload_segment_registers(code_selector: Selector, data_selector: Selector) { + + let current_code_selector: u16; + let current_data_selector: u16; + + unsafe { + asm!("mov $0, cs" : "=r" (current_code_selector) ::: "intel"); + asm!("mov $0, ds" : "=r" (current_data_selector) ::: "intel"); + } + assert_eq!(code_selector.0, current_code_selector); + assert_eq!(data_selector.0, current_data_selector); + + // jmp ax:.new_code_segment // TODO + // .new_code_segment: + // unsafe { asm!(" + // mov ax, $1 + // mov ss, ax + // mov ds, ax + // mov es, ax + // ":: "r" (code_selector.0), "r" (data_selector.0) :: "intel")}; + // +} + +/// Load the task state register. +pub unsafe fn load_ltr(selector: Selector) { + asm!("ltr $0" :: "r" (selector)); +} From cbe034f3a5f5930826de83c42f8d8a8553ff973f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 25 May 2016 14:12:23 +0200 Subject: [PATCH 06/23] wip --- blog/post/exceptions.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/blog/post/exceptions.md b/blog/post/exceptions.md index 847d927d..fffe93dd 100644 --- a/blog/post/exceptions.md +++ b/blog/post/exceptions.md @@ -155,7 +155,7 @@ pub struct EntryOptions(BitField); impl EntryOptions { fn minimal() -> Self { let mut options = BitField::new(0); - options.set_range(9..12, 0b111); // required 'one' bits + options.set_range(9..12, 0b111); // 'must-be-one' bits EntryOptions(options) } @@ -186,9 +186,9 @@ impl EntryOptions { } } ``` -Note that the ranges are _exclusive_ the upper bound. The bit indexes are different from the values in the [above table], because the `option` field starts at bit 32. Thus the privilege level bits are bits 13 (`=45‑32`) and 14 (`=46‑32`). +Note that the ranges are _exclusive_ the upper bound. The bit indexes are different from the values in the [above table], because the `option` field starts at bit 32. Thus e.g. the privilege level bits are bits 13 (`= 45‑32`) and 14 (`= 46‑32`). -The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers are interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`. +The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers can be interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`. [above table]: {{% relref "#the-interrupt-descriptor-table" %}} @@ -196,8 +196,6 @@ The `minimal` function creates an `EntryOptions` type with only the “must-be-o Now we can add a function to create new IDT entries: ```rust -type HandlerFunc = extern "C" fn() -> !; - impl Entry { pub fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { let pointer = handler as u64; @@ -212,11 +210,29 @@ impl Entry { } } ``` -We chose `extern "C" fn() -> !` as handler function type, which is a function using the C [calling convention]. It takes no arguments and is [diverging] \(indicated by the `!` return type). TODO why? +We take a GDT selector and a handler function as arguments and create a new IDT entry for it. The `HandlerFunc` type is described below. It is a function pointer that can be converted to an `u64`. We choose the lower 16 bits for `pointer_low`, the next 16 bits for `pointer_middle` and the remaining 32 bits for `pointer_high`. For the options field we choose our default options, i.e. present and disabled interrupts. + +### The Handler Function Type + +The `HandlerFunc` type is a type alias for a function type: + +``` rust +type HandlerFunc = extern "C" fn() -> !; +``` +It needs to be a function with a defined [calling convention], as it called directly by the hardware. The C calling convention is the de facto standard in OS development, so we're using it, too. The function takes no arguments, since the hardware doesn't supply any arguments when jumping to the handler function. [calling convention]: https://en.wikipedia.org/wiki/Calling_convention + +It is important that the function is [diverging], i.e. it must never return. The reason is that the hardware doesn't _call_ the handler functions, it just _jumps_ to them after pushing some values to the stack. So our stack might look different: + [diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions +![normal function return vs interrupt function return](/images/normal-vs-interrupt-function-return.svg) + +If our handler function returned normally, it would try to pop the return address from the stack. But it might get some completely different value then. For example, the CPU pushes an error code for some exceptions. Bad things would happen if we interpreted this error code as return address and jumped to it. Therefore interrupt handler functions must diverge[^fn-must-diverge]. + +[^fn-must-diverge]: Another reason is that overwrite the current register values by executing the handler function. Thus, the interrupted function looses its state and can't proceed anyway. + ### IDT methods TODO From 968ae00de7e96c9673f20db5af194be1714fc119 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 27 May 2016 21:18:16 +0200 Subject: [PATCH 07/23] Update blog post --- blog/post/exceptions.md | 247 ++++++++++++++++++++++++++++++++++------ 1 file changed, 213 insertions(+), 34 deletions(-) diff --git a/blog/post/exceptions.md b/blog/post/exceptions.md index fffe93dd..c1115db1 100644 --- a/blog/post/exceptions.md +++ b/blog/post/exceptions.md @@ -27,19 +27,26 @@ For the full list of exceptions check out the [OSDev wiki][exceptions]. ### The Interrupt Descriptor Table In order to catch and handle exceptions, we have to set up a so-called _Interrupt Descriptor Table_ (IDT). In this table we can specify a handler function for each CPU exception. The hardware uses this table directly, so we need to follow a predefined format. Each entry must have the following 16-byte structure: -Bits | Name | Description ---------|-----------------------------------|----------------------------------- -0-15 | Function Pointer [0:15] | The lower bits of the pointer to the handler function. -16-31 | GDT selector | Selector of a code segment in the GDT. -32-34 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called. -35-39 | Reserved (ignored) | -40 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. -41-43 | must be one | -44 | must be zero | -45-46 | Descriptor Privilege Level (DPL) | The minimal required privilege level required for calling this handler. -47 | Present | -48-95 | Function Pointer [16:63] | The remaining bits of the pointer to the handler function. -95-127 | Reserved (ignored) | +Type| Name | Description +----|--------------------------|----------------------------------- +u16 | Function Pointer [0:15] | The lower bits of the pointer to the handler function. +u16 | GDT selector | Selector of a code segment in the GDT. +u16 | Options | (see below) +u16 | Function Pointer [16:31] | The middle bits of the pointer to the handler function. +u32 | Function Pointer [32:63] | The remaining bits of the pointer to the handler function. +u32 | Reserved | + +The options field has the following format: + +Bits | Name | Description +------|-----------------------------------|----------------------------------- +0-2 | Interrupt Stack Table Index | 0: Don't switch stacks, 1-7: Switch to the n-th stack in the Interrupt Stack Table when this handler is called. +3-7 | Reserved | +8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. +9-11 | must be one | +12 | must be zero | +13‑14 | Descriptor Privilege Level (DPL) | The minimal required privilege level required for calling this handler. +15 | Present | Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column. @@ -159,7 +166,7 @@ impl EntryOptions { EntryOptions(options) } - pub fn new() -> Self { + fn new() -> Self { let mut options = Self::minimal(); options.set_present(true).disable_interrupts(true); options @@ -186,18 +193,14 @@ impl EntryOptions { } } ``` -Note that the ranges are _exclusive_ the upper bound. The bit indexes are different from the values in the [above table], because the `option` field starts at bit 32. Thus e.g. the privilege level bits are bits 13 (`= 45‑32`) and 14 (`= 46‑32`). - -The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers can be interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`. - -[above table]: {{% relref "#the-interrupt-descriptor-table" %}} +Note that the ranges are _exclusive_ the upper bound. The `minimal` function creates an `EntryOptions` type with only the “must-be-one” bits set. The `new` function, on the other hand, chooses reasonable defaults: It sets the present bit (why would you want to create a non-present entry?) and disables interrupts (normally we don't want that our exception handlers can be interrupted). By returning the self pointer from the `set_*` methods, we allow easy method chaining such as `options.set_present(true).disable_interrupts(true)`. ### Creating IDT Entries Now we can add a function to create new IDT entries: ```rust impl Entry { - pub fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { + fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { let pointer = handler as u64; Entry { gdt_selector: gdt_selector, @@ -231,24 +234,16 @@ It is important that the function is [diverging], i.e. it must never return. The If our handler function returned normally, it would try to pop the return address from the stack. But it might get some completely different value then. For example, the CPU pushes an error code for some exceptions. Bad things would happen if we interpreted this error code as return address and jumped to it. Therefore interrupt handler functions must diverge[^fn-must-diverge]. -[^fn-must-diverge]: Another reason is that overwrite the current register values by executing the handler function. Thus, the interrupted function looses its state and can't proceed anyway. +[^fn-must-diverge]: Another reason is that we overwrite the current register values by executing the handler function. Thus, the interrupted function looses its state and can't proceed anyway. ### IDT methods -TODO +Let's add a function to create new interrupt descriptor tables: ```rust impl Idt { pub fn new() -> Idt { Idt([Entry::missing(); 16]) } - - pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) { - self.0[entry as usize] = Entry::new(segmentation::cs(), handler); - } - - pub fn options(&mut self, entry: u8) -> &mut EntryOptions { - &mut self.0[entry as usize].options - } } impl Entry { @@ -264,14 +259,198 @@ impl Entry { } } ``` +The `missing` function creates a non-present Entry. We could choose any values for the pointer and GDT selector fields as long as the present bit is not set. -### A static IDT -TODO lazy_static etc +However, a table with non-present entries is not very useful. So we create a `set_handler` method to add new handler functions: + +```rust +impl Idt { + pub fn set_handler(&mut self, entry: u8, handler: HandlerFunc) + -> &mut EntryOptions + { + self.0[entry as usize] = Entry::new(segmentation::cs(), handler); + &mut self.0[entry as usize].options + } +} +``` +The method overwrites the specified entry with the given handler function. We use the `segmentation::cs`[^fn-segmentation-cs] function of the [x86 crate] to get the current code segment descriptor. There's no need for different kernel code segments in long mode, so the current `cs` value should be always the right choice. + +[x86 crate]: https://github.com/gz/rust-x86 +[^fn-segmentation-cs]: The `segmentation::cs` function was [added](https://github.com/gz/rust-x86/pull/12) in version 0.7.0, so you might need to update your `x86` version in your `Cargo.toml`. + +By returning a mutual reference to the entry's options, we allow the caller to override the default settings. For example, the caller could add a non-present entry by executing: `idt.set_handler(11, handler_fn).set_present(false)`. ### Loading the IDT -TODO +Now we're able to create new interrupt descriptor tables with registered handler functions. We just need a way to load an IDT, so that the CPU uses it. The x86 architecture uses a special register to store the active IDT and its length. In order to load a new IDT we need to update this register through the [lidt] instruction. -### Testing it +[lidt]: http://x86.renejeschke.de/html/file_module_x86_id_156.html + +The `lidt` instruction expects a pointer to a special data structure, which specifies the start address of the IDT and its length: + + +Type | Name | Description +--------|---------|----------------------------------- +u16 | Limit | The maximum addressable byte in the table. Equal to the table size in bytes minus 1. +u64 | Offset | Virtual start address of the table. + +This structure is already contained [in the x86 crate], so we don't need to create it ourselves. The same is true for the [lidt function]. So we just need to put the pieces together to create a `load` method: + +[in the x86 crate]: http://gz.github.io/rust-x86/x86/dtables/struct.DescriptorTablePointer.html +[lidt function]: http://gz.github.io/rust-x86/x86/dtables/fn.lidt.html + +```rust +impl Idt { + pub fn load(&self) { + use x86::dtables::{DescriptorTablePointer, lidt}; + use core::mem::size_of; + + let ptr = DescriptorTablePointer { + base: self as *const _ as u64, + limit: (size_of::() - 1) as u16, + }; + + unsafe { lidt(&ptr) }; + } +} +``` +The method does not need to modify the IDT, so it takes `self` by immutable reference. We convert this reference to an u64 and calculate the table size using [mem::size_of]. The additional `-1` is needed because the limit field has to be the maximum addressable byte. + +[mem::size_of]: https://doc.rust-lang.org/nightly/core/mem/fn.size_of.html + +Then we pass a pointer to our `ptr` structure to the `lidt` function, which calls the `lidt` assembly instruction in order to reload the IDT register. We need an unsafe block here, because the `lidt` assumes that the specified handler addresses are valid. + +### Safety +But can we really guarantee that handler addresses are always valid? Let's see: + +- The `Idt::new` function creates a new table populated with non-present entries. There's no way to set these entries to present from outside of this module, so this function is fine. +- The `set_handler` method allows us to overwrite a specified entry and point it to some handler function. Rust's type system guarantees that function pointers are always valid (as long as no `unsafe` is involved), so this function is fine, too. + +There are no other public functions in the `idt` module (except `load`), so it should be safe… right? + +Wrong! Imagine the following scenario: + +```rust +pub fn init() { + load_idt(); + cause_page_fault(); +} + +fn load_idt() { + let mut idt = idt::Idt::new(); + idt.set_handler(14, page_fault_handler); + idt.load(); +} + +fn cause_page_fault() { + let x = [1,2,3,4,5,6,7,8,9]; + unsafe{ *(0xdeadbeaf as *mut u64) = x[4]}; +} +``` +This won't work. If we're lucky, we get a triple fault and a boot loop. If we're unlucky, our kernel does strange things and fails at some completely unrelated place. So what's the problem here? + +Well, we construct an IDT _on the stack_ and load it. It is perfectly valid until the end of the `load_idt` function. But as soon as the function returns, its stack frame can be reused by other functions. Thus, the IDT gets overwritten by the stack frame of the `cause_page_fault` function. It declares an array of integers, which overwrite the entries of our loaded IDT. So when the page fault occurs and the CPU tries to read the entry, it only sees some garbage values and issues a double fault, which escalates to a triple fault and a CPU reset. + +Now imagine that the `cause_page_fault` function declared an array of pointers instead. If the present was coincidentally set, the CPU would jump to some random pointer and interpret random memory as code. This would be a clear violation of memory safety. + +### Fixing the load method +So how do we fix it? We could make the load function itself `unsafe` and push the unsafety to the caller. However, there is a much better solution in this case. Let's formulate our requirement: + +> The referenced IDT must be valid until a new IDT is loaded. + +We can't know when the next IDT will be loaded. Maybe never. So in the worst case: + +> The referenced IDT must be valid as long as our kernel runs. + +This is exactly the definition of a [static lifetime]. So we can easily ensure that the IDT lives long enough by adding a `'static` requirement to the signature of the `load` function: + +[static lifetime]: http://rustbyexample.com/scope/lifetime/static_lifetime.html + +```rust +pub fn load(&'static self) {...} +``` + +That's it! Now the Rust compiler ensures that the above error can't happen anymore: + +``` +error: `idt` does not live long enough + --> src/interrupts/mod.rs:78:5 +78 |> idt.load(); + |> ^^^ +note: reference must be valid for the static lifetime... +note: ...but borrowed value is only valid for the block suffix following + statement 0 at 75:34 + --> src/interrupts/mod.rs:75:35 +75 |> let mut idt = idt::Idt::new(); + |> ^ +``` + +### A static IDT +So a valid IDT needs to have the `'static` lifetime. We can either create a `static` IDT or [deliberately leak a Box][into_raw]. We will most likely only need a single IDT for the foreseeable future, so let's try the `static` approach: + +[into_raw]: https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.into_raw + +```rust +// in src/interrupts/mod.rs + +static IDT: idt::Idt = { + let mut idt = idt::Idt::new(); + + idt.set_handler(14, page_fault_handler); + + idt +}; + +extern "C" fn page_fault_handler() -> ! { + println!("EXCEPTION: PAGE FAULT"); + loop {} +} +``` + +Well… this doesn't work: + +``` +error: calls in statics are limited to constant functions, struct and enum + constructors [E0015] +... +error: blocks in statics are limited to items and tail expressions [E0016] +... +error: references in statics may only refer to immutable values [E0017] +... +``` +Maybe it will work someday when `const` functions become more powerful. But until then, we have to find another solution. + +### Lazy Statics to the Rescue +Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro evaluates it when it's referenced the first time. Thus, we can do almost everything in it and are even able to read runtime values (e.g. the number of cores). + +With `lazy_static`, we can define our IDT without problems: + +```rust +lazy_static! { + static ref IDT: idt::Idt = { + let mut idt = idt::Idt::new(); + + idt.set_handler(14, page_fault_handler); + + idt + }; +} +``` + +Now we're ready to load it! We add a `interrupts::init` function, which takes care of it: + +```rust +// in src/interrupts/mod.rs + +pub fn init() { + assert_has_not_been_called!(); + + IDT.load(); +} +``` +We're using our `assert_has_not_been_called` macro to ensure that the `init` function is called only once. It doesn't really matter in this case since we would just load the table again. However, calling an initialization function twice is a sign of some bug, so we leave it in. + + +## Testing it TODO page fault, some other fault to trigger double fault, kernel stack overflow ## Switching stacks From 8540d3844bd6b2f8f2c960a2cfe81230530e3c2a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 27 May 2016 21:21:08 +0200 Subject: [PATCH 08/23] Use new x86::segmentation::cs function and merge `set_handler` and `options` We avoid inline assembly and increase safety (it is no longer possible to set the non-present initilization entries to present). --- Cargo.toml | 2 +- src/interrupts/idt.rs | 14 ++++---------- src/interrupts/mod.rs | 4 +--- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35c3afea..a5503538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ git = "https://github.com/phil-opp/multiboot2-elf64" [dependencies.x86] default-features = false -version = "0.6.0" +version = "0.7.0" [lib] crate-type = ["staticlib"] diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index d507293a..f1700919 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -1,3 +1,4 @@ +use x86::segmentation::{self, SegmentSelector}; pub struct Idt([Entry; 16]); @@ -6,14 +7,8 @@ impl Idt { Idt([Entry::missing(); 16]) } - pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) { - let segment: u16; - unsafe { asm!("mov %cs, $0" : "=r" (segment) ) }; - let code_segment = SegmentSelector::from_raw(segment); - self.0[entry as usize] = Entry::new(code_segment, handler); - } - - pub fn options(&mut self, entry: u8) -> &mut EntryOptions { + pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) -> &mut EntryOptions { + self.0[entry as usize] = Entry::new(segmentation::cs(), handler); &mut self.0[entry as usize].options } @@ -31,7 +26,6 @@ impl Idt { } use bit_field::BitField; -use x86::segmentation::SegmentSelector; #[derive(Debug, Clone, Copy)] #[repr(C, packed)] @@ -56,7 +50,7 @@ impl Entry { } } - pub fn new(gdt_selector: SegmentSelector, handler: extern "C" fn() -> !) -> Self { + fn new(gdt_selector: SegmentSelector, handler: extern "C" fn() -> !) -> Self { let pointer = handler as u64; Entry { gdt_selector: gdt_selector, diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index c701d09d..cb8e804d 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -9,12 +9,10 @@ lazy_static! { let mut idt = idt::Idt::new(); idt.set_handler(0, divide_by_zero_handler); - idt.set_handler(8, double_fault_handler); + idt.set_handler(8, double_fault_handler).set_stack_index(1); idt.set_handler(13, general_protection_fault_handler); idt.set_handler(14, page_fault_handler); - idt.options(8).set_stack_index(1); - idt }; From 13e94de7b47964ff6143a4154cbdf26cc17d6f65 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 27 May 2016 21:21:52 +0200 Subject: [PATCH 09/23] Make the load function safe --- src/interrupts/idt.rs | 4 ++-- src/interrupts/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index f1700919..89922446 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -12,7 +12,7 @@ impl Idt { &mut self.0[entry as usize].options } - pub unsafe fn load(&'static self) { + pub fn load(&'static self) { use x86::dtables::{DescriptorTablePointer, lidt}; use core::mem::size_of; @@ -21,7 +21,7 @@ impl Idt { limit: (size_of::() - 1) as u16, }; - lidt(&ptr); + unsafe { lidt(&ptr) }; } } diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index cb8e804d..2a29469d 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -64,8 +64,8 @@ pub fn init() { GDT.table.load(); gdt::reload_segment_registers(GDT.selectors.code, GDT.selectors.data); gdt::load_ltr(GDT.selectors.tss); - IDT.load(); } + IDT.load(); } pub extern "C" fn divide_by_zero_handler() -> ! { From 5054e48c9af411d26c04ab7d9bb3acfff9fa3076 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 27 May 2016 21:22:16 +0200 Subject: [PATCH 10/23] Remove unneeded `pub`s --- src/interrupts/idt.rs | 4 ++-- src/interrupts/mod.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index 89922446..43ecb467 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -67,13 +67,13 @@ impl Entry { pub struct EntryOptions(BitField); impl EntryOptions { - pub fn minimal() -> Self { + fn minimal() -> Self { let mut options = BitField::new(0); options.set_range(9..12, 0b111); // required 'one' bits EntryOptions(options) } - pub fn new() -> Self { + fn new() -> Self { let mut options = Self::minimal(); options.set_present(true); options diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index 2a29469d..e628d1b9 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -68,22 +68,23 @@ pub fn init() { IDT.load(); } -pub extern "C" fn divide_by_zero_handler() -> ! { +extern "C" fn divide_by_zero_handler() -> ! { unsafe { print_error(format_args!("EXCEPTION: DIVIDE BY ZERO")) }; loop {} } -pub extern "C" fn double_fault_handler() -> ! { +extern "C" fn double_fault_handler() -> ! { unsafe { print_error(format_args!("EXCEPTION: DOUBLE FAULT")) }; + unsafe { asm!("iretq")}; loop {} } -pub extern "C" fn general_protection_fault_handler() -> ! { +extern "C" fn general_protection_fault_handler() -> ! { unsafe { print_error(format_args!("EXCEPTION: GENERAL PROTECTION FAULT")) }; loop {} } -pub extern "C" fn page_fault_handler() -> ! { +extern "C" fn page_fault_handler() -> ! { unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; loop {} } From 4633f84af663c3135c8fed8ca3811ef84a03ed22 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 13:37:03 +0200 Subject: [PATCH 11/23] Finish catching-exceptions post --- ...s.md => 2016-05-28-catching-exceptions.md} | 198 +++++++++++++++--- 1 file changed, 174 insertions(+), 24 deletions(-) rename blog/post/{exceptions.md => 2016-05-28-catching-exceptions.md} (70%) diff --git a/blog/post/exceptions.md b/blog/post/2016-05-28-catching-exceptions.md similarity index 70% rename from blog/post/exceptions.md rename to blog/post/2016-05-28-catching-exceptions.md index c1115db1..fb7f0e73 100644 --- a/blog/post/exceptions.md +++ b/blog/post/2016-05-28-catching-exceptions.md @@ -1,14 +1,16 @@ +++ title = "Catching Exceptions" -date = "2016-05-10" +date = "2016-05-28" +++ -TODO We will catch page faults, +In this post, we start exploring exceptions. We set up an interrupt descriptor table and add handler functions. At the end of this post, our kernel will be able to catch page faults. -## Interrupts -Whenever a device (e.g. the keyboard contoller) needs +As always, the complete source code is on [Github]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a comment section at the end of this page. + +[Github]: https://github.com/phil-opp/blog_os/tree/catching_exceptions +[issues]: https://github.com/phil-opp/blog_os/issues ## Exceptions An exception signals that something is wrong with the current instruction. For example, the CPU issues an exception if the current instruction tries to divide by 0. When an exception occurs, the CPU interrupts its current work and immediately calls a specific exception handler function, depending on the exception type. @@ -343,17 +345,17 @@ fn load_idt() { fn cause_page_fault() { let x = [1,2,3,4,5,6,7,8,9]; - unsafe{ *(0xdeadbeaf as *mut u64) = x[4]}; + unsafe{ *(0xdeadbeaf as *mut u64) = x[4] }; } ``` This won't work. If we're lucky, we get a triple fault and a boot loop. If we're unlucky, our kernel does strange things and fails at some completely unrelated place. So what's the problem here? -Well, we construct an IDT _on the stack_ and load it. It is perfectly valid until the end of the `load_idt` function. But as soon as the function returns, its stack frame can be reused by other functions. Thus, the IDT gets overwritten by the stack frame of the `cause_page_fault` function. It declares an array of integers, which overwrite the entries of our loaded IDT. So when the page fault occurs and the CPU tries to read the entry, it only sees some garbage values and issues a double fault, which escalates to a triple fault and a CPU reset. +Well, we construct an IDT _on the stack_ and load it. It is perfectly valid until the end of the `load_idt` function. But as soon as the function returns, its stack frame can be reused by other functions. Thus, the IDT gets overwritten by the stack frame of the `cause_page_fault` function. So when the page fault occurs and the CPU tries to read the entry, it only sees some garbage values and issues a double fault, which escalates to a triple fault and a CPU reset. Now imagine that the `cause_page_fault` function declared an array of pointers instead. If the present was coincidentally set, the CPU would jump to some random pointer and interpret random memory as code. This would be a clear violation of memory safety. ### Fixing the load method -So how do we fix it? We could make the load function itself `unsafe` and push the unsafety to the caller. However, there is a much better solution in this case. Let's formulate our requirement: +So how do we fix it? We could make the load function itself `unsafe` and push the unsafety to the caller. However, there is a much better solution in this case. In order to see it, we formulate the requirement for the `load` method: > The referenced IDT must be valid until a new IDT is loaded. @@ -367,6 +369,7 @@ This is exactly the definition of a [static lifetime]. So we can easily ensure t ```rust pub fn load(&'static self) {...} +// ^^^^^^^ ensure that the IDT reference has the 'static lifetime ``` That's it! Now the Rust compiler ensures that the above error can't happen anymore: @@ -401,12 +404,11 @@ static IDT: idt::Idt = { }; extern "C" fn page_fault_handler() -> ! { - println!("EXCEPTION: PAGE FAULT"); - loop {} + println!("EXCEPTION: PAGE FAULT"); + loop {} } ``` - -Well… this doesn't work: +We register a single handler function for a page fault (index 14). The handler function just prints an error message and enters a `loop`. However, it doesn't work this way: ``` error: calls in statics are limited to constant functions, struct and enum @@ -417,10 +419,10 @@ error: blocks in statics are limited to items and tail expressions [E0016] error: references in statics may only refer to immutable values [E0017] ... ``` -Maybe it will work someday when `const` functions become more powerful. But until then, we have to find another solution. +The reason is that the Rust compiler is not able to evaluate the value of the `static` at compile time. Maybe it will work someday when `const` functions become more powerful. But until then, we have to find another solution. ### Lazy Statics to the Rescue -Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro evaluates it when it's referenced the first time. Thus, we can do almost everything in it and are even able to read runtime values (e.g. the number of cores). +Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values (e.g. the number of cores). With `lazy_static`, we can define our IDT without problems: @@ -436,31 +438,179 @@ lazy_static! { } ``` -Now we're ready to load it! We add a `interrupts::init` function, which takes care of it: +Now we're ready to load our IDT! Therefore we add a `interrupts::init` function: ```rust // in src/interrupts/mod.rs pub fn init() { - assert_has_not_been_called!(); - IDT.load(); } ``` -We're using our `assert_has_not_been_called` macro to ensure that the `init` function is called only once. It doesn't really matter in this case since we would just load the table again. However, calling an initialization function twice is a sign of some bug, so we leave it in. - +We don't need our `assert_has_not_been_called` macro here, since nothing bad happens when `init` is called twice. It just reloads the same IDT again. ## Testing it -TODO page fault, some other fault to trigger double fault, kernel stack overflow +Now we should be able to catch page faults! Let's try it in our `rust_main`: -## Switching stacks +```rust +// in src/lib.rs -### The Interrupt Stack Table +pub extern "C" fn rust_main(...) { + ... + memory::init(boot_info); -### The Task State Segment + // initialize our IDT + interrupts::init(); -### The Global Descriptor Table (again) + // provoke a page fault by writing to some random address + unsafe{ *(0xdeadbeaf as *mut u64) = 42 }; -### Putting it together + println!("It did not crash!"); + loop {} +} +``` +It works! We see a `EXCEPTION: PAGE FAULT` message at the bottom of our screen: + +![QEMU screenshot with `EXCEPTION: PAGE FAULT` message](images/qemu-page-fault-println.png) + +Let's try something else: + +```rust +pub extern "C" fn rust_main(...) { + ... + interrupts::init(); + + // provoke a page fault inside println + println!("{:?}", unsafe{ *(0xdeadbeaf as *mut u64) = 42 }); + + println!("It did not crash!"); + loop {} +} +``` +Now the output ends on the `guard page` line. No `EXCEPTION` message and no `It did not crash` message either. What's happening? + +### Debugging +Let's debug it using [GDB]. It is a console debugger and works with nearly everything, including QEMU. To make QEMU listen for a debugger connection, we start it with the `-s` flag: + +[GDB]: https://www.gnu.org/software/gdb/ + +```Makefile +# in `Makefile` + +run: $(iso) + @qemu-system-x86_64 -cdrom $(iso) -s +``` + +Then we can launch GDB in another console window: + +``` +> gdb build/kernel-x86_64.bin +[some version, copyright, and usage information] +Reading symbols from build/kernel-x86_64.bin...done. +(gdb) +``` +Now we can connect to our running QEMU instance on port `1234`: + +``` +(gdb) target extern :1234 +Remote debugging using :1234 +0x00000000001031dd in spin::mutex::cpu_relax () + at /home/.../spin-0.3.5/src/mutex.rs:102 +102 unsafe { asm!("pause" :::: "volatile"); } +``` +So we're locked in a function named `mutex::cpu_relax` inside the `spin` crate. Let's try a backtrace: + +``` +(gdb) backtrace +#0 0x00000000001031dd in spin::mutex::cpu_relax () + at /home/.../spin-0.3.5/src/mutex.rs:102 +#1 spin::mutex::{{impl}}::obtain_lock ( + self=0x117220 ) + at /home/.../spin-0.3.5/src/mutex.rs:142 +#2 0x0000000000103163 in spin::mutex::{{impl}}::lock ( + self=0x117220 ) + at /home/.../spin-0.3.5/src/mutex.rs:163 +#3 0x000000000010de09 in blog_os::interrupts::page_fault_handler () + at src/vga_buffer.rs:31 +... +``` +Pretty verbose… but very useful. Let's clean it up a bit: + +- `spin::mutex::cpu_relax` +- `spin::mutex::obtain_lock` +- `spin::mutex::lock` +- `blog_os::interrupts::page_fault_handler` +- ... + +It's a _back_-trace, so it goes from the innermost function to the outermost function. We see that our page fault handler was called successfully. It then tried to write its error message. Therefore, it tried to `lock` the static `WRITER`, which in turn called `obtain_lock` and `cpu_relax`. + +So our kernel tries to lock the output `WRITER`, which is already locked by the interrupted `println`. Thus, our exception handler waits forever and we don't see what error occurred. Yay, that's our first deadlock! :) + +(As you see, GDB can be very useful sometimes. For more GDB information check out our [Set Up GDB] page.) + +[Set Up GDB]: {{% relref "set-up-gdb.md" %}} + +## Printing Errors Reliably +In order to guarantee that we always see error messages, we add a `print_error` function to our `vga_buffer` module: + +```rust +// in src/vga_buffer.rs + +pub unsafe fn print_error(fmt: fmt::Arguments) { + use core::fmt::Write; + + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Red, Color::Black), + buffer: Unique::new(0xb8000 as *mut _), + }; + writer.new_line(); + writer.write_fmt(fmt); +} +``` + +Instead of using the static `WRITER`, this function creates a new `Writer` on each invocation. Thereby it ignores the mutex and is always able to print to the screen without deadlocking. We print in red to highlight the error and add a newline to avoid overwriting unfinished lines. + +### Safety +This function clearly violates the invariants of the `vga_buffer` module, as it creates another `Unique` pointing to `0xb8000`. Thus, we deliberately introduce a data race on the VGA buffer. For this reason, the function is marked as `unsafe` and should only be used if absolutely necessary. + +However, the situation is not _that_ bad. The VGA buffer only stores characters (no pointers) and we never rely on the buffer's values. So the function might cause mangled output, but should never be able to violate memory safety. + +### Using print_error +Let's use the new `print_error` function to print the page fault error: + +```rust +// in src/interrupts/mod.rs + +extern "C" fn page_fault_handler() -> ! { + unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; + loop {} +} +``` +We use the built-in [format_args] macro to translate the error string to a `fmt::Arguments` type. Now we should always see the error message, even if the exception occurred inside `println`: + +[format_args]: https://doc.rust-lang.org/nightly/std/macro.format_args!.html + +![QEMU screenshot with new red `EXCEPTION: PAGE FAULT` message](images/qemu-page-fault-red.png) ## What's next? +Now we're able to catch _almost_ all page faults. However, some page faults still cause a triple fault and a bootloop. For example, try the following code: + +```rust +pub extern "C" fn rust_main(...) { + ... + interrupts::init(); + + // provoke a kernel stack overflow, which hits the guard page + fn recursive() { + recursive(); + } + recursive(); + + println!("It did not crash!"); + loop {} +} +``` + +The next post will explore and fix this triple fault by creating a double fault handler. After that, we should never again experience a triple fault in our kernel. From 08540eb602c4baff316b4be5622cd7138489eb97 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 13:38:13 +0200 Subject: [PATCH 12/23] Remove unneeded unsafe --- src/vga_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 6bd5781b..7f319cdf 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -142,7 +142,7 @@ pub unsafe fn print_error(fmt: fmt::Arguments) { let mut writer = Writer { column_position: 0, color_code: ColorCode::new(Color::Red, Color::Black), - buffer: unsafe { Unique::new(0xb8000 as *mut _) }, + buffer: Unique::new(0xb8000 as *mut _), }; writer.new_line(); writer.write_fmt(fmt); From e4b42b106fa0b36741ae95b74460131562f3c5d8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 14:09:01 +0200 Subject: [PATCH 13/23] Reset source code to master again --- Cargo.toml | 5 +- src/interrupts/gdt.rs | 115 ------------------------------------------ src/interrupts/idt.rs | 101 ------------------------------------- src/interrupts/mod.rs | 90 --------------------------------- src/lib.rs | 30 +++-------- src/vga_buffer.rs | 12 ----- 6 files changed, 8 insertions(+), 345 deletions(-) delete mode 100644 src/interrupts/gdt.rs delete mode 100644 src/interrupts/idt.rs delete mode 100644 src/interrupts/mod.rs diff --git a/Cargo.toml b/Cargo.toml index a5503538..d0cb6f3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,10 @@ name = "blog_os" version = "0.1.0" [dependencies] -bit_field = "0.1.0" -bitflags = "0.7.0" once = "0.2.1" rlibc = "0.1.4" spin = "0.3.4" +bitflags = "0.7.0" [dependencies.hole_list_allocator] path = "libs/hole_list_allocator" @@ -18,7 +17,7 @@ git = "https://github.com/phil-opp/multiboot2-elf64" [dependencies.x86] default-features = false -version = "0.7.0" +version = "0.6.0" [lib] crate-type = ["staticlib"] diff --git a/src/interrupts/gdt.rs b/src/interrupts/gdt.rs deleted file mode 100644 index 930b1caf..00000000 --- a/src/interrupts/gdt.rs +++ /dev/null @@ -1,115 +0,0 @@ -use bit_field::BitField; -use collections::vec::Vec; -use core::ops::Range; -use x86::task::TaskStateSegment; - -pub struct Gdt(Vec>); - -impl Gdt { - pub fn new() -> Gdt { - let zero_entry = BitField::new(0); - Gdt(vec![zero_entry]) - } - - pub fn add_entry(&mut self, entry: Entry) -> Selector { - use core::mem::size_of; - let index = self.0.len() * size_of::>(); - - match entry { - Entry::UserSegment(entry) => self.0.push(entry), - Entry::SystemSegment(entry_low, entry_high) => { - self.0.push(entry_low); - self.0.push(entry_high); - } - } - - Selector(index as u16) - } - - pub unsafe fn load(&'static self) { - use x86::dtables::{DescriptorTablePointer, lgdt}; - use core::mem::{self, size_of}; - - let ptr = DescriptorTablePointer { - base: self.0.as_ptr() as u64, - limit: (self.0.len() * size_of::() - 1) as u16, - }; - - lgdt(&ptr); - } -} - -pub enum Entry { - UserSegment(BitField), - SystemSegment(BitField, BitField), -} - -impl Entry { - pub fn code_segment() -> Entry { - let flags = DESCRIPTOR_TYPE | PRESENT | READ_WRITE | EXECUTABLE | LONG_MODE; - Entry::UserSegment(BitField::new(flags.bits())) - } - - pub fn data_segment() -> Entry { - let flags = DESCRIPTOR_TYPE | PRESENT | READ_WRITE; - Entry::UserSegment(BitField::new(flags.bits())) - } - - pub fn tss_segment(tss: &'static TaskStateSegment) -> Entry { - use core::mem::size_of; - - let ptr = BitField::new(tss as *const _ as u64); - - let mut low = BitField::new(PRESENT.bits()); - low.set_range(0..16, (size_of::() - 1) as u64); - low.set_range(16..40, ptr.get_range(0..24)); - low.set_range(40..44, 0b1001); // type: available 64-bit tss - - let mut high = BitField::new(0); - high.set_range(0..32, ptr.get_range(32..64)); - - Entry::SystemSegment(low, high) - } -} - -bitflags! { - flags EntryFlags: u64 { - const READ_WRITE = 1 << 41, - const CONFORMING = 1 << 42, - const EXECUTABLE = 1 << 43, - const DESCRIPTOR_TYPE = 1 << 44, - const PRESENT = 1 << 47, - const LONG_MODE = 1 << 53, - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Selector(u16); - -pub fn reload_segment_registers(code_selector: Selector, data_selector: Selector) { - - let current_code_selector: u16; - let current_data_selector: u16; - - unsafe { - asm!("mov $0, cs" : "=r" (current_code_selector) ::: "intel"); - asm!("mov $0, ds" : "=r" (current_data_selector) ::: "intel"); - } - assert_eq!(code_selector.0, current_code_selector); - assert_eq!(data_selector.0, current_data_selector); - - // jmp ax:.new_code_segment // TODO - // .new_code_segment: - // unsafe { asm!(" - // mov ax, $1 - // mov ss, ax - // mov ds, ax - // mov es, ax - // ":: "r" (code_selector.0), "r" (data_selector.0) :: "intel")}; - // -} - -/// Load the task state register. -pub unsafe fn load_ltr(selector: Selector) { - asm!("ltr $0" :: "r" (selector)); -} diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs deleted file mode 100644 index 43ecb467..00000000 --- a/src/interrupts/idt.rs +++ /dev/null @@ -1,101 +0,0 @@ -use x86::segmentation::{self, SegmentSelector}; - -pub struct Idt([Entry; 16]); - -impl Idt { - pub fn new() -> Idt { - Idt([Entry::missing(); 16]) - } - - pub fn set_handler(&mut self, entry: u8, handler: extern "C" fn() -> !) -> &mut EntryOptions { - self.0[entry as usize] = Entry::new(segmentation::cs(), handler); - &mut self.0[entry as usize].options - } - - pub fn load(&'static self) { - use x86::dtables::{DescriptorTablePointer, lidt}; - use core::mem::size_of; - - let ptr = DescriptorTablePointer { - base: self as *const _ as u64, - limit: (size_of::() - 1) as u16, - }; - - unsafe { lidt(&ptr) }; - } -} - -use bit_field::BitField; - -#[derive(Debug, Clone, Copy)] -#[repr(C, packed)] -pub struct Entry { - pointer_low: u16, - gdt_selector: SegmentSelector, - options: EntryOptions, - pointer_middle: u16, - pointer_high: u32, - reserved: u32, -} - -impl Entry { - fn missing() -> Self { - Entry { - gdt_selector: SegmentSelector::new(0), - pointer_low: 0, - pointer_middle: 0, - pointer_high: 0, - options: EntryOptions::minimal(), - reserved: 0, - } - } - - fn new(gdt_selector: SegmentSelector, handler: extern "C" fn() -> !) -> Self { - let pointer = handler as u64; - Entry { - gdt_selector: gdt_selector, - pointer_low: pointer as u16, - pointer_middle: (pointer >> 16) as u16, - pointer_high: (pointer >> 32) as u32, - options: EntryOptions::new(), - reserved: 0, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct EntryOptions(BitField); - -impl EntryOptions { - fn minimal() -> Self { - let mut options = BitField::new(0); - options.set_range(9..12, 0b111); // required 'one' bits - EntryOptions(options) - } - - fn new() -> Self { - let mut options = Self::minimal(); - options.set_present(true); - options - } - - pub fn set_present(&mut self, present: bool) -> &mut Self { - self.0.set_bit(15, present); - self - } - - pub fn disable_interrupts(&mut self, disable: bool) -> &mut Self { - self.0.set_bit(8, !disable); - self - } - - pub fn set_privilege_level(&mut self, dpl: u16) -> &mut Self { - self.0.set_range(13..15, dpl); - self - } - - pub fn set_stack_index(&mut self, index: u16) -> &mut Self { - self.0.set_range(0..3, index); - self - } -} diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs deleted file mode 100644 index e628d1b9..00000000 --- a/src/interrupts/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -use x86::task::{load_ltr, TaskStateSegment}; -use vga_buffer::print_error; - -mod idt; -mod gdt; - -lazy_static! { - static ref IDT: idt::Idt = { - let mut idt = idt::Idt::new(); - - idt.set_handler(0, divide_by_zero_handler); - idt.set_handler(8, double_fault_handler).set_stack_index(1); - idt.set_handler(13, general_protection_fault_handler); - idt.set_handler(14, page_fault_handler); - - idt - }; - - static ref TSS: TaskStateSegment = { - let mut tss = TaskStateSegment::new(); - - let stack_size = 1024 * 3; // 3KiB - let stack_bottom = unsafe { - ::alloc::heap::allocate(stack_size, 16) as usize // TODO - }; - let stack_top = stack_bottom + stack_size; - - tss.ist[0] = stack_top as u64; - - tss - }; - - static ref GDT: Gdt = { - let mut table = gdt::Gdt::new(); - - let selectors = GdtSelectors { - code: table.add_entry(gdt::Entry::code_segment()), - data: table.add_entry(gdt::Entry::data_segment()), - tss: table.add_entry(gdt::Entry::tss_segment(&TSS)), - }; - - Gdt { - table: table, - selectors: selectors, - } - }; -} - -struct Gdt { - table: gdt::Gdt, - selectors: GdtSelectors, -} - -struct GdtSelectors { - code: gdt::Selector, - data: gdt::Selector, - tss: gdt::Selector, -} - -pub fn init() { - assert_has_not_been_called!(); - - unsafe { - GDT.table.load(); - gdt::reload_segment_registers(GDT.selectors.code, GDT.selectors.data); - gdt::load_ltr(GDT.selectors.tss); - } - IDT.load(); -} - -extern "C" fn divide_by_zero_handler() -> ! { - unsafe { print_error(format_args!("EXCEPTION: DIVIDE BY ZERO")) }; - loop {} -} - -extern "C" fn double_fault_handler() -> ! { - unsafe { print_error(format_args!("EXCEPTION: DOUBLE FAULT")) }; - unsafe { asm!("iretq")}; - loop {} -} - -extern "C" fn general_protection_fault_handler() -> ! { - unsafe { print_error(format_args!("EXCEPTION: GENERAL PROTECTION FAULT")) }; - loop {} -} - -extern "C" fn page_fault_handler() -> ! { - unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; - loop {} -} diff --git a/src/lib.rs b/src/lib.rs index 89fc5a86..9b131ead 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,6 @@ #![feature(lang_items)] #![feature(const_fn, unique)] #![feature(alloc, collections)] -#![feature(asm)] -#![feature(drop_types_in_const)] -#![feature(heap_api)] #![no_std] extern crate rlibc; @@ -29,12 +26,9 @@ extern crate alloc; #[macro_use] extern crate collections; -extern crate bit_field; - #[macro_use] mod vga_buffer; mod memory; -mod interrupts; #[no_mangle] pub extern "C" fn rust_main(multiboot_information_address: usize) { @@ -49,20 +43,11 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { // set up guard page and map the heap pages memory::init(boot_info); - interrupts::init(); + use alloc::boxed::Box; + let heap_test = Box::new(42); - //println!("{:?}", unsafe { *(0xdeadbeaf as *mut u32) }); - //unsafe { *(0xdeadbeaf as *mut u32) = 42 }; - - fn recursive() { - recursive(); - } - recursive(); - - unsafe { *(0xdeadbeaf as *mut u32) = 42 }; - - unsafe { - asm!("xor eax, eax; idiv eax" :::: "intel"); + for i in 0..10000 { + format!("Some String"); } println!("It did not crash!"); @@ -94,11 +79,8 @@ extern "C" fn eh_personality() {} #[cfg(not(test))] #[lang = "panic_fmt"] extern "C" fn panic_fmt(fmt: core::fmt::Arguments, file: &str, line: u32) -> ! { - use vga_buffer::print_error; - unsafe { - print_error(format_args!("\n\nPANIC in {} at line {}:", file, line)); - print_error(format_args!(" {}", fmt)); - } + println!("\n\nPANIC in {} at line {}:", file, line); + println!(" {}", fmt); loop {} } diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 7f319cdf..2546fdd5 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -135,15 +135,3 @@ struct ScreenChar { struct Buffer { chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], } - -pub unsafe fn print_error(fmt: fmt::Arguments) { - use core::fmt::Write; - - let mut writer = Writer { - column_position: 0, - color_code: ColorCode::new(Color::Red, Color::Black), - buffer: Unique::new(0xb8000 as *mut _), - }; - writer.new_line(); - writer.write_fmt(fmt); -} From 3b71e9e5a08a605ea30de6e93db376d449756bca Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 14:15:20 +0200 Subject: [PATCH 14/23] =?UTF-8?q?Add=20=E2=80=9CCatching=20Exceptions?= =?UTF-8?q?=E2=80=9D=20post=20to=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e000fdee..c46a58d6 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ This repository contains the source code for the _Writing an OS in Rust_ series - [Kernel Heap](http://os.phil-opp.com/kernel-heap.html) ([source code](https://github.com/phil-opp/blog_os/tree/kernel_heap)) +## Interrupts +- [Catching Exceptions](http://os.phil-opp.com/catching-exceptions.html) + ([source code](https://github.com/phil-opp/blog_os/tree/catching_exceptions)) + ## Additional Resources - [Cross Compile Binutils](http://os.phil-opp.com/cross-compile-binutils.html) - [Cross Compile libcore](http://os.phil-opp.com/cross-compile-libcore.html) From 745b1f6a0bef785ecfa3b70b4938d159ef018f7a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 14:36:23 +0200 Subject: [PATCH 15/23] =?UTF-8?q?Improve=20=5FUnwind=5FResume=20descriptio?= =?UTF-8?q?n=20and=20remove=20claim=20about=20the=20=E2=80=9Cnext=20post?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog/post/2016-04-11-kernel-heap.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/blog/post/2016-04-11-kernel-heap.md b/blog/post/2016-04-11-kernel-heap.md index d9164c20..82903723 100644 --- a/blog/post/2016-04-11-kernel-heap.md +++ b/blog/post/2016-04-11-kernel-heap.md @@ -326,9 +326,7 @@ target/x86_64-unknown-linux-gnu/debug/libblog_os.a(bump_allocator-[…].0.o): undefined reference to `_Unwind_Resume' ``` -This function is part of Rust's unwinding machinery. We disabled most of by passing `-Z no-landing-pads` to rustc, but apparently some panic related code still links to it. The new “[panic as abort]” feature might fix this. - -[panic as abort]: https://github.com/rust-lang/rust/issues/32837 +This function is part of Rust's unwinding machinery. We disabled most of by passing `-Z no-landing-pads` to rustc, but apparently our precompiled `libcollections` still links to it. To work around this issue for now, we add a dummy function: @@ -341,7 +339,7 @@ pub extern fn _Unwind_Resume() -> ! { } ``` -This is just a temporary fix to keep this post simple. The next post will resolve this issue in a better way using a new build setup. +This is just a temporary fix to keep this post simple. We will resolve this issue in a better way in a future post. Now our kernel compiles again. But when we run it, a triple fault occurs and causes permanent rebooting. We use QEMU for debugging as described [in the previous post][qemu debugging]: From 865e74bfadf7a990dd7e73cf5f5cfa71165706d5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 14:37:19 +0200 Subject: [PATCH 16/23] We don't overhaul our build system yet --- blog/post/2016-04-11-kernel-heap.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blog/post/2016-04-11-kernel-heap.md b/blog/post/2016-04-11-kernel-heap.md index 82903723..bfda7099 100644 --- a/blog/post/2016-04-11-kernel-heap.md +++ b/blog/post/2016-04-11-kernel-heap.md @@ -855,4 +855,6 @@ Now we're able to use heap storage in our kernel without leaking memory. This al [B-tree]: https://en.wikipedia.org/wiki/B-tree ## What's next? -This post concludes the section about memory management for now. We will revisit this topic eventually, but now it's time to explore other topics. The upcoming posts will be about CPU exceptions and interrupts. We will catch all page, double, and triple faults and create a driver to read keyboard input. But first, we need to improve our build setup. The next post will eliminate most of our Makefile using advanced Cargo features and prepare our kernel for interrupt handling. +This post concludes the section about memory management for now. We will revisit this topic eventually, but now it's time to explore other topics. The upcoming posts will be about CPU exceptions and interrupts. We will catch all page, double, and triple faults and create a driver to read keyboard input. The [next post] starts by setting up a so-called _Interrupt Descriptor Table_. + +[next post]: {{% relref "2016-05-28-catching-exceptions.md" %}} From e2b45d3971b579f5f6af1dffa953720c10c35b10 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:21:24 +0200 Subject: [PATCH 17/23] Fix minor errors in code snippets --- blog/post/2016-05-28-catching-exceptions.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/blog/post/2016-05-28-catching-exceptions.md b/blog/post/2016-05-28-catching-exceptions.md index fb7f0e73..a6afdac4 100644 --- a/blog/post/2016-05-28-catching-exceptions.md +++ b/blog/post/2016-05-28-catching-exceptions.md @@ -83,7 +83,7 @@ Now we create types for the IDT and its entries: ```rust // src/interrupts/idt.rs -use x86::segmentation::SegmentSelector; +use x86::segmentation::{self, SegmentSelector}; pub struct Idt([Entry; 16]); @@ -222,7 +222,7 @@ We take a GDT selector and a handler function as arguments and create a new IDT The `HandlerFunc` type is a type alias for a function type: ``` rust -type HandlerFunc = extern "C" fn() -> !; +pub type HandlerFunc = extern "C" fn() -> !; ``` It needs to be a function with a defined [calling convention], as it called directly by the hardware. The C calling convention is the de facto standard in OS development, so we're using it, too. The function takes no arguments, since the hardware doesn't supply any arguments when jumping to the handler function. @@ -251,7 +251,7 @@ impl Idt { impl Entry { fn missing() -> Self { Entry { - gdt_selector: 0, + gdt_selector: SegmentSelector::new(0), pointer_low: 0, pointer_middle: 0, pointer_high: 0, @@ -583,6 +583,8 @@ Let's use the new `print_error` function to print the page fault error: ```rust // in src/interrupts/mod.rs +use vga_buffer::print_error; + extern "C" fn page_fault_handler() -> ! { unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; loop {} From 245125cb4a6ff36fca0c6391aaf8763c6de31d34 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:44:32 +0200 Subject: [PATCH 18/23] Minor improvements to post --- blog/post/2016-05-28-catching-exceptions.md | 34 +++++++++++---------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/blog/post/2016-05-28-catching-exceptions.md b/blog/post/2016-05-28-catching-exceptions.md index a6afdac4..c4b02f25 100644 --- a/blog/post/2016-05-28-catching-exceptions.md +++ b/blog/post/2016-05-28-catching-exceptions.md @@ -19,8 +19,8 @@ We've already seen several types of exceptions in our kernel: - **Invalid Opcode**: This exception occurs when the current instruction is invalid. For example, this exception occurred when we tried to use SSE instructions before enabling SSE. Without SSE, the CPU didn't know the `movups` and `movaps` instructions, so it throws an exception when it stumbles over them. - **Page Fault**: A page fault occurs on illegal memory accesses. For example, if the current instruction tries to read from an unmapped page or tries to write to a read-only page. -- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered. -- **Triple Fault**: If another exception occurs when the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. This causes the bootloops we experienced in the previous posts. +- **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another exception exception occurs _while calling the exception handler_, the CPU raises a double fault exception. This exception also occurs when there is no handler function registered for an exception. +- **Triple Fault**: If an exception occurs while the CPU tries to call the double fault handler function, it issues a fatal _triple fault_. We can't catch or handle a triple fault. Most processors react by resetting themselves and rebooting the operating system. This causes the bootloops we experienced in the previous posts. For the full list of exceptions check out the [OSDev wiki][exceptions]. @@ -47,7 +47,7 @@ Bits | Name | Description 8 | 0: Interrupt Gate, 1: Trap Gate | If this bit is 0, interrupts are disabled when this handler is called. 9-11 | must be one | 12 | must be zero | -13‑14 | Descriptor Privilege Level (DPL) | The minimal required privilege level required for calling this handler. +13‑14 | Descriptor Privilege Level (DPL) | The minimal privilege level required for calling this handler. 15 | Present | Each exception has a predefined IDT index. For example the invalid opcode exception has table index 6 and the page fault exception has table index 14. Thus, the hardware can automatically load the corresponding IDT entry for each exception. The [Exception Table][exceptions] in the OSDev wiki shows the IDT indexes of all exceptions in the “Vector nr.” column. @@ -422,19 +422,21 @@ error: references in statics may only refer to immutable values [E0017] The reason is that the Rust compiler is not able to evaluate the value of the `static` at compile time. Maybe it will work someday when `const` functions become more powerful. But until then, we have to find another solution. ### Lazy Statics to the Rescue -Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values (e.g. the number of cores). +Fortunately the `lazy_static` macro exists. Instead of evaluating a `static` at compile time, the macro performs the initialization when the `static` is referenced the first time. Thus, we can do almost everything in the initialization block and are even able to read runtime values. With `lazy_static`, we can define our IDT without problems: ```rust +// in src/interrupts/mod.rs + lazy_static! { - static ref IDT: idt::Idt = { - let mut idt = idt::Idt::new(); + static ref IDT: idt::Idt = { + let mut idt = idt::Idt::new(); - idt.set_handler(14, page_fault_handler); + idt.set_handler(14, page_fault_handler); - idt - }; + idt + }; } ``` @@ -512,9 +514,9 @@ Reading symbols from build/kernel-x86_64.bin...done. Now we can connect to our running QEMU instance on port `1234`: ``` -(gdb) target extern :1234 +(gdb) target remote :1234 Remote debugging using :1234 -0x00000000001031dd in spin::mutex::cpu_relax () +0x00000000001031bd in spin::mutex::cpu_relax () at /home/.../spin-0.3.5/src/mutex.rs:102 102 unsafe { asm!("pause" :::: "volatile"); } ``` @@ -522,16 +524,16 @@ So we're locked in a function named `mutex::cpu_relax` inside the `spin` crate. ``` (gdb) backtrace -#0 0x00000000001031dd in spin::mutex::cpu_relax () +#0 0x00000000001031bd in spin::mutex::cpu_relax () at /home/.../spin-0.3.5/src/mutex.rs:102 #1 spin::mutex::{{impl}}::obtain_lock ( - self=0x117220 ) + self=0x111230 ) at /home/.../spin-0.3.5/src/mutex.rs:142 -#2 0x0000000000103163 in spin::mutex::{{impl}}::lock ( - self=0x117220 ) + self=0x111230 ) at /home/.../spin-0.3.5/src/mutex.rs:163 -#3 0x000000000010de09 in blog_os::interrupts::page_fault_handler () +#3 0x000000000010da59 in blog_os::interrupts::page_fault_handler () at src/vga_buffer.rs:31 ... ``` From b84c5822df6bf79b0fc9d146a93e58d9cba6cde1 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:22:18 +0200 Subject: [PATCH 19/23] Create interrupt module with IDT submodule --- Cargo.toml | 5 +- src/interrupts/idt.rs | 103 ++++++++++++++++++++++++++++++++++++++++++ src/interrupts/mod.rs | 1 + src/lib.rs | 3 ++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 src/interrupts/idt.rs create mode 100644 src/interrupts/mod.rs diff --git a/Cargo.toml b/Cargo.toml index d0cb6f3c..a5503538 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,11 @@ name = "blog_os" version = "0.1.0" [dependencies] +bit_field = "0.1.0" +bitflags = "0.7.0" once = "0.2.1" rlibc = "0.1.4" spin = "0.3.4" -bitflags = "0.7.0" [dependencies.hole_list_allocator] path = "libs/hole_list_allocator" @@ -17,7 +18,7 @@ git = "https://github.com/phil-opp/multiboot2-elf64" [dependencies.x86] default-features = false -version = "0.6.0" +version = "0.7.0" [lib] crate-type = ["staticlib"] diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs new file mode 100644 index 00000000..ea7c41cd --- /dev/null +++ b/src/interrupts/idt.rs @@ -0,0 +1,103 @@ +use x86::segmentation::{self, SegmentSelector}; + +pub struct Idt([Entry; 16]); + +impl Idt { + pub fn new() -> Idt { + Idt([Entry::missing(); 16]) + } + + pub fn set_handler(&mut self, entry: u8, handler: HandlerFunc) -> &mut EntryOptions { + self.0[entry as usize] = Entry::new(segmentation::cs(), handler); + &mut self.0[entry as usize].options + } + + pub fn load(&'static self) { + use x86::dtables::{DescriptorTablePointer, lidt}; + use core::mem::size_of; + + let ptr = DescriptorTablePointer { + base: self as *const _ as u64, + limit: (size_of::() - 1) as u16, + }; + + unsafe { lidt(&ptr) }; + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed)] +pub struct Entry { + pointer_low: u16, + gdt_selector: SegmentSelector, + options: EntryOptions, + pointer_middle: u16, + pointer_high: u32, + reserved: u32, +} + +type HandlerFunc = extern "C" fn() -> !; + +impl Entry { + fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { + let pointer = handler as u64; + Entry { + gdt_selector: gdt_selector, + pointer_low: pointer as u16, + pointer_middle: (pointer >> 16) as u16, + pointer_high: (pointer >> 32) as u32, + options: EntryOptions::new(), + reserved: 0, + } + } + + fn missing() -> Self { + Entry { + gdt_selector: SegmentSelector::new(0), + pointer_low: 0, + pointer_middle: 0, + pointer_high: 0, + options: EntryOptions::minimal(), + reserved: 0, + } + } +} + +use bit_field::BitField; + +#[derive(Debug, Clone, Copy)] +pub struct EntryOptions(BitField); + +impl EntryOptions { + fn minimal() -> Self { + let mut options = BitField::new(0); + options.set_range(9..12, 0b111); // 'must-be-one' bits + EntryOptions(options) + } + + fn new() -> Self { + let mut options = Self::minimal(); + options.set_present(true).disable_interrupts(true); + options + } + + fn set_present(&mut self, present: bool) -> &mut Self { + self.0.set_bit(15, present); + self + } + + fn disable_interrupts(&mut self, disable: bool) -> &mut Self { + self.0.set_bit(8, !disable); + self + } + + fn set_privilege_level(&mut self, dpl: u16) -> &mut Self { + self.0.set_range(13..15, dpl); + self + } + + fn set_stack_index(&mut self, index: u16) -> &mut Self { + self.0.set_range(0..3, index); + self + } +} diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs new file mode 100644 index 00000000..e640a3f1 --- /dev/null +++ b/src/interrupts/mod.rs @@ -0,0 +1 @@ +mod idt; diff --git a/src/lib.rs b/src/lib.rs index 9b131ead..d0512afd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ extern crate bitflags; extern crate x86; #[macro_use] extern crate once; +extern crate bit_field; extern crate hole_list_allocator; extern crate alloc; @@ -30,6 +31,8 @@ extern crate collections; mod vga_buffer; mod memory; +mod interrupts; + #[no_mangle] pub extern "C" fn rust_main(multiboot_information_address: usize) { // ATTENTION: we have a very small stack and no guard page From c65b16f42d27daab392a035e41eca80054122f2a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:26:20 +0200 Subject: [PATCH 20/23] Create a static IDT with a page fault handler function --- src/interrupts/mod.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index e640a3f1..efaa55df 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -1 +1,20 @@ mod idt; + +lazy_static! { + static ref IDT: idt::Idt = { + let mut idt = idt::Idt::new(); + + idt.set_handler(14, page_fault_handler); + + idt + }; +} + +pub fn init() { + IDT.load(); +} + +extern "C" fn page_fault_handler() -> ! { + println!("EXCEPTION: PAGE FAULT"); + loop {} +} From 2d15a7bdb17a33eebc486df2d4d1137b14e3341c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:28:49 +0200 Subject: [PATCH 21/23] Make HandlerFunc public, because it is part of a public interface --- src/interrupts/idt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interrupts/idt.rs b/src/interrupts/idt.rs index ea7c41cd..af590880 100644 --- a/src/interrupts/idt.rs +++ b/src/interrupts/idt.rs @@ -36,7 +36,7 @@ pub struct Entry { reserved: u32, } -type HandlerFunc = extern "C" fn() -> !; +pub type HandlerFunc = extern "C" fn() -> !; impl Entry { fn new(gdt_selector: SegmentSelector, handler: HandlerFunc) -> Self { From c1df1d33549892d7c0ff97dc1731205588544b3f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:29:11 +0200 Subject: [PATCH 22/23] Initialize the IDT and provoke a page fault --- src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0512afd..06b601f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,15 +46,13 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { // set up guard page and map the heap pages memory::init(boot_info); - use alloc::boxed::Box; - let heap_test = Box::new(42); + // initialize our IDT + interrupts::init(); - for i in 0..10000 { - format!("Some String"); - } + // provoke a page fault by writing to some random address + unsafe{ *(0xdeadbeaf as *mut u64) = 42 }; println!("It did not crash!"); - loop {} } From f58a6fe18575db588a66c6d007868e40c9581405 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 28 May 2016 15:41:16 +0200 Subject: [PATCH 23/23] Add a `print_error` function, which works for exceptions inside println --- src/interrupts/mod.rs | 4 +++- src/lib.rs | 4 ++-- src/vga_buffer.rs | 13 +++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index efaa55df..4beffbc8 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -14,7 +14,9 @@ pub fn init() { IDT.load(); } +use vga_buffer::print_error; + extern "C" fn page_fault_handler() -> ! { - println!("EXCEPTION: PAGE FAULT"); + unsafe { print_error(format_args!("EXCEPTION: PAGE FAULT")) }; loop {} } diff --git a/src/lib.rs b/src/lib.rs index 06b601f9..3eb08628 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,8 +49,8 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { // initialize our IDT interrupts::init(); - // provoke a page fault by writing to some random address - unsafe{ *(0xdeadbeaf as *mut u64) = 42 }; + // provoke a page fault inside println + println!("{:?}", unsafe{ *(0xdeadbeaf as *mut u64) = 42 }); println!("It did not crash!"); loop {} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 2546fdd5..2b4499da 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -38,6 +38,19 @@ pub fn clear_screen() { } } +pub unsafe fn print_error(fmt: fmt::Arguments) { + use core::fmt::Write; + + let mut writer = Writer { + column_position: 0, + color_code: ColorCode::new(Color::Red, Color::Black), + buffer: Unique::new(0xb8000 as *mut _), + }; + writer.new_line(); + writer.write_fmt(fmt); +} + + #[allow(dead_code)] #[repr(u8)] pub enum Color {