Skip to content

Commit

Permalink
Merge pull request #475 from acheronfail/refactor-interrupts
Browse files Browse the repository at this point in the history
Refactor exception code into new `interrupts` module
  • Loading branch information
phil-opp authored Oct 18, 2018
2 parents a198de6 + 48f9e9c commit a1dd6b2
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 68 deletions.
87 changes: 70 additions & 17 deletions blog/content/second-edition/posts/06-cpu-exceptions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,14 @@ If you are interested in more details: We also have a series of posts that expla
[too-much-magic]: #too-much-magic

## Implementation
Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We start by creating an `init_idt` function that creates a new `InterruptDescriptorTable`:
Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We'll start by creating a new interrupts module in `src/interrupts.rs`, that first creates an `init_idt` function that creates a new `InterruptDescriptorTable`:

``` rust
// in src/main.rs
// in src/lib.rs

pub mod interrupts;

// in src/interrupts.rs

extern crate x86_64;
use x86_64::structures::idt::InterruptDescriptorTable;
Expand All @@ -228,7 +232,7 @@ The breakpoint exception is commonly used in debuggers: When the user sets a bre
For our use case, we don't need to overwrite any instructions. Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program. So let's create a simple `breakpoint_handler` function and add it to our IDT:

```rust
/// in src/main.rs
// in src/interrupts.rs

use x86_64::structures::idt::{InterruptDescriptorTable, ExceptionStackFrame};

Expand All @@ -246,7 +250,7 @@ extern "x86-interrupt" fn breakpoint_handler(

Our handler just outputs a message and pretty-prints the exception stack frame.

When we try to compile it, the following error occurs:
When we try to compile it, the following errors occur:

```
error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue #40180)
Expand All @@ -260,7 +264,45 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue
= help: add #![feature(abi_x86_interrupt)] to the crate attributes to enable
```

This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `main.rs`.
This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `lib.rs`.

```
error: cannot find macro `println!` in this scope
--> src/interrupts.rs:40:5
|
40 | println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
| ^^^^^^^
|
= help: have you added the `#[macro_use]` on the module/import?
```

This happened because we forgot to add `#[macro_use]` before our import of the `vga_buffer` module.

```rust
// in src/lib.rs

pub mod gdt;
pub mod interrupts;
pub mod serial;
#[macro_use] // new
pub mod vga_buffer;
```

However, after adding `#[macro_use]` before the module import, we still get the same error. Sometimes this can be confusing, but it's actually a quirk of how Rust's macro system works. Macros _must be defined_ before you can use them. This is one case where import order matters in Rust. We can easily fix this by ensuring the order of our imports places the macros first:

```rust
// in src/lib.rs

#[macro_use]
pub mod vga_buffer; // import this before other modules so its macros may be used
pub mod gdt;
pub mod interrupts;
pub mod serial;
```

Now we can use our `print!` and `println!` macros in `interrupts.rs`. If you'd like to know more about the ins and outs of macros and how they differ from functions [you can find more information here][in-depth-rust-macros].

[in-depth-rust-macros]: https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html

### Loading the IDT
In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it:
Expand All @@ -269,7 +311,7 @@ In order that the CPU uses our new interrupt descriptor table, we need to load i
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load

```rust
// in src/main.rs
// in src/interrupts.rs

pub fn init_idt() {
let mut idt = InterruptDescriptorTable::new();
Expand Down Expand Up @@ -339,10 +381,7 @@ We already imported the `lazy_static` crate when we [created an abstraction for
[vga text buffer lazy static]: ./second-edition/posts/03-vga-text-buffer/index.md#lazy-statics

```rust
// in src/main.rs

#[macro_use]
extern crate lazy_static;
// in src/interrupts.rs

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
Expand Down Expand Up @@ -370,7 +409,7 @@ Now we should be able to handle breakpoint exceptions! Let's try it in our `_sta
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");

init_idt();
blog_os::interrupts::init_idt();

// invoke a breakpoint exception
x86_64::instructions::int3();
Expand All @@ -395,15 +434,14 @@ Let's create an integration test that ensures that the above continues to work.
```rust
// in src/bin/test-exception-breakpoint.rs

use blog_os::exit_qemu;
[…]
use core::sync::atomic::{AtomicUsize, Ordering};

static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);

#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start() -> ! {
init_idt();
init_test_idt();

// invoke a breakpoint exception
x86_64::instructions::int3();
Expand All @@ -424,16 +462,31 @@ pub extern "C" fn _start() -> ! {
loop {}
}

extern "x86-interrupt" fn breakpoint_handler(_: &mut ExceptionStackFrame) {

lazy_static! {
static ref TEST_IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
}

pub fn init_test_idt() {
TEST_IDT.load();
}

extern "x86-interrupt" fn breakpoint_handler(
_stack_frame: &mut ExceptionStackFrame)
{
BREAKPOINT_HANDLER_CALLED.fetch_add(1, Ordering::SeqCst);
}

// […]
[…]
```

For space reasons we don't show the full content here. You can find the full file [on Github](https://github.com/phil-opp/blog_os/blob/master/src/bin/test-exception-breakpoint.rs).

It is basically a copy of our `main.rs` with some modifications to `_start` and `breakpoint_handler`. The most interesting part is the `BREAKPOINT_HANDLER_CALLER` static. It is an [`AtomicUsize`], an integer type that can be safely concurrently modifies because all of its operations are atomic. We increment it when the `breakpoint_handler` is called and verify in our `_start` function that the handler was called exactly once.
It is similar to our `main.rs`, but uses a custom IDT called `TEST_IDT` and different `_start` and `breakpoint_handler` functions. The most interesting part is the `BREAKPOINT_HANDLER_CALLER` static. It is an [`AtomicUsize`], an integer type that can be safely concurrently modifies because all of its operations are atomic. We increment it when the `breakpoint_handler` is called and verify in our `_start` function that the handler was called exactly once.

[`AtomicUsize`]: https://doc.rust-lang.org/core/sync/atomic/struct.AtomicUsize.html

Expand Down
14 changes: 8 additions & 6 deletions blog/content/second-edition/posts/07-double-faults/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Let's provoke a double fault by triggering an exception for that we didn't defin
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");

init_idt();
blog_os::interrupts::init_idt();

// trigger a page fault
unsafe {
Expand All @@ -60,7 +60,7 @@ So in order to prevent this triple fault, we need to either provide a handler fu
A double fault is a normal exception with an error code, so we can specify a handler function similar to our breakpoint handler:

```rust
// in src/main.rs
// in src/interrupts.rs

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
Expand Down Expand Up @@ -162,7 +162,7 @@ Let's try it ourselves! We can easily provoke a kernel stack overflow by calling
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");

init_idt();
blog_os::interrupts::init_idt();

fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
Expand Down Expand Up @@ -314,7 +314,7 @@ pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");

blog_os::gdt::init();
init_idt();
blog_os::interrupts::init_idt();

[…]
}
Expand Down Expand Up @@ -380,15 +380,17 @@ We reload the code segment register using [`set_cs`] and to load the TSS using [
Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT:

```rust
// in src/main.rs
// in src/interrupts.rs

use gdt;

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault.set_handler_fn(double_fault_handler)
.set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX); // new
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); // new
}

