Rename naked exception handling section
@@ -0,0 +1,559 @@
|
||||
+++
|
||||
title = "Catching Exceptions"
|
||||
order = 1
|
||||
url = "catching-exceptions"
|
||||
date = "2016-05-28"
|
||||
updated = "2016-06-25"
|
||||
+++
|
||||
|
||||
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 divide-by-zero faults.
|
||||
|
||||
<!-- more --><aside id="toc"></aside>
|
||||
|
||||
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
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: ./extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: ./posts/09-handling-exceptions/index.md
|
||||
|
||||
## 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.
|
||||
|
||||
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 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].
|
||||
|
||||
[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:
|
||||
|
||||
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 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.
|
||||
|
||||
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_64::instructions::segmentation;
|
||||
use x86_64::structures::gdt::SegmentSelector;
|
||||
use x86_64::PrivilegeLevel;
|
||||
|
||||
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 {...}
|
||||
|
||||
pub fn set_present(&mut self, present: bool) {...}
|
||||
|
||||
pub fn disable_interrupts(&mut self, disable: bool) {...}
|
||||
|
||||
pub fn set_privilege_level(&mut self, dpl: u16) {...}
|
||||
|
||||
pub 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` trait that provides the following [Range]-based API:
|
||||
|
||||
[Range]: https://doc.rust-lang.org/nightly/core/ops/struct.Range.html
|
||||
|
||||
``` rust
|
||||
self.0.set_bits(0..3, stack_index);
|
||||
```
|
||||
|
||||
I think it is much more readable, since we abstracted away all bit-masking details. The `BitField` trait 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]: https://crates.io/crates/bit_field
|
||||
|
||||
Now we can use the trait to implement the methods of `EntryOptions`:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/idt.rs
|
||||
|
||||
use bit_field::BitField;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EntryOptions(u16);
|
||||
|
||||
impl EntryOptions {
|
||||
fn minimal() -> Self {
|
||||
let mut options = 0;
|
||||
options.set_bits(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
|
||||
}
|
||||
|
||||
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_bits(13..15, dpl);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_stack_index(&mut self, index: u16) -> &mut Self {
|
||||
self.0.set_bits(0..3, index);
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
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 {
|
||||
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 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
|
||||
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.
|
||||
|
||||
[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
|
||||
|
||||

|
||||
|
||||
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 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
|
||||
Let's add a function to create new interrupt descriptor tables:
|
||||
|
||||
```rust
|
||||
impl Idt {
|
||||
pub fn new() -> Idt {
|
||||
Idt([Entry::missing(); 16])
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn missing() -> Self {
|
||||
Entry {
|
||||
gdt_selector: SegmentSelector::new(0, PrivilegeLevel::Ring0),
|
||||
pointer_low: 0,
|
||||
pointer_middle: 0,
|
||||
pointer_high: 0,
|
||||
options: EntryOptions::minimal(),
|
||||
reserved: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
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.
|
||||
|
||||
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` function of the [x86_64 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_64 crate]: https://docs.rs/x86_64
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
[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_64 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_64 crate]: http://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/struct.DescriptorTablePointer.html
|
||||
[lidt function]: http://docs.rs/x86_64/0.1.0/x86_64/instructions/tables/fn.lidt.html
|
||||
|
||||
```rust
|
||||
impl Idt {
|
||||
pub fn load(&self) {
|
||||
use x86_64::instructions::tables::{DescriptorTablePointer, lidt};
|
||||
use core::mem::size_of;
|
||||
|
||||
let ptr = DescriptorTablePointer {
|
||||
base: self as *const _ as u64,
|
||||
limit: (size_of::<Self>() - 1) as u16,
|
||||
};
|
||||
|
||||
unsafe { lidt(&ptr) };
|
||||
}
|
||||
}
|
||||
```
|
||||
The method does not need to modify the IDT, so it takes `self` by immutable reference. First, we create a `DescriptorTablePointer` and then we pass it to `lidt`. The `lidt` function expects that the `base` field has the type `u64`, therefore we need to cast the `self` pointer. For calculating the `limit` we use [mem::size_of]. The additional `-1` is needed because the limit field has to be the maximum addressable byte (inclusive bound). We need an unsafe block around `lidt`, because the function assumes that the specified handler addresses are valid.
|
||||
|
||||
[mem::size_of]: https://doc.rust-lang.org/nightly/core/mem/fn.size_of.html
|
||||
|
||||
#### 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. 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. 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.
|
||||
|
||||
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) {...}
|
||||
// ^^^^^^^ 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:
|
||||
|
||||
```
|
||||
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(0, divide_by_zero_handler);
|
||||
|
||||
idt
|
||||
};
|
||||
|
||||
extern "C" fn divide_by_zero_handler() -> ! {
|
||||
println!("EXCEPTION: DIVIDE BY ZERO");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
We register a single handler function for a [divide by zero error] \(index 0). Like the name says, this exception occurs when dividing a number by 0. Thus we have an easy way to test our new exception handler.
|
||||
|
||||
[divide by zero error]: http://wiki.osdev.org/Exceptions#Divide-by-zero_Error
|
||||
|
||||
However, it doesn't work this way:
|
||||
|
||||
```
|
||||
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]
|
||||
...
|
||||
```
|
||||
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.
|
||||
|
||||
Let's add the `lazy_static` crate to our project:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
```
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "0.2.1"
|
||||
features = ["spin_no_std"]
|
||||
```
|
||||
We need the `spin_no_std` feature, since we don't link the standard library.
|
||||
|
||||
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();
|
||||
|
||||
idt.set_handler(0, divide_by_zero_handler);
|
||||
|
||||
idt
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now we're ready to load our IDT! Therefore we add a `interrupts::init` function:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
pub fn init() {
|
||||
IDT.load();
|
||||
}
|
||||
```
|
||||
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
|
||||
Now we should be able to catch page faults! Let's try it in our `rust_main`:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
pub extern "C" fn rust_main(...) {
|
||||
...
|
||||
memory::init(boot_info);
|
||||
|
||||
// initialize our IDT
|
||||
interrupts::init();
|
||||
|
||||
// provoke a divide-by-zero fault
|
||||
42 / 0;
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
When we run it, we get a runtime panic:
|
||||
|
||||
```
|
||||
PANIC in src/lib.rs at line 57:
|
||||
attempted to divide by zero
|
||||
```
|
||||
|
||||
That's a not our exception handler. The reason is that Rust itself checks for a possible division by zero and panics in that case. So in order to raise a divide-by-zero error in the CPU, we need to bypass the Rust compiler somehow.
|
||||
|
||||
### Inline Assembly
|
||||
In order to cause a divide-by-zero exception, we need to execute a [div] or [idiv] assembly instruction with operand 0. We could write a small assembly function and call it from our Rust code. An easier way is to use Rust's [inline assembly] macro.
|
||||
|
||||
[div]: http://x86.renejeschke.de/html/file_module_x86_id_72.html
|
||||
[idiv]: http://x86.renejeschke.de/html/file_module_x86_id_137.html
|
||||
[inline assembly]: https://doc.rust-lang.org/book/inline-assembly.html
|
||||
|
||||
Inline assembly allows us to write raw x86 assembly within a Rust function. The feature is unstable, so we need to add `#![feature(asm)]` to our `src/lib.rs`. Then we're able to write a `divide_by_zero` function:
|
||||
|
||||
```rust
|
||||
fn divide_by_zero() {
|
||||
unsafe {
|
||||
asm!("mov dx, 0; div dx" ::: "ax", "dx" : "volatile", "intel")
|
||||
}
|
||||
}
|
||||
```
|
||||
Let's try to decode it:
|
||||
|
||||
- The `asm!` macro emits raw assembly instructions, so it's `unsafe` to use it.
|
||||
- We insert two assembly instructions here: `mov dx, 0` and `div dx`. The former loads a 0 into the `dx` register (a subset of `rdx`) and the latter divides the `ax` register by `dx`. (The `div` instruction always implicitly operates on the `ax` register).
|
||||
- The colons are separators. After the first `:` we could specify output operands and after the second `:` we could specify input operands. We need neither, so we leave these areas empty.
|
||||
- After the third colon, we specify the so-called _clobbers_. These tell the compiler that our assembly modifies the values of some registers. Otherwise, the compiler assumes that the registers preserve their value. In our case, we clobber `dx` (we load 0 to it) and `ax` (the `div` instruction places the result in it).
|
||||
- The last block (after the 4th colon) specifies some options. The `volatile` option tells the compiler: “This code has side effects. Do not delete it and do not move it elsewhere”. In our case, the “side effect” is the divide-by-zero exception. Finally, the `intel` option allows us to use the Intel assembly syntax instead of the default AT&T syntax.
|
||||
|
||||
Let's use our new `divide_by_zero` function to raise a CPU exception:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
pub extern "C" fn rust_main(...) {
|
||||
...
|
||||
|
||||
// provoke a divide-by-zero fault
|
||||
divide_by_zero();
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
It works! We see a `EXCEPTION: DIVIDE BY ZERO` message at the bottom of our screen:
|
||||
|
||||

|
||||
|
||||
## What's next?
|
||||
We've successfully caught our first exception! However, our `EXCEPTION: DIVIDE BY ZERO` message doesn't contain much information about the cause of the exception. The next post improves the situation by printing i.a. the current stack pointer and address of the causing instruction. We will also explore other exceptions such as page faults, for which the CPU pushes an _error code_ on the stack.
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,661 @@
|
||||
+++
|
||||
title = "Better Exception Messages"
|
||||
order = 2
|
||||
url = "better-exception-messages"
|
||||
date = "2016-08-03"
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
|
||||
In this post, we explore exceptions in more detail. Our goal is to print additional information when an exception occurs, for example the values of the instruction and stack pointer. In the course of this, we will explore inline assembly and naked functions. We will also add a handler function for page faults and read the associated error code.
|
||||
|
||||
<!-- more --><aside id="toc"></aside>
|
||||
|
||||
As always, the complete source code is on [Github]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a [gitter chat] and a comment section at the end of this page.
|
||||
|
||||
[Github]: https://github.com/phil-opp/blog_os/tree/better_exception_messages
|
||||
[issues]: https://github.com/phil-opp/blog_os/issues
|
||||
[gitter chat]: https://gitter.im/phil-opp/blog_os
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: ./extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: ./posts/09-handling-exceptions/index.md
|
||||
|
||||
## Exceptions in Detail
|
||||
An exception signals that something is wrong with the currently-executed instruction. Whenever an exception occurs, the CPU interrupts its current work and starts an internal exception routine.
|
||||
|
||||
This routine involves reading the interrupt descriptor table and invoking the registered handler function. But first, the CPU pushes various information onto the stack, which describe the current state and provide information about the cause of the exception:
|
||||
|
||||

|
||||
|
||||
The pushed information contain the instruction and stack pointer, the current CPU flags, and (for some exceptions) an error code, which contains further information about the cause of the exception. Let's look at the fields in detail:
|
||||
|
||||
- First, the CPU aligns the stack pointer on a 16-byte boundary. This allows the handler function to use SSE instructions, which partly expect such an alignment.
|
||||
- After that, the CPU pushes the stack segment descriptor (SS) and the old stack pointer (from before the alignment) onto the stack. This allows us to restore the previous stack pointer when we want to resume the interrupted program.
|
||||
- Then the CPU pushes the contents of the [RFLAGS] register. This register contains various state information of the interrupted program. For example, it indicates if interrupts were enabled and whether the last executed instruction returned zero.
|
||||
- Next the CPU pushes the instruction pointer and its code segment descriptor onto the stack. This tells us the address of the last executed instruction, which caused the exception.
|
||||
- Finally, the CPU pushes an error code for some exceptions. This error code only exists for exceptions such as page faults or general protection faults and provides additional information. For example, it tells us whether a page fault was caused by a read or a write request.
|
||||
|
||||
[RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
|
||||
|
||||
## Printing the Exception Stack Frame
|
||||
Let's create a struct that represents the exception stack frame:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct ExceptionStackFrame {
|
||||
instruction_pointer: u64,
|
||||
code_segment: u64,
|
||||
cpu_flags: u64,
|
||||
stack_pointer: u64,
|
||||
stack_segment: u64,
|
||||
}
|
||||
```
|
||||
The divide-by-zero fault pushes no error code, so we leave it out for now. Note that the stack grows downwards in memory, so we need to declare the fields in reverse order (compared to the figure above).
|
||||
|
||||
Now we need a way to find the memory address of this stack frame. When we look at the above graphic again, we see that the start address of the exception stack frame is the new stack pointer. So we just need to read the value of `rsp` at the very beginning of our handler function:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn divide_by_zero_handler() -> ! {
|
||||
let stack_frame: &ExceptionStackFrame;
|
||||
unsafe {
|
||||
asm!("mov $0, rsp" : "=r"(stack_frame) ::: "intel");
|
||||
}
|
||||
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame);
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
We're using [inline assembly] here to load the value from the `rsp` register into `stack_frame`. The syntax is a bit strange, so here's a quick explanation:
|
||||
|
||||
[inline assembly]: https://doc.rust-lang.org/nightly/book/inline-assembly.html
|
||||
|
||||
- The `asm!` macro emits raw assembly instructions. This is the only way to read raw register values in Rust.
|
||||
- We insert a single assembly instruction: `mov $0, rsp`. It moves the value of `rsp` to some register (the `$0` is a placeholder for an arbitrary register, which gets filled by the compiler).
|
||||
- The colons are separators. After the first colon, the `asm!` macro expects output operands. We're specifying our `stack_frame` variable as a single output operand here. The `=r` tells the compiler that it should use any register for the first placeholder `$0`.
|
||||
- After the second colon, we can specify input operands. We don't need any, therefore we leave it empty.
|
||||
- After the third colon, the macro expects so called [clobbers]. We don't change any register values, so we leave it empty too.
|
||||
- The last block (after the 4th colon) specifies options. The `intel` option tells the compiler that our code is in Intel assembly syntax (instead of the default AT&T syntax).
|
||||
|
||||
[clobbers]: https://doc.rust-lang.org/nightly/book/inline-assembly.html#clobbers
|
||||
|
||||
So the inline assembly loads the stack pointer value to `stack_frame` at the very beginning of our function. Thus we have a pointer to the exception stack frame and are able to pretty-print its `Debug` formatting through the `{:#?}` argument.
|
||||
|
||||
### Testing it
|
||||
Let's try it by executing `make run`:
|
||||
|
||||

|
||||
|
||||
Those `ExceptionStackFrame` values look very wrong. The instruction pointer definitely shouldn't be 1 and the code segment should be `0x8` instead of some big number. So what's going on here?
|
||||
|
||||
### Debugging
|
||||
It seems like we somehow got the pointer wrong. The `ExceptionStackFrame` type and our inline assembly seem correct, so something must be modifying `rsp` before we load it into `stack_frame`.
|
||||
|
||||
Let's see what's happening by looking at the disassembly of our function:
|
||||
|
||||
```
|
||||
> objdump -d build/kernel-x86_64.bin | grep -A20 "divide_by_zero_handler"
|
||||
|
||||
[...]
|
||||
000000000010ced0 <_ZN7blog_os10interrupts22divide_by_zero_handler17h62189e8E>:
|
||||
10ced0: 55 push %rbp
|
||||
10ced1: 48 89 e5 mov %rsp,%rbp
|
||||
10ced4: 48 81 ec b0 00 00 00 sub $0xb0,%rsp
|
||||
10cedb: 48 8d 45 98 lea -0x68(%rbp),%rax
|
||||
10cedf: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx
|
||||
10cee6: 1d 1d 1d
|
||||
10cee9: 48 89 4d 98 mov %rcx,-0x68(%rbp)
|
||||
10ceed: 48 89 4d f8 mov %rcx,-0x8(%rbp)
|
||||
10cef1: 48 89 e1 mov %rsp,%rcx
|
||||
10cef4: 48 89 4d f8 mov %rcx,-0x8(%rbp)
|
||||
10cef8: ...
|
||||
[...]
|
||||
```
|
||||
Our `divide_by_zero_handler` starts at address `0x10ced0`. Let's look at the instruction at address `0x10cef1`:
|
||||
|
||||
```
|
||||
mov %rsp,%rcx
|
||||
```
|
||||
This is our inline assembly instruction, which loads the stack pointer into the `stack_frame` variable. It just looks a bit different, since it's in AT&T syntax and contains `rcx` instead of our `$0` placeholder. It moves `rsp` to `rcx`, and then the next instruction (`mov %rcx,-0x8(%rbp)`) moves `rcx` to the variable on the stack.
|
||||
|
||||
We can clearly see the problem here: The compiler inserted various other instructions before our inline assembly. These instructions modify the stack pointer so that we don't read the original `rsp` value and get a wrong pointer. But why is the compiler doing this?
|
||||
|
||||
The reason is that we need some place on the stack to store things like variables. Therefore the compiler inserts a so-called _[function prologue]_, which prepares the stack and reserves space for all variables. In our case, the compiler subtracts from the stack pointer to make room for i.a. our `stack_frame` variable. This prologue is the first thing in every function and comes before every other code.
|
||||
|
||||
So in order to correctly load the exception frame pointer, we need some way to circumvent the automatic prologue generation.
|
||||
|
||||
[function prologue]: https://en.wikipedia.org/wiki/Function_prologue
|
||||
|
||||
### Naked Functions
|
||||
Fortunately there is a way to disable the prologue: [naked functions]. A naked function has no prologue and immediately starts with the first instruction of its body. However, most Rust code requires the prologue. Therefore naked functions should only contain inline assembly.
|
||||
|
||||
[naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
|
||||
|
||||
A naked function looks like this (note the `#[naked]` attribute):
|
||||
|
||||
```rust
|
||||
#[naked]
|
||||
extern "C" fn naked_function_example() {
|
||||
unsafe {
|
||||
asm!("mov rax, 0x42" ::: "rax" : "intel");
|
||||
};
|
||||
}
|
||||
```
|
||||
Naked functions are highly unstable, so we need to add `#![feature(naked_functions)]` to our `src/lib.rs`.
|
||||
|
||||
If you want to try it, insert it in `src/lib.rs` and call it from `rust_main`. When we inspect the disassembly, we see that the function prologue is missing:
|
||||
|
||||
```
|
||||
> objdump -d build/kernel-x86_64.bin | grep -A5 "naked_function_example"
|
||||
[...]
|
||||
000000000010df90 <_ZN7blog_os22naked_function_example17ha9f733dfe42b595dE>:
|
||||
10df90: 48 c7 c0 2a 00 00 00 mov $0x42,%rax
|
||||
10df97: c3 retq
|
||||
10df98: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
|
||||
10df9f: 00
|
||||
```
|
||||
It contains just the specified inline assembly and a return instruction (you can ignore the junk values after the return statement). So let's try to use a naked function to retrieve the exception frame pointer.
|
||||
|
||||
### A Naked Exception Handler
|
||||
We can't use Rust code in naked functions, but we still want to use Rust in our exception handler. Therefore we split our handler function in two parts. A main exception handler in Rust and a small naked wrapper function, which just loads the exception frame pointer and then calls the main handler.
|
||||
|
||||
Our new two-stage exception handler looks like this:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
#[naked]
|
||||
extern "C" fn divide_by_zero_wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!(/* load exception frame pointer and call main handler */);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn divide_by_zero_handler(stack_frame: &ExceptionStackFrame)
|
||||
-> !
|
||||
{
|
||||
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}",
|
||||
unsafe { &*stack_frame });
|
||||
loop {}
|
||||
}
|
||||
|
||||
```
|
||||
The naked wrapper function retrieves the exception stack frame pointer and then calls the `divide_by_zero_handler` with the pointer as argument. We can't use Rust code in naked functions, so we need to do both things in inline assembly.
|
||||
|
||||
Retrieving the pointer to the exception stack frame is easy: We just need to load it from the `rsp` register. Our wrapper function has no prologue (it's naked), so we can be sure that nothing modifies the register before.
|
||||
|
||||
Calling the main handler is a bit more complicated, since we need to pass the argument correctly. Our main handler uses the C calling convention, which specifies that the the first argument is passed in the `rdi` register. So we need to load the pointer value into `rdi` and then use the `call` instruction to call `divide_by_zero_handler`.
|
||||
|
||||
Translated to assembly, it looks like this:
|
||||
|
||||
```nasm
|
||||
mov rdi, rsp
|
||||
call divide_by_zero_handler
|
||||
```
|
||||
It moves the exception stack frame pointer from `rsp` to `rdi`, where the first argument is expected, and then calls the main handler. Let's create the corresponding inline assembly to complete our wrapper function:
|
||||
|
||||
```rust
|
||||
#[naked]
|
||||
extern "C" fn divide_by_zero_wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("mov rdi, rsp; call $0"
|
||||
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
|
||||
: "rdi" : "intel");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Instead of `call divide_by_zero_handler`, we use a placeholder again. The reason is Rust's name mangling, which changes the name of the `divide_by_zero_handler` function. To circumvent this, we pass a function pointer as input parameter (after the second colon). The `"i"` tells the compiler that it is an immediate value, which can be directly inserted for the placeholder. We also specify a clobber after the third colon, which tells the compiler that we change the value of the `rdi` register.
|
||||
|
||||
### Intrinsics::Unreachable
|
||||
When we try to compile it, we get the following error:
|
||||
|
||||
```
|
||||
error: computation may converge in a function marked as diverging
|
||||
--> src/interrupts/mod.rs:23:1
|
||||
|>
|
||||
23 |> extern "C" fn divide_by_zero_wrapper() -> ! {
|
||||
|> ^
|
||||
```
|
||||
The reason is that we marked our `divide_by_zero_wrapper` function as diverging (the `!`). We call another diverging function in inline assembly, so it is clear that the function diverges. However, the Rust compiler doesn't understand inline assembly, so it doesn't know that. To fix this, we tell the compiler that all code after the `asm!` macro is unreachable:
|
||||
|
||||
```rust
|
||||
#[naked]
|
||||
extern "C" fn divide_by_zero_wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("mov rdi, rsp; call $0"
|
||||
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
|
||||
: "rdi" : "intel");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The [intrinsics::unreachable] function is unstable, so we need to add `#![feature(core_intrinsics)]` to our `src/lib.rs`. It is just an annotation for the compiler and produces no real code. (Not to be confused with the [unreachable!] macro, which is completely different!)
|
||||
|
||||
[intrinsics::unreachable]: https://doc.rust-lang.org/nightly/core/intrinsics/fn.unreachable.html
|
||||
[unreachable!]: https://doc.rust-lang.org/nightly/core/macro.unreachable!.html
|
||||
|
||||
### It works!
|
||||
The last step is to update the interrupt descriptor table (IDT) to use our new wrapper function:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
let mut idt = idt::Idt::new();
|
||||
idt.set_handler(0, divide_by_zero_wrapper); // changed
|
||||
idt
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Now we see a correct exception stack frame when we execute `make run`:
|
||||
|
||||

|
||||
|
||||
## Testing on real Hardware
|
||||
Virtual machines such as QEMU are very convenient to quickly test our kernel. However, they might behave a bit different than real hardware in some situations. So we should test our kernel on real hardware, too.
|
||||
|
||||
Let's do it by burning it to an USB stick:
|
||||
|
||||
```
|
||||
> sudo dd if=build/os-x86_64.iso of=/dev/sdX; and sync
|
||||
```
|
||||
|
||||
Replace `sdX` by the device name of your USB stick. But **be careful**! The command will erase everything on that device.
|
||||
|
||||
Now we should be able to boot from this USB stick. When we do it, we see that it works fine on real hardware, too. Great!
|
||||
|
||||
However, this section wouldn't exist if there weren't a problem. To trigger this problem, we add some example code to the start of our `divide_by_zero_handler`:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn divide_by_zero_handler(...) {
|
||||
let x = (1u64, 2u64, 3u64);
|
||||
let y = Some(x);
|
||||
for i in (0..100).map(|z| (z, z - 1)) {}
|
||||
|
||||
println!(...);
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
This is just some garbage code that doesn't do anything useful. When we try it in QEMU using `make run`, it still works fine. However, when we burn it to an USB stick again and boot from it on real hardware, we see that our computer reboots just before printing the exception message.
|
||||
|
||||
So our code, which worked well in QEMU, _causes a triple fault_ on real hardware. What's happening?
|
||||
|
||||
### Reproducing the Bug in QEMU
|
||||
Debugging on a real machine is difficult. Fortunately there is a way to reproduce this bug in QEMU: We use Linux's [Kernel-based Virtual Machine] \(KVM) by passing the `‑enable-kvm` flag:
|
||||
|
||||
[Kernel-based Virtual Machine]: https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine
|
||||
|
||||
```
|
||||
> qemu-system-x86_64 -cdrom build/os-x86_64.iso -enable-kvm
|
||||
```
|
||||
|
||||
Now QEMU triple faults as well. This should make debugging much easier.
|
||||
|
||||
### Debugging
|
||||
|
||||
QEMU's `-d int`, which prints every exception, doesn't seem to work in KVM mode. However `-d cpu_reset` still works. It prints the complete CPU state whenever the CPU resets. Let's try it:
|
||||
|
||||
```
|
||||
> qemu-system-x86_64 -cdrom build/os-x86_64.iso -enable-kvm -d cpu_reset
|
||||
CPU Reset (CPU 0)
|
||||
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
|
||||
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
|
||||
EIP=00000000 EFL=00000000 [-------] CPL=0 II=0 A20=0 SMM=0 HLT=0
|
||||
[...]
|
||||
CPU Reset (CPU 0)
|
||||
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000663
|
||||
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
|
||||
EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
|
||||
[...]
|
||||
CPU Reset (CPU 0)
|
||||
RAX=0000000000118cb8 RBX=0000000000000800 RCX=1d1d1d1d1d1d1d1d RDX=0..0000000
|
||||
RSI=0000000000112cd0 RDI=0000000000118d38 RBP=0000000000118d28 RSP=0..0118c68
|
||||
R8 =0000000000000000 R9 =0000000000000100 R10=0000000000118700 R11=0..0118a00
|
||||
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0..0000000
|
||||
RIP=000000000010cf08 RFL=00210002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
|
||||
[...]
|
||||
```
|
||||
The first two resets occur while the CPU is still in 32-bit mode (`EAX` instead of `RAX`), so we ignore them. The third reset is the interesting one, because it occurs in 64-bit mode. The register dump tells us that the instruction pointer (`rip`) was `0x10cf08` just before the reset. This might be the address of the instruction that caused the triple fault.
|
||||
|
||||
We can find the corresponding instruction by disassembling our kernel:
|
||||
|
||||
```
|
||||
objdump -d build/kernel-x86_64.bin | grep "10cf08:"
|
||||
10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp)
|
||||
```
|
||||
The [movaps] instruction is an [SSE] instruction that moves aligned 128bit values. It can fail for a number of reasons:
|
||||
|
||||
[movaps]: http://x86.renejeschke.de/html/file_module_x86_id_180.html
|
||||
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
|
||||
|
||||
1. For an illegal memory operand effective address in the CS, DS, ES, FS or GS segments.
|
||||
2. For an illegal address in the SS segment.
|
||||
3. If a memory operand is not aligned on a 16-byte boundary.
|
||||
4. For a page fault.
|
||||
5. If TS in CR0 is set.
|
||||
|
||||
The segment registers contain no meaningful values in long mode, so they can't contain illegal addresses. We did not change the TS bit in [CR0] and there is no reason for a page fault either. So it has to be option 3.
|
||||
|
||||
[CR0]: https://en.wikipedia.org/wiki/Control_register#CR0
|
||||
|
||||
### 16-byte Alignment
|
||||
Some SSE instructions such as `movaps` require that memory operands are 16-byte aligned. In our case, the instruction is `movaps %xmm0,-0x50(%rbp)`, which writes to address `rbp - 0x50`. Therefore `rbp` needs to be 16-byte aligned.
|
||||
|
||||
Let's look at the above `-d cpu_reset` dump again and check the value of `rbp`:
|
||||
|
||||
```
|
||||
CPU Reset (CPU 0)
|
||||
RAX=[...] RBX=[...] RCX=[...] RDX=[...]
|
||||
RSI=[...] RDI=[...] RBP=0000000000118d28 RSP=[...]
|
||||
...
|
||||
```
|
||||
`RBP` is `0x118d28`, which is _not_ 16-byte aligned. So this is the reason for the triple fault. (It seems like QEMU doesn't check the alignment for `movaps`, but real hardware of course does.)
|
||||
|
||||
But how did we end up with a misaligned `rbp` register?
|
||||
|
||||
### The Base Pointer
|
||||
In order to solve this mystery, we need to look at the disassembly of the preceding code:
|
||||
|
||||
```
|
||||
> objdump -d build/kernel-x86_64.bin | grep -B10 "10cf08:"
|
||||
000000000010cee0 <_ZN7blog_os10interrupts22divide_by_zero_handler17hE>:
|
||||
10cee0: 55 push %rbp
|
||||
10cee1: 48 89 e5 mov %rsp,%rbp
|
||||
10cee4: 48 81 ec c0 00 00 00 sub $0xc0,%rsp
|
||||
10ceeb: 48 8d 45 90 lea -0x70(%rbp),%rax
|
||||
10ceef: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx
|
||||
10cef6: 1d 1d 1d
|
||||
10cef9: 48 89 4d 90 mov %rcx,-0x70(%rbp)
|
||||
10cefd: 48 89 7d f8 mov %rdi,-0x8(%rbp)
|
||||
10cf01: 0f 10 05 a8 51 00 00 movups 0x51a8(%rip),%xmm0
|
||||
10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp)
|
||||
```
|
||||
At the last line we have the `movaps` instruction, which caused the triple fault. The exception occurs inside our `divide_by_zero_handler` function. We see that `rbp` is loaded with the value of `rsp` at the beginning (at `0x10cee1`). The `rbp` register holds the so-called _base pointer_, which points to the beginning of the stack frame. It is used in the rest of the function to address variables and other values on the stack.
|
||||
|
||||
The base pointer is initialized directly from the stack pointer (`rsp`) after pushing the old base pointer. There is no special alignment code, so the compiler blindly assumes that `(rsp - 8)`[^fn-rsp-8] is always 16-byte aligned. This seems to be wrong in our case. But why does the compiler assume this?
|
||||
|
||||
[^fn-rsp-8]: By pushing the old base pointer, `rsp` is updated to `rsp-8`.
|
||||
|
||||
### Calling Conventions
|
||||
The reason is that our exception handler is defined as `extern "C" function`, which specifies that it's using the C [calling convention]. On x86_64 Linux, the C calling convention is specified by the System V AMD64 ABI ([PDF][system v abi]). Section 3.2.2 defines the following:
|
||||
|
||||
[calling convention]: https://en.wikipedia.org/wiki/X86_calling_conventions
|
||||
[system v abi]: http://web.archive.org/web/20160801075139/http://www.x86-64.org/documentation/abi.pdf
|
||||
|
||||
> The end of the input argument area shall be aligned on a 16 byte boundary. In other words, the value (%rsp + 8) is always a multiple of 16 when control is transferred to the function entry point.
|
||||
|
||||
The “end of the input argument area” refers to the last stack-passed argument (in our case there aren't any). So the stack pointer must be 16 byte aligned whenever we `call` a C-compatible function. The `call` instruction then pushes the return value on the stack so that “the value (%rsp + 8) is a multiple of 16 when control is transferred to the function entry point”.
|
||||
|
||||
_Summary_: The calling convention requires a 16 byte aligned stack pointer before `call` instructions. The compiler relies on this requirement, but we broke it somehow. Thus the generated code triple faults due to a misaligned memory address in the `movaps` instruction.
|
||||
|
||||
### Fixing the Alignment
|
||||
In order to fix this bug, we need to make sure that the stack pointer is correctly aligned before calling `extern "C"` functions. Let's summarize the stack pointer modifications that occur before the exception handler is called:
|
||||
|
||||
1. The CPU aligns the stack pointer to a 16 byte boundary.
|
||||
2. The CPU pushes `ss`, `rsp`, `rflags`, `cs`, and `rip`. So it pushes five 8 byte registers, which makes `rsp` misaligned.
|
||||
3. The wrapper function calls `divide_by_zero_handler` with a misaligned stack pointer.
|
||||
|
||||
The problem is that we're pushing an uneven number of 8 byte registers. Thus we need to align the stack pointer again before the `call` instruction:
|
||||
|
||||
```rust
|
||||
#[naked]
|
||||
extern "C" fn divide_by_zero_wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("mov rdi, rsp
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0"
|
||||
:: "i"(divide_by_zero_handler as extern "C" fn(_) -> !)
|
||||
: "rdi" : "intel");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The additional `sub rsp, 8` instruction aligns the stack pointer to a 16 byte boundary. Now it should work on real hardware (and in QEMU KVM mode) again.
|
||||
|
||||
## A Handler Macro
|
||||
The next step is to add handlers for other exceptions. However, we would need wrapper functions for them too. To avoid this code duplication, we create a `handler` macro that creates the wrapper functions for us:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("mov rdi, rsp
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0"
|
||||
:: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame) -> !)
|
||||
: "rdi" : "intel");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
The macro takes a single Rust identifier (`ident`) as argument and expands to a `{}` block (hence the double braces). The block defines a new wrapper function that calls the function `$name` and passes a pointer to the exception stack frame. Note that we're fixing the argument type to `&ExceptionStackFrame`. If we used a `_` like before, the passed function could accept an arbitrary argument, which would lead to ugly bugs at runtime.
|
||||
|
||||
Now we can remove the `divide_by_zero_wrapper` and use our new `handler!` macro instead:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
let mut idt = idt::Idt::new();
|
||||
idt.set_handler(0, handler!(divide_by_zero_handler)); // new
|
||||
idt
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `handler!` macro needs to be defined above the static `IDT`, because macros are only available after their definition.
|
||||
|
||||
### Invalid Opcode Exception
|
||||
With the `handler!` macro we can create new handler functions easily. For example, we can add a handler for the invalid opcode exception as follows:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
let mut idt = idt::Idt::new();
|
||||
idt.set_handler(0, handler!(divide_by_zero_handler));
|
||||
idt.set_handler(6, handler!(invalid_opcode_handler)); // new
|
||||
idt
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn invalid_opcode_handler(stack_frame: &ExceptionStackFrame)
|
||||
-> !
|
||||
{
|
||||
let stack_frame = unsafe { &*stack_frame };
|
||||
println!("\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}",
|
||||
stack_frame.instruction_pointer, stack_frame);
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
Invalid opcode faults have the vector number 6, so we set the 6th IDT entry. This time we additionally print the address of the invalid instruction.
|
||||
|
||||
We can test our new handler with the special [ud2] instruction, which generates a invalid opcode:
|
||||
|
||||
[ud2]: http://x86.renejeschke.de/html/file_module_x86_id_318.html
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
...
|
||||
|
||||
// initialize our IDT
|
||||
interrupts::init();
|
||||
|
||||
// provoke a invalid opcode exception
|
||||
unsafe { asm!("ud2") };
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
## Exceptions with Error Codes
|
||||
When a divide-by-zero exception occurs, we immediately know the reason: Someone tried to divide by zero. In contrast, there are faults with many possible causes. For example, a page fault occurs in many occasions: When accessing a non-present page, when writing to a read-only page, when the page table is malformed, etc. In order to differentiate these causes, the CPU pushes an additional error code onto the stack for such exceptions, which gives additional information.
|
||||
|
||||
### A new Macro
|
||||
Since the CPU pushes an additional error code, the stack frame is different and our `handler!` macro is not applicable. Therefore we create a new `handler_with_error_code!` macro for them:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler_with_error_code {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("pop rsi // pop error code into rsi
|
||||
mov rdi, rsp
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0"
|
||||
:: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame, u64) -> !)
|
||||
: "rdi","rsi" : "intel");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
The difference to the `handler!` macro is the additional error code argument. The CPU pushes the error code last, so we pop it right at the beginning of the wrapper function. We pop it into `rsi` because the C calling convention expects the second argument in it.
|
||||
|
||||
### A Page Fault Handler
|
||||
Let's write a page fault handler which analyzes and prints the error code:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
|
||||
error_code: u64) -> !
|
||||
{
|
||||
println!(
|
||||
"\nEXCEPTION: PAGE FAULT with error code {:?}\n{:#?}",
|
||||
error_code, unsafe { &*stack_frame });
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
We need to register our new handler function in the static interrupt descriptor table (IDT):
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
let mut idt = idt::Idt::new();
|
||||
|
||||
idt.set_handler(0, handler!(divide_by_zero_handler));
|
||||
idt.set_handler(6, handler!(invalid_opcode_handler));
|
||||
// new
|
||||
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
|
||||
|
||||
idt
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Page faults have the vector number 14, so we set the 14th IDT entry.
|
||||
|
||||
#### Testing it
|
||||
Let's test our new page fault handler by provoking a page fault in our main function:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main(multiboot_information_address: usize) {
|
||||
...
|
||||
|
||||
// initialize our IDT
|
||||
interrupts::init();
|
||||
|
||||
// provoke a page fault
|
||||
unsafe { *(0xdeadbeaf as *mut u64) = 42 };
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
We get the following output:
|
||||
|
||||

|
||||
|
||||
### The Page Fault Error Code
|
||||
“Error code 2” is not really an useful error message. Let's improve this by creating a `PageFaultErrorCode` type:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
bitflags! {
|
||||
flags PageFaultErrorCode: u64 {
|
||||
const PROTECTION_VIOLATION = 1 << 0,
|
||||
const CAUSED_BY_WRITE = 1 << 1,
|
||||
const USER_MODE = 1 << 2,
|
||||
const MALFORMED_TABLE = 1 << 3,
|
||||
const INSTRUCTION_FETCH = 1 << 4,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- When the `PROTECTION_VIOLATION` flag is set, the page fault was caused e.g. by a write to a read-only page. If it's not set, it was caused by accessing a non-present page.
|
||||
- The `CAUSED_BY_WRITE` flag specifies if the fault was caused by a write (if set) or a read (if not set).
|
||||
- The `USER_MODE` flag is set when the fault occurred in non-priviledged mode.
|
||||
- The `MALFORMED_TABLE` flag is set when the page table entry has a 1 in a reserved field.
|
||||
- When the `INSTRUCTION_FETCH` flag is set, the page fault occurred while fetching the next instruction.
|
||||
|
||||
Now we can improve our page fault error message by using the new `PageFaultErrorCode`. We also print the accessed memory address:
|
||||
|
||||
```rust
|
||||
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
|
||||
error_code: u64) -> !
|
||||
{
|
||||
use x86_64::registers::control_regs;
|
||||
println!(
|
||||
"\nEXCEPTION: PAGE FAULT while accessing {:#x}\
|
||||
\nerror code: {:?}\n{:#?}",
|
||||
unsafe { control_regs::cr2() },
|
||||
PageFaultErrorCode::from_bits(error_code).unwrap(),
|
||||
unsafe { &*stack_frame });
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
The `from_bits` function tries to convert the `u64` into a `PageFaultErrorCode`. We use `unwrap` to panic if the error code has invalid bits set, since this indicates an error in our `PageFaultErrorCode` definition or a stack corruption. We also print the contents of the `cr2` register. It contains the accessed memory address, which was the cause of the page fault.
|
||||
|
||||
Now we get a useful error message when a page fault occurs, which allows us to debug it more easily:
|
||||
|
||||

|
||||
|
||||
As expected, the page fault was caused by write to `0xdeadbeaf`. The `PROTECTION_VIOLATION` flag is not set, so the accessed page was not present.
|
||||
|
||||
## What's next?
|
||||
Now we're able to catch and analyze various exceptions. The next step is to _resolve_ exceptions, if possible. An example is [demand paging]: The OS swaps out memory pages to disk so that a page fault occurs when the page is accessed the next time. In that case, the OS can resolve the exception by bringing the page back into memory. Afterwards, the OS resumes the interrupted program as if nothing had happened.
|
||||
|
||||
[demand paging]: https://en.wikipedia.org/wiki/Demand_paging
|
||||
|
||||
The next post will implement the first portion of demand paging: saving and restoring the complete state of an program. This will allow us to transparently interrupt and resume programs in the future.
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,901 @@
|
||||
+++
|
||||
title = "Returning from Exceptions"
|
||||
order = 3
|
||||
url = "returning-from-exceptions"
|
||||
date = "2016-09-21"
|
||||
updated = "2016-11-01"
|
||||
+++
|
||||
|
||||
In this post, we learn how to return from exceptions correctly. In the course of this, we will explore the `iretq` instruction, the C calling convention, multimedia registers, and the red zone.
|
||||
|
||||
<!-- more --><aside id="toc"></aside>
|
||||
|
||||
As always, the complete source code is on [Github]. Please file [issues] for any problems, questions, or improvement suggestions. There is also a [gitter chat] and a comment section at the end of this page.
|
||||
|
||||
[Github]: https://github.com/phil-opp/blog_os/tree/returning_from_exceptions
|
||||
[issues]: https://github.com/phil-opp/blog_os/issues
|
||||
[gitter chat]: https://gitter.im/phil-opp/blog_os
|
||||
|
||||
> **Note**: This post describes how to handle exceptions using naked functions (see [“Handling Exceptions with Naked Functions”] for an overview). Our new way of handling exceptions can be found in the [“Handling Exceptions”] post.
|
||||
|
||||
[“Handling Exceptions with Naked Functions”]: ./extra/naked-exceptions/_index.md
|
||||
[“Handling Exceptions”]: ./posts/09-handling-exceptions/index.md
|
||||
|
||||
## Introduction
|
||||
Most exceptions are fatal and can't be resolved. For example, we can't return from a divide-by-zero exception in a reasonable way. However, there are some exceptions that we can resolve:
|
||||
|
||||
Imagine a system that uses [memory mapped files]: We map a file into the virtual address space without loading it into memory. Whenever we access a part of the file for the first time, a page fault occurs. However, this page fault is not fatal. We can resolve it by loading the corresponding page from disk into memory and setting the `present` flag in the page table. Then we can return from the page fault handler and restart the failed instruction, which now successfully accesses the file data.
|
||||
|
||||
[memory mapped files]: https://en.wikipedia.org/wiki/Memory-mapped_file
|
||||
|
||||
Memory mapped files are completely out of scope for us right now (we have neither a file concept nor a hard disk driver). So we need an exception that we can resolve easily so that we can return from it in a reasonable way. Fortunately, there is an exception that needs no resolution at all: the breakpoint exception.
|
||||
|
||||
## The Breakpoint Exception
|
||||
The breakpoint exception is the perfect exception to test our upcoming return-from-exception logic. Its only purpose is to temporary pause a program when the breakpoint instruction `int3` is executed.
|
||||
|
||||
The breakpoint exception is commonly used in debuggers: When the user sets a breakpoint, the debugger overwrites the corresponding instruction with the `int3` instruction so that the CPU throws the breakpoint exception when it reaches that line. When the user wants to continue the program, the debugger replaces the `int3` instruction with the original instruction again and continues the program. For more details, see the [How debuggers work] series.
|
||||
|
||||
[How debuggers work]: http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
|
||||
|
||||
For our use case, we don't need to overwrite any instructions (it wouldn't even be possible since we [set the page table flags] to read-only). Instead, we just want to print a message when the breakpoint instruction is executed and then continue the program.
|
||||
|
||||
[set the page table flags]: ./posts/07-remap-the-kernel/index.md#using-the-correct-flags
|
||||
|
||||
### Catching Breakpoints
|
||||
Let's start by defining a handler function for the breakpoint exception:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn breakpoint_handler(stack_frame: &ExceptionStackFrame) -> !
|
||||
{
|
||||
let stack_frame = unsafe { &*stack_frame };
|
||||
println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
|
||||
stack_frame.instruction_pointer, stack_frame);
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
We print an error message and also output the instruction pointer and the rest of the stack frame. Note that this function does _not_ return yet, since our `handler!` macro still requires a diverging function.
|
||||
|
||||
We need to register our new handler function in the interrupt descriptor table (IDT):
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
lazy_static! {
|
||||
static ref IDT: idt::Idt = {
|
||||
let mut idt = idt::Idt::new();
|
||||
|
||||
idt.set_handler(0, handler!(divide_by_zero_handler));
|
||||
idt.set_handler(3, handler!(breakpoint_handler)); // new
|
||||
idt.set_handler(6, handler!(invalid_opcode_handler));
|
||||
idt.set_handler(14, handler_with_error_code!(page_fault_handler));
|
||||
|
||||
idt
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
We set the IDT entry with number 3 since it's the vector number of the breakpoint exception.
|
||||
|
||||
#### Testing it
|
||||
In order to test it, we insert an `int3` instruction in our `rust_main`:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
...
|
||||
#[macro_use] // needed for the `int!` macro
|
||||
extern crate x86_64;
|
||||
...
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main(...) {
|
||||
...
|
||||
interrupts::init();
|
||||
|
||||
// trigger a breakpoint exception
|
||||
unsafe { int!(3) };
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
|
||||
When we execute `make run`, we see the following:
|
||||
|
||||

|
||||
|
||||
It works! Now we “just” need to return from the breakpoint handler somehow so that we see the `It did not crash` message again.
|
||||
|
||||
## Returning from Exceptions
|
||||
So how do we return from exceptions? To make it easier, we look at a normal function return first:
|
||||
|
||||

|
||||
|
||||
When calling a function, the `call` instruction pushes the return address on the stack. When the called function is finished, it can return to the parent function through the `ret` instruction, which pops the return address from the stack and then jumps to it.
|
||||
|
||||
The exception stack frame, in contrast, looks a bit different:
|
||||
|
||||

|
||||
|
||||
Instead of pushing a return address, the CPU pushes the stack and instruction pointers (with their segment descriptors), the RFLAGS register, and an optional error code. It also aligns the stack pointer to a 16 byte boundary before pushing values.
|
||||
|
||||
So we can't use a normal `ret` instruction, since it expects a different stack frame layout. Instead, there is a special instruction for returning from exceptions: `iretq`.
|
||||
|
||||
### The `iretq` Instruction
|
||||
The `iretq` instruction is the one and only way to return from exceptions and is specifically designed for this purpose. The AMD64 manual ([PDF][amd-manual]) even demands that `iretq` “_must_ be used to terminate the exception or interrupt handler associated with the exception”.
|
||||
|
||||
[amd-manual]: https://support.amd.com/TechDocs/24594.pdf
|
||||
|
||||
IRETQ restores `rip`, `cs`, `rflags`, `rsp`, and `ss` from the values saved on the stack and thus continues the interrupted program. The instruction does not handle the optional error code, so it must be popped from the stack before.
|
||||
|
||||
We see that `iretq` treats the stored instruction pointer as return address. For most exceptions, the stored `rip` points to the instruction that caused the fault. So by executing `iretq`, we restart the failing instruction. This makes sense because we should have resolved the exception when returning from it, so the instruction should no longer fail (e.g. the accessed part of the memory mapped file is now present in memory).
|
||||
|
||||
The situation is a bit different for the breakpoint exception, since it needs no resolution. Restarting the `int3` instruction wouldn't make sense, since it would cause a new breakpoint exception and we would enter an endless loop. For this reason the hardware designers decided that the stored `rip` should point to the next instruction after the `int3` instruction.
|
||||
|
||||
Let's check this for our breakpoint handler. Remember, the handler printed the following message (see the image above):
|
||||
|
||||
```
|
||||
EXCEPTION: BREAKPOINT at 0x110970
|
||||
```
|
||||
|
||||
So let's disassemble the instruction at `0x110970` and its predecessor:
|
||||
|
||||
```shell
|
||||
> objdump -d build/kernel-x86_64.bin | grep -B1 "110970:"
|
||||
11096f: cc int3
|
||||
110970: 48 c7 01 2a 00 00 00 movq $0x2a,(%rcx)
|
||||
```
|
||||
|
||||
We see that `0x110970` indeed points to the next instruction after `int3`. So we can simply jump to the stored instruction pointer when we want to return from the breakpoint exception.
|
||||
|
||||
### Implementation
|
||||
Let's update our `handler!` macro to support non-diverging exception handlers:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("mov rdi, rsp
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0"
|
||||
:: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame)) // no longer diverging
|
||||
: "rdi" : "intel", "volatile");
|
||||
|
||||
// new
|
||||
asm!("add rsp, 8 // undo stack pointer alignment
|
||||
iretq"
|
||||
:::: "intel", "volatile");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
When an exception handler returns from the `call` instruction, we use the `iretq` instruction to continue the interrupted program. Note that we need to undo the stack pointer alignment before, so that `rsp` points to the end of the exception stack frame again.
|
||||
|
||||
We've changed the handler function type, so we need to adjust our existing exception handlers:
|
||||
|
||||
```diff
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn divide_by_zero_handler(
|
||||
- stack_frame: &ExceptionStackFrame) -> ! {...}
|
||||
+ stack_frame: &ExceptionStackFrame) {...}
|
||||
|
||||
extern "C" fn invalid_opcode_handler(
|
||||
- stack_frame: &ExceptionStackFrame) -> ! {...}
|
||||
+ stack_frame: &ExceptionStackFrame) {...}
|
||||
|
||||
extern "C" fn breakpoint_handler(
|
||||
- stack_frame: &ExceptionStackFrame) -> ! {
|
||||
+ stack_frame: &ExceptionStackFrame) {
|
||||
println!(...);
|
||||
- loop {}
|
||||
}
|
||||
```
|
||||
Note that we also removed the `loop {}` at the end of our `breakpoint_handler` so that it no longer diverges. The `divide_by_zero_handler` and the `invalid_opcode_handler` still diverge (albeit the new function type would allow a return).
|
||||
|
||||
### Testing
|
||||
Let's try our new `iretq` logic:
|
||||
|
||||

|
||||
|
||||
Instead of the expected _“It did not crash”_ message after the breakpoint exception, we get a page fault. The strange thing is that our kernel tried to access address `0x1`, which should never happen. So it seems like we messed up something important.
|
||||
|
||||
### Debugging
|
||||
Let's debug it using GDB. For that we execute `make debug` in one terminal (which starts QEMU with the `-s -S` flags) and then `make gdb` (which starts and connects GDB) in a second terminal. For more information about GDB debugging, check out our [Set Up GDB] guide.
|
||||
|
||||
[Set Up GDB]: ./extra/set-up-gdb/index.md
|
||||
|
||||
First we want to check if our `iretq` was successful. Therefore we set a breakpoint on the `println!("It did not crash line!")` statement in `src/lib.rs`. Let's assume that it's on line 61:
|
||||
|
||||
```
|
||||
(gdb) break blog_os/src/lib.rs:61
|
||||
Breakpoint 1 at 0x110a95: file /home/.../blog_os/src/lib.rs, line 61.
|
||||
```
|
||||
|
||||
This line is after the `int3` instruction, so we know that the `iretq` succeeded when the breakpoint is hit. To test this, we continue the execution:
|
||||
|
||||
```
|
||||
(gdb) continue
|
||||
Continuing.
|
||||
|
||||
Breakpoint 1, blog_os::rust_main (multiboot_information_address=1539136)
|
||||
at /home/.../blog_os/src/lib.rs:61
|
||||
61 println!("It did not crash!");
|
||||
|
||||
```
|
||||
It worked! So our kernel successfully returned from the `int3` instruction, which means that the `iretq` itself works.
|
||||
|
||||
However, when we `continue` the execution again, we get the page fault. So the exception occurs somewhere in the `println` logic. This means that it occurs in code generated by the compiler (and not e.g. in inline assembly). But the compiler should never access `0x1`, so how is this happening?
|
||||
|
||||
The answer is that we've used the wrong _calling convention_ for our exception handlers. Thus, we violate some compiler invariants so that the code that works fine without intermediate exceptions starts to violate memory safety when it's executed after a breakpoint exception.
|
||||
|
||||
## Calling Conventions
|
||||
Exceptions are quite similar to function calls: The CPU jumps to the first instruction of the (handler) function and executes the function. Afterwards, if the function is not diverging, the CPU jumps to the return address and continues the execution of the parent function.
|
||||
|
||||
However, there is a major difference between exceptions and function calls: A function call is invoked voluntary by a compiler inserted `call` instruction, while an exception might occur at _any_ instruction. In order to understand the consequences of this difference, we need to examine function calls in more detail.
|
||||
|
||||
[Calling conventions] specify the details of a function call. For example, they specify where function parameters are placed (e.g. in registers or on the stack) and how results are returned. On x86_64 Linux, the following rules apply for C functions (specified in the [System V ABI]):
|
||||
|
||||
[Calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
[System V ABI]: http://refspecs.linuxbase.org/elf/x86-64-abi-0.99.pdf
|
||||
|
||||
- the first six integer arguments are passed in registers `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`
|
||||
- additional arguments are passed on the stack
|
||||
- results are returned in `rax` and `rdx`
|
||||
|
||||
Note that Rust does not follow the C ABI (in fact, [there isn't even a Rust ABI yet][rust abi]). So these rules apply only to functions declared as `extern "C" fn`.
|
||||
|
||||
[rust abi]: https://github.com/rust-lang/rfcs/issues/600
|
||||
|
||||
### Preserved and Scratch Registers
|
||||
The calling convention divides the registers in two parts: _preserved_ and _scratch_ registers.
|
||||
|
||||
The values of the preserved register must remain unchanged across function calls. So a called function (the _“callee”_) is only allowed to overwrite these registers if it restores their original values before returning. Therefore these registers are called _“callee-saved”_. A common pattern is to save these registers to the stack at the function's beginning and restore them just before returning.
|
||||
|
||||
In contrast, a called function is allowed to overwrite scratch registers without restrictions. If the caller wants to preserve the value of a scratch register across a function call, it needs to backup and restore it (e.g. by pushing it to the stack before the function call). So the scratch registers are _caller-saved_.
|
||||
|
||||
On x86_64, the C calling convention specifies the following preserved and scratch registers:
|
||||
|
||||
preserved registers | scratch registers
|
||||
---|---
|
||||
`rbp`, `rbx`, `rsp`, `r12`, `r13`, `r14`, `r15` | `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`, `r9`, `r10`, `r11`
|
||||
_callee-saved_ | _caller-saved_
|
||||
|
||||
The compiler knows these rules, so it generates the code accordingly. For example, most functions begin with a `push rbp`, which backups `rbp` on the stack (because it's a callee-saved register).
|
||||
|
||||
### The Exception Calling Convention
|
||||
In contrast to function calls, exceptions can occur on _any_ instruction. In most cases we don't even know at compile time if the generated code will cause an exception. For example, the compiler can't know if an instruction causes a stack overflow or an other page fault.
|
||||
|
||||
Since we don't know when an exception occurs, we can't backup any registers before. This means that we can't use a calling convention that relies on caller-saved registers for our exception handlers. But we do so at the moment: Our exception handlers are declared as `extern "C" fn` and thus use the C calling convention.
|
||||
|
||||
So here is what happens:
|
||||
|
||||
- `rust_main` is executing; it writes some memory address into `rax`.
|
||||
- The `int3` instruction causes a breakpoint exception.
|
||||
- Our `breakpoint_handler` prints to the screen and assumes that it can overwrite `rax` freely (since it's a scratch register). Somehow the value `0` ends up in `rax`.
|
||||
- We return from the breakpoint exception using `iretq`.
|
||||
- `rust_main` continues and accesses the memory address in `rax`.
|
||||
- The CPU tries to access address `0x1`, which causes a page fault.
|
||||
|
||||
So our exception handler erroneously assumes that the scratch registers were saved by the caller. But the caller (`rust_main`) couldn't save any registers since it didn't know that an exception occurs. So nobody saves `rax` and the other scratch registers, which leads to the page fault.
|
||||
|
||||
The problem is that we use a calling convention with caller-saved registers for our exception handlers. Instead, we need a calling convention means that preserves _all registers_. In other words, all registers must be callee-saved:
|
||||
|
||||
```rust
|
||||
extern "all-registers-callee-saved" fn exception_handler() {...}
|
||||
```
|
||||
|
||||
Unfortunately, Rust does not support such a calling convention. It was [proposed once][interrupt calling conventions], but did not get accepted for various reasons. The primary reason was that such calling conventions can be simulated by writing a naked wrapper function.
|
||||
|
||||
(Remember: [Naked functions] are functions without prologue and can contain only inline assembly. They were discussed in the [previous post][naked fn post].)
|
||||
|
||||
[interrupt calling conventions]: https://github.com/rust-lang/rfcs/pull/1275
|
||||
[Naked functions]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
|
||||
[naked fn post]: ./extra/naked-exceptions/02-better-exception-messages/index.md#naked-functions
|
||||
|
||||
### A naked wrapper function
|
||||
|
||||
Such a naked wrapper function might look like this:
|
||||
|
||||
```rust
|
||||
#[naked]
|
||||
extern "C" fn calling_convention_wrapper() {
|
||||
unsafe {
|
||||
asm!("
|
||||
push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rsi
|
||||
push rdi
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
// TODO: call exception handler with C calling convention
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
" :::: "intel", "volatile");
|
||||
}
|
||||
}
|
||||
```
|
||||
This wrapper function saves all _scratch_ registers to the stack before calling the exception handler and restores them afterwards. Note that we `pop` the registers in reverse order.
|
||||
|
||||
We don't need to backup _preserved_ registers since they are callee-saved in the C calling convention. Thus, the compiler already takes care of preserving their values.
|
||||
|
||||
### Fixing our Handler Macro
|
||||
Let's update our handler macro to fix the calling convention problem. Therefore we need to backup and restore all scratch registers. For that we create two new macros:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! save_scratch_registers {
|
||||
() => {
|
||||
asm!("push rax
|
||||
push rcx
|
||||
push rdx
|
||||
push rsi
|
||||
push rdi
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
" :::: "intel", "volatile");
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! restore_scratch_registers {
|
||||
() => {
|
||||
asm!("pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rax
|
||||
" :::: "intel", "volatile");
|
||||
}
|
||||
}
|
||||
```
|
||||
We need to declare these macros _above_ our `handler` macro, since macros are only available after their declaration.
|
||||
|
||||
Now we can use these macros to fix our `handler!` macro:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
save_scratch_registers!();
|
||||
asm!("mov rdi, rsp
|
||||
add rdi, 9*8 // calculate exception stack frame pointer
|
||||
// sub rsp, 8 (stack is aligned already)
|
||||
call $0"
|
||||
:: "i"($name as
|
||||
extern "C" fn(&ExceptionStackFrame))
|
||||
: "rdi" : "intel", "volatile");
|
||||
|
||||
restore_scratch_registers!();
|
||||
asm!("
|
||||
// add rsp, 8 (undo stack alignment; not needed anymore)
|
||||
iretq"
|
||||
:::: "intel", "volatile");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
It's important that we save the registers first, before we modify any of them. After the `call` instruction (but before `iretq`) we restore the registers again. Because we're now changing `rsp` (by pushing the register values) before we load it into `rdi`, we would get a wrong exception stack frame pointer. Therefore we need to adjust it by adding the number of bytes we push. We push 9 registers that are 8 bytes each, so `9 * 8` bytes in total.
|
||||
|
||||
Note that we no longer need to manually align the stack pointer, because we're pushing an uneven number of registers in `save_scratch_registers`. Thus the stack pointer already has the required 16-byte alignment.
|
||||
|
||||
### Testing it again
|
||||
Let's test it again with our corrected `handler!` macro:
|
||||
|
||||

|
||||
|
||||
The page fault is gone and we see the _“It did not crash”_ message again!
|
||||
|
||||
So the page fault occurred because our exception handler didn't preserve the scratch register `rax`. Our new `handler!` macro fixes this problem by saving all scratch registers (including `rax`) before calling exception handlers. Thus, `rax` still contains the valid memory address when `rust-main` continues execution.
|
||||
|
||||
## Multimedia Registers
|
||||
When we discussed calling conventions above, we assummed that a x86_64 CPU only has the following 16 registers: `rax`, `rbx`, `rcx`, `rdx`, `rsi`, `rdi`, `rsp`, `rbp`, `r8`, `r9`, `r10`, `r11`.`r12`, `r13`, `r14`, and `r15`. These registers are called _general purpose registers_ since each of them can be used for arithmetic and load/store instructions.
|
||||
|
||||
However, modern CPUs also have a set of _special purpose registers_, which can be used to improve performance in several use cases. On x86_64, the most important set of special purpose registers are the _multimedia registers_. These registers are larger than the general purpose registers and can be used to speed up audio/video processing or matrix calculations. For example, we could use them to add two 4-dimensional vectors _in a single CPU instruction_:
|
||||
|
||||

|
||||
|
||||
Such multimedia instructions are called [Single Instruction Multiple Data (SIMD)] instructions, because they simultaneously perform an operation (e.g. addition) on multiple data words. Good compilers are able to transform normal loops into such SIMD code automatically. This process is called [auto-vectorization] and can lead to huge performance improvements.
|
||||
|
||||
[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
|
||||
[auto-vectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization
|
||||
|
||||
However, auto-vectorization causes a problem for us: Most of the multimedia registers are caller-saved. According to our discussion of calling conventions above, this means that our exception handlers erroneously assume that they are allowed to overwrite them without preserving their values.
|
||||
|
||||
We don't use any multimedia registers explicitly, but the Rust compiler might auto-vectorize our code (including the exception handlers). Thus we could silently clobber the multimedia registers, which leads to the same problems as above:
|
||||
|
||||

|
||||
|
||||
This example shows a program that is using the first three multimedia registers (`mm0` to `mm2`). At some point, an exception occurs and control is transfered to the exception handler. The exception handler uses `mm1` for its own data and thus overwrites the previous value. When the exception is resolved, the CPU continues the interrupted program again. However, the program is now corrupt since it relies on the original `mm1` value.
|
||||
|
||||
### Saving and Restoring Multimedia Registers
|
||||
In order to fix this problem, we need to backup all caller-saved multimedia registers before we call the exception handler. The problem is that the set of multimedia registers varies between CPUs. There are different standards:
|
||||
|
||||
- [MMX]: The MMX instruction set was introduced in 1997 and defines eight 64 bit registers called `mm0` through `mm7`. These registers are just aliases for the registers of the [x87 floating point unit].
|
||||
- [SSE]: The _Streaming SIMD Extensions_ instruction set was introduced in 1999. Instead of re-using the floating point registers, it adds a completely new register set. The sixteen new registers are called `xmm0` through `xmm15` and are 128 bits each.
|
||||
- [AVX]: The _Advanced Vector Extensions_ are extensions that further increase the size of the multimedia registers. The new registers are called `ymm0` through `ymm15` and are 256 bits each. They extend the `xmm` registers, so e.g. `xmm0` is the lower (or upper?) half of `ymm0`.
|
||||
|
||||
[MMX]: https://en.wikipedia.org/wiki/MMX_(instruction_set)
|
||||
[x87 floating point unit]: https://en.wikipedia.org/wiki/X87
|
||||
[SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
|
||||
[AVX]: https://en.wikipedia.org/wiki/Advanced_Vector_Extensions
|
||||
|
||||
The Rust compiler (and LLVM) assume that the `x86_64-unknown-linux-gnu` target supports only MMX and SSE, so we don't need to save the `ymm0` through `ymm15`. But we need to save `xmm0` through `xmm15` and also `mm0` through `mm7`. There is a special instruction to do this: [fxsave]. This instruction saves the floating point and multimedia state to a given address. It needs _512 bytes_ to store that state.
|
||||
|
||||
[fxsave]: http://x86.renejeschke.de/html/file_module_x86_id_128.html
|
||||
|
||||
In order to save/restore the multimedia registers, we _could_ add new macros:
|
||||
|
||||
```rust
|
||||
macro_rules! save_multimedia_registers {
|
||||
() => {
|
||||
asm!("sub rsp, 512
|
||||
fxsave [rsp]
|
||||
" :::: "intel", "volatile");
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! restore_multimedia_registers {
|
||||
() => {
|
||||
asm!("fxrstor [rsp]
|
||||
add rsp, 512
|
||||
" :::: "intel", "volatile");
|
||||
}
|
||||
}
|
||||
```
|
||||
First, we reserve the 512 bytes on the stack and then we use `fxsave` to backup the multimedia registers. In order to restore them later, we use the [fxrstor] instruction. Note that `fxsave` and `fxrstor` require a 16 byte aligned memory address.
|
||||
|
||||
[fxrstor]: http://x86.renejeschke.de/html/file_module_x86_id_127.html
|
||||
|
||||
However, _we won't do it that way_. The problem is the large amount of memory required. We will reuse the same code when we handle hardware interrupts in a future post. So for each mouse click, pressed key, or arrived network package we need to write 512 bytes to memory. This would be a huge performance problem.
|
||||
|
||||
Fortunately, there exists an alternative solution.
|
||||
|
||||
### Disabling Multimedia Extensions
|
||||
We just disable MMX, SSE, and all the other fancy multimedia extensions in our kernel[^fn-userspace-sse]. This way, our exception handlers won't clobber the multimedia registers because they won't use them at all.
|
||||
|
||||
[^fn-userspace-sse]: Userspace programs will still be able to use the multimedia registers.
|
||||
|
||||
This solution has its own disadvantages, of course. For example, it leads to slower kernel code because the compiler can't perform any auto-vectorization optimizations. But it's still the faster solution (since we save many memory accesses) and most kernels do it this way (including Linux).
|
||||
|
||||
So how do we disable MMX and SSE? Well, we just tell the compiler that our target system doesn't support it. Since the very beginning, we're compiling our kernel for the `x86_64-unknown-linux-gnu` target. This worked fine so far, but now we want a different target without support for multimedia extensions. We can do so by creating a _target configuration file_.
|
||||
|
||||
### Target Specifications
|
||||
In order to disable the multimedia extensions for our kernel, we need to compile for a custom target. We want a target that is equal to `x86_64-unknown-linux-gnu`, but without MMX and SSE support. Rust allows us to specify such a target using a JSON configuration file.
|
||||
|
||||
A minimal target specification that describes the `x86_64-unknown-linux-gnu` target looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"arch": "x86_64",
|
||||
"os": "none"
|
||||
}
|
||||
```
|
||||
|
||||
The `llvm-target` field specifies the target triple that is passed to LLVM. We want to derive a 64-bit Linux target, so we choose `x86_64-unknown-linux-gnu`. The `data-layout` field is also passed to LLVM and specifies how data should be laid out in memory. It consists of various specifications seperated by a `-` character. For example, the `e` means little endian and `S128` specifies that the stack should be 128 bits (= 16 byte) aligned. The format is described in detail in the [LLVM documentation][data layout] but there shouldn't be a reason to change this string.
|
||||
|
||||
The other fields are used for conditional compilation. This allows crate authors to use `cfg` variables to write special code for depending on the OS or the architecture. There isn't any up-to-date documentation about these fields but the [corresponding source code][target specification] is quite readable.
|
||||
|
||||
[data layout]: http://llvm.org/docs/LangRef.html#data-layout
|
||||
[target specification]: https://github.com/rust-lang/rust/blob/c772948b687488a087356cb91432425662e034b9/src/librustc_back/target/mod.rs#L194-L214
|
||||
|
||||
#### Disabling MMX and SSE
|
||||
In order to disable the multimedia extensions, we create a new target named `x86_64-blog_os`. To describe this target, we create a file named `x86_64-blog_os.json` in the project root with the following content:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128",
|
||||
"target-endian": "little",
|
||||
"target-pointer-width": "64",
|
||||
"arch": "x86_64",
|
||||
"os": "none",
|
||||
"features": "-mmx,-sse"
|
||||
}
|
||||
```
|
||||
|
||||
It's equal to `x86_64-unknown-linux-gnu` target but has one additional option: `"features": "-mmx,-sse"`. So we added two target _features_: `-mmx` and `-sse`. The minus prefix defines that our target does _not_ support this feature. So by specifying `-mmx` and `-sse`, we disable the default `mmx` and `sse` features.
|
||||
|
||||
In order to compile for the new target, we need to adjust our Makefile:
|
||||
|
||||
```diff
|
||||
# in `Makefile`
|
||||
|
||||
arch ?= x86_64
|
||||
-target ?= $(arch)-unknown-linux-gnu
|
||||
+target ?= $(arch)-blog_os
|
||||
...
|
||||
```
|
||||
The new target name (`x86_64-blog_os`) is the file name of the JSON configuration file without the `.json` extension.
|
||||
|
||||
### Cross compilation
|
||||
Let's try if our kernel still works with the new target:
|
||||
|
||||
```
|
||||
> make run
|
||||
Compiling raw-cpuid v2.0.1
|
||||
Compiling rlibc v0.1.5
|
||||
Compiling x86 v0.7.1
|
||||
Compiling spin v0.3.5
|
||||
error[E0463]: can't find crate for `core`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
Build failed, waiting for other jobs to finish...
|
||||
...
|
||||
Makefile:52: recipe for target 'cargo' failed
|
||||
make: *** [cargo] Error 101
|
||||
```
|
||||
It doesn't compile anymore. The error tells us that the Rust compiler no longer finds the core library.
|
||||
|
||||
The [core library] is implicitly linked to all `no_std` crates and contains things such as `Result`, `Option`, and iterators. We've used that library without problems since [the very beginning], so why is it no longer available?
|
||||
|
||||
[core library]: https://doc.rust-lang.org/nightly/core/index.html
|
||||
[the very beginning]: ./posts/03-set-up-rust/index.md
|
||||
|
||||
The problem is that the core library is distributed together with the Rust compiler as a _precompiled_ library. So it is only valid for the host triple, which is `x86_64-unknown-linux-gnu` in our case. If we want to compile code for other targets, we need to recompile `core` for these targets first.
|
||||
|
||||
#### Xargo
|
||||
That's where [xargo] comes in. It is a wrapper for cargo that eases cross compilation. We can install it by executing:
|
||||
|
||||
[xargo]: https://github.com/japaric/xargo
|
||||
|
||||
```
|
||||
cargo install xargo
|
||||
```
|
||||
|
||||
Xargo depends on the rust source code, which we can install with `rustup component add rust-src`.
|
||||
|
||||
Xargo is “a drop-in replacement for cargo”, so every cargo command also works with `xargo`. You can do e.g. `xargo --help`, `xargo clean`, or `xargo doc`. However, the `build` command gains additional functionality: `xargo build` will automatically cross compile the `core` library when compiling for custom targets.
|
||||
|
||||
That's exactly what we want, so we change one letter in our Makefile:
|
||||
|
||||
```diff
|
||||
# in `Makefile`
|
||||
...
|
||||
|
||||
cargo:
|
||||
- @cargo build --target $(target)
|
||||
+ @xargo build --target $(target)
|
||||
...
|
||||
```
|
||||
|
||||
Now the build goes through `xargo`, which should fix the compilation error. Let's try it out:
|
||||
|
||||
```
|
||||
> make run
|
||||
Compiling core v0.0.0 (file:///home/…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
|
||||
LLVM ERROR: SSE register return with SSE disabled
|
||||
error: Could not compile `core`.
|
||||
```
|
||||
Well, we get a different error now, so it seems like we're making progress :). It seems like there is a “SSE register return” although SSE is disabled. But what's an “SSE register return”?
|
||||
|
||||
### SSE Register Return
|
||||
Remember when we discussed calling conventions above? The calling convention defines which registers are used for return values. Well, the [System V ABI] defines that `xmm0` should be used for returning floating point values. So somewhere in the `core` library a function returns a float and LLVM doesn't know what to do. The ABI says “use `xmm0`” but the target specification says “don't use `xmm` registers”.
|
||||
|
||||
In order to fix this problem, we need to change our float ABI. The idea is to avoid normal hardware-supported floats and use a pure software implementation instead. We can do so by enabling the `soft-float` feature for our target. For that, we edit `x86_64-blog_os.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
...
|
||||
"features": "-mmx,-sse,+soft-float"
|
||||
}
|
||||
```
|
||||
|
||||
The plus prefix tells LLVM to enable the `soft-float` feature.
|
||||
|
||||
Let's try `make run` again:
|
||||
|
||||
```
|
||||
> make run
|
||||
Compiling core v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
|
||||
Finished release [optimized] target(s) in 21.95 secs
|
||||
Compiling spin v0.4.5
|
||||
Compiling once v0.3.2
|
||||
Compiling x86 v0.8.0
|
||||
Compiling bitflags v0.7.0
|
||||
Compiling raw-cpuid v2.0.1
|
||||
Compiling rlibc v0.1.5
|
||||
Compiling linked_list_allocator v0.2.3
|
||||
Compiling volatile v0.1.0
|
||||
Compiling bitflags v0.4.0
|
||||
Compiling bit_field v0.5.0
|
||||
Compiling spin v0.3.5
|
||||
Compiling multiboot2 v0.1.0
|
||||
Compiling lazy_static v0.2.2
|
||||
Compiling hole_list_allocator v0.1.0 (file:///…/libs/hole_list_allocator)
|
||||
Compiling blog_os v0.1.0 (file:///…)
|
||||
error[E0463]: can't find crate for `alloc`
|
||||
--> src/lib.rs:33:1
|
||||
|
|
||||
33 | extern crate alloc;
|
||||
| ^^^^^^^^^^^^^^^^^^^ can't find crate
|
||||
|
||||
error: aborting due to previous error
|
||||
```
|
||||
We see that `xargo` now compiles the `core` crate in release mode. Then it starts the normal cargo build. Cargo then recompiles all dependencies, since it needs to generate different code for the new target.
|
||||
|
||||
However, the build still fails. The reason is that xargo only installs `core` by default, but we also need the `alloc` and `collections` crates. We can enable them by creating a file named `Xargo.toml` with the following contents:
|
||||
|
||||
```toml
|
||||
# Xargo.toml
|
||||
|
||||
[target.x86_64-blog_os.dependencies]
|
||||
collections = {}
|
||||
```
|
||||
|
||||
Now xargo compiles `alloc` and `collections`, too:
|
||||
|
||||
```
|
||||
> make run
|
||||
Compiling core v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
|
||||
Compiling std_unicode v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd_unicode)
|
||||
Compiling alloc v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc)
|
||||
Compiling collections v0.0.0 (file:///…/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcollections)
|
||||
Finished release [optimized] target(s) in 28.84 secs
|
||||
Compiling blog_os v0.1.0 (file:///…/Documents/blog_os/master)
|
||||
warning: unused variable: `allocator` […]
|
||||
warning: unused variable: `frame` […]
|
||||
|
||||
Finished debug [unoptimized + debuginfo] target(s) in 1.75 secs
|
||||
```
|
||||
|
||||
It worked! Now we have a kernel that never touches the multimedia registers! We can verify this by executing:
|
||||
|
||||
```
|
||||
> objdump -d build/kernel-x86_64.bin | grep "mm[0-9]"
|
||||
```
|
||||
If the command produces no output, our kernel uses neither MMX (`mm0` – `mm7`) nor SSE (`xmm0` – `xmm15`) registers.
|
||||
|
||||
So now our return-from-exception logic works without problems in _most_ cases. However, there is still a pitfall hidden in the C calling convention, which might cause hideous bugs in some rare cases.
|
||||
|
||||
## The Red Zone
|
||||
The [red zone] is an optimization of the [System V ABI] that allows functions to temporary use the 128 bytes below its stack frame without adjusting the stack pointer:
|
||||
|
||||
[red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone
|
||||
|
||||

|
||||
|
||||
The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the local variables.
|
||||
|
||||
The red zone is defined as the 128 bytes below the adjusted stack pointer. The function can use this area for temporary data that's not needed across function calls. Thus, the two instructions for adjusting the stack pointer can be avoided in some cases (e.g. in small leaf functions).
|
||||
|
||||
However, this optimization leads to huge problems with exceptions. Let's assume that an exception occurs while a function uses the red zone:
|
||||
|
||||

|
||||
|
||||
The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. It might fail or cause another exception, but it could also lead to strange bugs that [take weeks to debug].
|
||||
|
||||
[take weeks to debug]: http://forum.osdev.org/viewtopic.php?t=21720
|
||||
|
||||
### Adjusting our Exception Handler?
|
||||
The problem is that the [System V ABI] demands that the red zone _“shall not be modified by signal or interrupt handlers.”_ Our current exception handlers do not respect this. We could try to fix it by subtracting 128 from the stack pointer before pushing anything:
|
||||
|
||||
```nasm
|
||||
sub rsp, 128
|
||||
save_scratch_registers()
|
||||
...
|
||||
call ...
|
||||
...
|
||||
restore_scratch_registers()
|
||||
add rsp, 128
|
||||
|
||||
iretq
|
||||
```
|
||||
_This will not work._ The problem is that the CPU pushes the exception stack frame before even calling our handler function. So the CPU itself will clobber the red zone and there is nothing we can do about that. So our only chance is to disable the red zone.
|
||||
|
||||
### Disabling the Red Zone
|
||||
The red zone is a property of our target, so in order to disable it we edit our `x86_64-blog_os.json` a last time:
|
||||
|
||||
```json
|
||||
{
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
...
|
||||
"features": "-mmx,-sse,+soft-float",
|
||||
"disable-redzone": true
|
||||
}
|
||||
```
|
||||
|
||||
We add one additional option at the end: `"disable-redzone": true`. As you might guess, this option disables the red zone optimization.
|
||||
|
||||
Now we have a red zone free kernel!
|
||||
|
||||
## Exceptions with Error Codes
|
||||
We're now able to correctly return from exceptions without error codes. However, we still can't return from exceptions that push an error code (e.g. page faults). Let's fix that by updating our `handler_with_error_code` macro:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler_with_error_code {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
asm!("pop rsi // pop error code into rsi
|
||||
mov rdi, rsp
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0"
|
||||
:: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame, u64))
|
||||
: "rdi","rsi" : "intel");
|
||||
asm!("iretq" :::: "intel", "volatile");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
First, we change the type of the handler function: no more `-> !`, so it no longer needs to diverge. We also add an `iretq` instruction at the end.
|
||||
|
||||
Now we can make our `page_fault_handler` non-diverging:
|
||||
|
||||
```diff
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame,
|
||||
- error_code: u64) -> ! { ... }
|
||||
+ error_code: u64) { ... }
|
||||
```
|
||||
|
||||
However, now we have the same problem as above: The handler function will overwrite the scratch registers and cause bugs when returning. Let's fix this by invoking `save_scratch_registers` at the beginning:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler_with_error_code {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
save_scratch_registers!();
|
||||
asm!("pop rsi // pop error code into rsi
|
||||
mov rdi, rsp
|
||||
add rdi, 10*8 // calculate exception stack frame pointer
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0
|
||||
add rsp, 8 // undo stack pointer alignment
|
||||
" :: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame, u64))
|
||||
: "rdi","rsi" : "intel");
|
||||
restore_scratch_registers!();
|
||||
asm!("iretq" :::: "intel", "volatile");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Now we backup the scratch registers to the stack right at the beginning and restore them just before the `iretq`. Like in the `handler` macro, we now need to add `10*8` to `rdi` in order to get the correct exception stack frame pointer (`save_scratch_registers` pushes nine 8 byte registers, plus the error code). We also need to undo the stack pointer alignment after the `call` [^fn-stack-alignment].
|
||||
|
||||
[^fn-stack-alignment]: The stack alignment is actually wrong here, since we additionally pushed an uneven number of registers. However, the `pop rsi` is wrong too, since the error code is no longer at the top of the stack. When we fix that problem, the stack alignment becomes correct again. So I left it in to keep things simple.
|
||||
|
||||
Now we have one last bug: We `pop` the error code into `rsi`, but the error code is no longer at the top of the stack (since `save_scratch_registers` pushed 9 registers on top of it). So we need to do it differently:
|
||||
|
||||
```rust
|
||||
// in src/interrupts/mod.rs
|
||||
|
||||
macro_rules! handler_with_error_code {
|
||||
($name: ident) => {{
|
||||
#[naked]
|
||||
extern "C" fn wrapper() -> ! {
|
||||
unsafe {
|
||||
save_scratch_registers!();
|
||||
asm!("mov rsi, [rsp + 9*8] // load error code into rsi
|
||||
mov rdi, rsp
|
||||
add rdi, 10*8 // calculate exception stack frame pointer
|
||||
sub rsp, 8 // align the stack pointer
|
||||
call $0
|
||||
add rsp, 8 // undo stack pointer alignment
|
||||
" :: "i"($name as extern "C" fn(
|
||||
&ExceptionStackFrame, u64))
|
||||
: "rdi","rsi" : "intel");
|
||||
restore_scratch_registers!();
|
||||
asm!("add rsp, 8 // pop error code
|
||||
iretq" :::: "intel", "volatile");
|
||||
::core::intrinsics::unreachable();
|
||||
}
|
||||
}
|
||||
wrapper
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
Instead of using `pop`, we're calculating the error code address manually (`save_scratch_registers` pushes nine 8 byte registers) and load it into `rsi` using a `mov`. So now the error code stays on the stack. But `iretq` doesn't handle the error code, so we need to pop it before invoking `iretq`.
|
||||
|
||||
Phew! That was a lot of fiddling with assembly. Let's test if it still works.
|
||||
|
||||
### Testing
|
||||
First, we test if the exception stack frame pointer and the error code are still correct:
|
||||
|
||||
```rust
|
||||
// in rust_main in src/lib.rs
|
||||
|
||||
...
|
||||
unsafe { int!(3) };
|
||||
|
||||
// provoke a page fault
|
||||
unsafe { *(0xdeadbeaf as *mut u64) = 42; }
|
||||
|
||||
println!("It did not crash!");
|
||||
loop {}
|
||||
```
|
||||
|
||||
This should cause the following error message:
|
||||
|
||||
```
|
||||
EXCEPTION: PAGE FAULT while accessing 0xdeadbeaf
|
||||
error code: CAUSED_BY_WRITE
|
||||
ExceptionStackFrame {
|
||||
instruction_pointer: 1114753,
|
||||
code_segment: 8,
|
||||
cpu_flags: 2097158,
|
||||
stack_pointer: 1171104,
|
||||
stack_segment: 16
|
||||
}
|
||||
```
|
||||
The error code should still be `CAUSED_BY_WRITE` and the exception stack frame values should also be correct (e.g. `code_segment` should be 8 and `stack_segment` should be 16).
|
||||
|
||||
#### Returning from Page Faults
|
||||
Let's see what happens if we comment out the trailing `loop` in our page fault handler:
|
||||
|
||||

|
||||
|
||||
We see that the same error message is printed over and over again. Here is what happens:
|
||||
|
||||
- The CPU executes `rust_main` and tries to access `0xdeadbeaf`. This causes a page fault.
|
||||
- The page fault handler prints an error message and returns without fixing the cause of the exception (`0xdeadbeaf` is still unaccessible).
|
||||
- The CPU restarts the instruction that caused the page fault and thus tries to access `0xdeadbeaf` again. Of course, this causes a page fault again.
|
||||
- The page fault handler prints the error message and returns.
|
||||
|
||||
… and so on. Thus, our code indefinitely jumps between the page fault handler and the instruction that accesses `0xdeadbeaf`.
|
||||
|
||||
This is a good thing! It means that our `iretq` logic is working correctly, since it returns to the correct instruction every time. So our `handler_with_error_code` macro seems to be correct.
|
||||
|
||||
## What's next?
|
||||
We are now able to catch exceptions and to return from them. However, there are still exceptions that completely crash our kernel by causing a [triple fault]. In the next post, we will fix this issue by handling a special type of exception: the [double fault]. Thus, we will be able to avoid random reboots in our kernel.
|
||||
|
||||
[triple fault]: https://en.wikipedia.org/wiki/Triple_fault
|
||||
[double fault]: https://en.wikipedia.org/wiki/Double_fault
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="17cm" height="11cm" viewBox="-60 -21 340 212" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="90" y="65.9389">
|
||||
<tspan x="90" y="65.9389"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="204" y1="70" x2="176.236" y2="70"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="185.118,65 175.118,70 185.118,75 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="208" y="72.75">
|
||||
<tspan x="208" y="72.75">Old Stack Pointer</tspan>
|
||||
</text>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="75" y="230">
|
||||
<tspan x="75" y="230"></tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="70" x2="-4" y2="70"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="190" x2="-4" y2="190"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="-12" y1="72.2361" x2="-12" y2="187.764"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-7,81.118 -12,71.118 -17,81.118 "/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-17,178.882 -12,188.882 -7,178.882 "/>
|
||||
</g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="0" y1="-20" x2="0" y2="0"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="170" y1="-20" x2="170" y2="0"/>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="0" width="170" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="0" width="170" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="13.125">
|
||||
<tspan x="85" y="13.125">Return Address</tspan>
|
||||
</text>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="84" y="4">
|
||||
<tspan x="84" y="4"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="0" y="20" width="170" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="20" width="170" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="33.125">
|
||||
<tspan x="85" y="33.125">Local Variable 1</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #cccccc" x="0" y="40" width="170" height="32"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x="0" y="40" width="170" height="32"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="85" y="59.125">
|
||||
<tspan x="85" y="59.125">Local Variables 2..n</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="40" x2="170" y2="40"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="72" x2="120" y2="72"/>
|
||||
<g>
|
||||
<rect style="fill: #ff3333" x="0" y="70" width="170" height="120"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="70" width="170" height="120"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="40" y="179.125">
|
||||
<tspan x="40" y="179.125">Red Zone</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:end;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="132.75">
|
||||
<tspan x="-20" y="132.75">128 bytes</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ffc200" x="30" y="70" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="70" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="88.125">
|
||||
<tspan x="100" y="88.125">Exception Stack Frame</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ffe000" x="30" y="100" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="100" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="118.125">
|
||||
<tspan x="100" y="118.125">Register Backup</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #c6db97" x="30" y="130" width="140" height="30"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="30" y="130" width="140" height="30"/>
|
||||
</g>
|
||||
<text font-size="8.46654" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="100" y="147.925">
|
||||
<tspan x="100" y="147.925">Handler Function Stack Frame</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #ff3333" x1="30" y1="70" x2="30" y2="160"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #ff3333" x1="30" y1="160" x2="170" y2="160"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="204" y1="160" x2="176.236" y2="160"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="185.118,155 175.118,160 185.118,165 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="208" y="162.635">
|
||||
<tspan x="208" y="162.635">New Stack Pointer</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="14cm" height="9cm" viewBox="-60 -21 270 172" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="90" y="65.9389">
|
||||
<tspan x="90" y="65.9389"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="154" y1="70" x2="126.236" y2="70"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="135.118,65 125.118,70 135.118,75 "/>
|
||||
</g>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="158" y="72.75">
|
||||
<tspan x="158" y="72.75">Stack Pointer</tspan>
|
||||
</text>
|
||||
<text font-size="7.90222" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="75" y="230">
|
||||
<tspan x="75" y="230"></tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="70" x2="-4" y2="70"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="-20" y1="150" x2="-4" y2="150"/>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="-12" y1="72.2361" x2="-12" y2="147.764"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-7,81.118 -12,71.118 -17,81.118 "/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="-17,138.882 -12,148.882 -7,138.882 "/>
|
||||
</g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="0" y1="-20" x2="0" y2="0"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x1="120" y1="-20" x2="120" y2="0"/>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="0" width="120" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="0" width="120" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="13.125">
|
||||
<tspan x="60" y="13.125">Return Address</tspan>
|
||||
</text>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="84" y="4">
|
||||
<tspan x="84" y="4"></tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="0" y="20" width="120" height="20"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="20" width="120" height="20"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="33.125">
|
||||
<tspan x="60" y="33.125">Local Variable 1</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #cccccc" x="0" y="40" width="120" height="32"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke-dasharray: 4; stroke: #000000" x="0" y="40" width="120" height="32"/>
|
||||
</g>
|
||||
<text font-size="9.03097" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="59.125">
|
||||
<tspan x="60" y="59.125">Local Variables 2..n</tspan>
|
||||
</text>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="40" x2="120" y2="40"/>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="0" y1="72" x2="120" y2="72"/>
|
||||
<g>
|
||||
<rect style="fill: #ff3333" x="0" y="70" width="120" height="80"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="70" width="120" height="80"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="60" y="113.125">
|
||||
<tspan x="60" y="113.125">Red Zone</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:end;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="112.75">
|
||||
<tspan x="-20" y="112.75">128 bytes</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,114 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
|
||||
<svg width="13cm" height="4cm" viewBox="-1 -23 252 63" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="0" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="0" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="25" y="8.125">
|
||||
<tspan x="25" y="8.125">mm0</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="10" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="10" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="0" y="20" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="20" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="0" y="30" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="0" y="30" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="25" y="17.9931">
|
||||
<tspan x="25" y="17.9931">mm1</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="25" y="28.125">
|
||||
<tspan x="25" y="28.125">mm2</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="25" y="38.125">
|
||||
<tspan x="25" y="38.125">mm3</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="100" y="-4.44089e-15" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="100" y="-4.44089e-15" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="125" y="8.125">
|
||||
<tspan x="125" y="8.125">mm0</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ff0000" x="100" y="10" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="100" y="10" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="100" y="20" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="100" y="20" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="100" y="30" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="100" y="30" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="125" y="18.125">
|
||||
<tspan x="125" y="18.125">mm1</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="125" y="28.125">
|
||||
<tspan x="125" y="28.125">mm2</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="125" y="38.125">
|
||||
<tspan x="125" y="38.125">mm3</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="200" y="0" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="200" y="0" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="225" y="8.125">
|
||||
<tspan x="225" y="8.125">mm0</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<rect style="fill: #ff0000" x="200" y="10" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="200" y="10" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #00ff00" x="200" y="20" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="200" y="20" width="50" height="10"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect style="fill: #dddddd" x="200" y="30" width="50" height="10"/>
|
||||
<rect style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x="200" y="30" width="50" height="10"/>
|
||||
</g>
|
||||
<text font-size="9.03111" style="fill: #ffffff;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="225" y="18.125">
|
||||
<tspan x="225" y="18.125">mm1</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="225" y="28.125">
|
||||
<tspan x="225" y="28.125">mm2</tspan>
|
||||
</text>
|
||||
<text font-size="9.03111" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="225" y="38.125">
|
||||
<tspan x="225" y="38.125">mm3</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="25" y="-10.0787">
|
||||
<tspan x="25" y="-10.0787">Program</tspan>
|
||||
</text>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="125" y="-15.0175">
|
||||
<tspan x="125" y="-15.0175">Exception</tspan>
|
||||
<tspan x="125" y="-5.13977">Handler</tspan>
|
||||
</text>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="50" y1="-12.8287" x2="97.7639" y2="-12.8287"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="94.882,-10.8287 98.882,-12.8287 94.882,-14.8287 "/>
|
||||
</g>
|
||||
<g>
|
||||
<line style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" x1="150" y1="-12.8287" x2="197.764" y2="-12.8287"/>
|
||||
<polyline style="fill: none; fill-opacity:0; stroke-width: 1; stroke: #000000" points="194.882,-10.8287 198.882,-12.8287 194.882,-14.8287 "/>
|
||||
</g>
|
||||
<text font-size="7.9021" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="225" y="-10.0787">
|
||||
<tspan x="225" y="-10.0787">Program</tspan>
|
||||
</text>
|
||||
<text font-size="6.77323" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="75" y="-14.737">
|
||||
<tspan x="75" y="-14.737">Exception</tspan>
|
||||
<tspan x="75" y="-6.27032">occurs</tspan>
|
||||
</text>
|
||||
<text font-size="6.77323" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="175" y="-14.737">
|
||||
<tspan x="175" y="-14.737">Exception</tspan>
|
||||
<tspan x="175" y="-6.27032">resolved</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
6
blog/content/extra/naked-exceptions/_index.md
Normal file
@@ -0,0 +1,6 @@
|
||||
+++
|
||||
title = "Handling Exceptions using naked Functions"
|
||||
sort_by = "order"
|
||||
template = "handling-exceptions-with-naked-fns.html"
|
||||
insert_anchor = "left"
|
||||
+++
|
||||