idt
Expand Down
8 changes: 4 additions & 4 deletions src/bin/test-exception-breakpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);
#[cfg(not(test))]
#[no_mangle]
pub extern "C" fn _start() -> ! {
init_idt();
init_test_idt();

// invoke a breakpoint exception
x86_64::instructions::int3();
Expand Down Expand Up @@ -59,15 +59,15 @@ fn panic(info: &PanicInfo) -> ! {
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
static ref TEST_IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
}

pub fn init_idt() {
IDT.load();
pub fn init_test_idt() {
TEST_IDT.load();
}

extern "x86-interrupt" fn breakpoint_handler(_stack_frame: &mut ExceptionStackFrame) {
Expand Down
8 changes: 4 additions & 4 deletions src/bin/test-exception-double-fault-stack-overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use core::panic::PanicInfo;
#[allow(unconditional_recursion)]
pub extern "C" fn _start() -> ! {
blog_os::gdt::init();
init_idt();
init_test_idt();

fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
Expand Down Expand Up @@ -53,7 +53,7 @@ fn panic(info: &PanicInfo) -> ! {
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
static ref TEST_IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
unsafe {
idt.double_fault
Expand All @@ -65,8 +65,8 @@ lazy_static! {
};
}

pub fn init_idt() {
IDT.load();
pub fn init_test_idt() {
TEST_IDT.load();
}

extern "x86-interrupt" fn double_fault_handler(
Expand Down
32 changes: 32 additions & 0 deletions src/interrupts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};
use gdt;

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault
.set_handler_fn(double_fault_handler)
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
}

idt
};
}

pub fn init_idt() {
IDT.load();
}

extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}

extern "x86-interrupt" fn double_fault_handler(
stack_frame: &mut ExceptionStackFrame,
_error_code: u64,
) {
println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
loop {}
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![no_std] // don't link the Rust standard library
#![feature(abi_x86_interrupt)]

extern crate bootloader_precompiled;
extern crate spin;
Expand All @@ -13,9 +14,11 @@ extern crate array_init;
#[cfg(test)]
extern crate std;

#[macro_use]
pub mod vga_buffer;
pub mod gdt;
pub mod interrupts;
pub mod serial;
pub mod vga_buffer;

pub unsafe fn exit_qemu() {
use x86_64::instructions::port::Port;
Expand Down
37 changes: 1 addition & 36 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
#![feature(abi_x86_interrupt)]
#![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]

#[macro_use]
extern crate blog_os;
extern crate x86_64;
#[macro_use]
extern crate lazy_static;

use core::panic::PanicInfo;

Expand All @@ -19,7 +16,7 @@ pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");

blog_os::gdt::init();
init_idt();
blog_os::interrupts::init_idt();

fn stack_overflow() {
stack_overflow(); // for each recursion, the return address is pushed
Expand All @@ -39,35 +36,3 @@ fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}

use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};

lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault
.set_handler_fn(double_fault_handler)
.set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX);
}

idt
};
}

pub fn init_idt() {
IDT.load();
}

extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}

extern "x86-interrupt" fn double_fault_handler(
stack_frame: &mut ExceptionStackFrame,
_error_code: u64,
) {
println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
loop {}
}

0 comments on commit a1dd6b2

Please sign in to comment.