Merge pull request #458 from phil-opp/idt_rename

Update to x86_64 version 0.2.8
This commit is contained in:
Philipp Oppermann
2018-07-14 14:47:13 +02:00
committed by GitHub
9 changed files with 169 additions and 70 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,5 @@
# Generated by Cargo
/target/
Cargo.lock
bootimage.bin

102
Cargo.lock generated Normal file
View File

@@ -0,0 +1,102 @@
[[package]]
name = "array-init"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bit_field"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bitflags"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blog_os"
version = "0.2.0"
dependencies = [
"array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "os_bootinfo"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "spin"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uart_16550"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "usize_conversions"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ux"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "volatile"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "x86_64"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3cc8456d0ae81a8c76f59e384683a601548c38949a4bfcb65dd31ded5c75ff3"
"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
"checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a"
"checksum spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14db77c5b914df6d6173dda9a3b3f5937bd802934fa5edaf934df06a3491e56f"
"checksum uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "269f953d8de3226f7c065c589c7b4a3e83d10a419c7c3b5e2e0f197e6acc966e"
"checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5"
"checksum ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53d8df5dd8d07fedccd202de1887d94481fadaea3db70479f459e8163a1fab41"
"checksum volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54d4343a2df2d65144a874f95950754ee7b7e8594f6027aae8c7d0f4858a3fe8"
"checksum x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "51b347fd81faca2e19366605a2bb52aa2f0e1572835678e3355b66aab18650e6"

View File

@@ -7,7 +7,7 @@ version = "0.2.0"
spin = "0.4.6"
volatile = "0.2.3"
uart_16550 = "0.1.0"
x86_64 = "0.2.6"
x86_64 = "0.2.8"
[dependencies.lazy_static]
version = "1.0"

View File

@@ -200,7 +200,7 @@ To write to the I/O port, we use the [`x86_64`] crate:
# in Cargo.toml
[dependencies]
x86_64 = "0.2.6"
x86_64 = "0.2.8"
```
```rust

View File

@@ -77,43 +77,43 @@ When an exception occurs, the CPU roughly does the following:
Don't worry about steps 4 and 5 for now, we will learn about the global descriptor table and hardware interrupts in future posts.
## An IDT Type
Instead of creating our own IDT type, we will use the [`Idt` struct] of the `x86_64` crate, which looks like this:
Instead of creating our own IDT type, we will use the [`InterruptDescriptorTable` struct] of the `x86_64` crate, which looks like this:
[`Idt` struct]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/struct.Idt.html
[`InterruptDescriptorTable` struct]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html
``` rust
#[repr(C)]
pub struct Idt {
pub divide_by_zero: IdtEntry<HandlerFunc>,
pub debug: IdtEntry<HandlerFunc>,
pub non_maskable_interrupt: IdtEntry<HandlerFunc>,
pub breakpoint: IdtEntry<HandlerFunc>,
pub overflow: IdtEntry<HandlerFunc>,
pub bound_range_exceeded: IdtEntry<HandlerFunc>,
pub invalid_opcode: IdtEntry<HandlerFunc>,
pub device_not_available: IdtEntry<HandlerFunc>,
pub double_fault: IdtEntry<HandlerFuncWithErrCode>,
pub invalid_tss: IdtEntry<HandlerFuncWithErrCode>,
pub segment_not_present: IdtEntry<HandlerFuncWithErrCode>,
pub stack_segment_fault: IdtEntry<HandlerFuncWithErrCode>,
pub general_protection_fault: IdtEntry<HandlerFuncWithErrCode>,
pub page_fault: IdtEntry<PageFaultHandlerFunc>,
pub x87_floating_point: IdtEntry<HandlerFunc>,
pub alignment_check: IdtEntry<HandlerFuncWithErrCode>,
pub machine_check: IdtEntry<HandlerFunc>,
pub simd_floating_point: IdtEntry<HandlerFunc>,
pub virtualization: IdtEntry<HandlerFunc>,
pub security_exception: IdtEntry<HandlerFuncWithErrCode>,
pub struct InterruptDescriptorTable {
pub divide_by_zero: Entry<HandlerFunc>,
pub debug: Entry<HandlerFunc>,
pub non_maskable_interrupt: Entry<HandlerFunc>,
pub breakpoint: Entry<HandlerFunc>,
pub overflow: Entry<HandlerFunc>,
pub bound_range_exceeded: Entry<HandlerFunc>,
pub invalid_opcode: Entry<HandlerFunc>,
pub device_not_available: Entry<HandlerFunc>,
pub double_fault: Entry<HandlerFuncWithErrCode>,
pub invalid_tss: Entry<HandlerFuncWithErrCode>,
pub segment_not_present: Entry<HandlerFuncWithErrCode>,
pub stack_segment_fault: Entry<HandlerFuncWithErrCode>,
pub general_protection_fault: Entry<HandlerFuncWithErrCode>,
pub page_fault: Entry<PageFaultHandlerFunc>,
pub x87_floating_point: Entry<HandlerFunc>,
pub alignment_check: Entry<HandlerFuncWithErrCode>,
pub machine_check: Entry<HandlerFunc>,
pub simd_floating_point: Entry<HandlerFunc>,
pub virtualization: Entry<HandlerFunc>,
pub security_exception: Entry<HandlerFuncWithErrCode>,
// some fields omitted
}
```
The fields have the type [`IdtEntry<F>`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`].
The fields have the type [`idt::Entry<F>`], which is a struct that represents the fields of an IDT entry (see the table above). The type parameter `F` defines the expected handler function type. We see that some entries require a [`HandlerFunc`] and some entries require a [`HandlerFuncWithErrCode`]. The page fault even has its own special type: [`PageFaultHandlerFunc`].
[`IdtEntry<F>`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/struct.IdtEntry.html
[`HandlerFunc`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/type.HandlerFunc.html
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/type.PageFaultHandlerFunc.html
[`idt::Entry<F>`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.Entry.html
[`HandlerFunc`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.HandlerFunc.html
[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/type.PageFaultHandlerFunc.html
Let's look at the `HandlerFunc` type first:
@@ -188,14 +188,14 @@ So the _exception stack frame_ looks like this:
In the `x86_64` crate, the exception stack frame is represented by the [`ExceptionStackFrame`] struct. It is passed to interrupt handlers as `&mut` and can be used to retrieve additional information about the exception's cause. The struct contains no error code field, since only some few exceptions push an error code. These exceptions use the separate [`HandlerFuncWithErrCode`] function type, which has an additional `error_code` argument.
[`ExceptionStackFrame`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/struct.ExceptionStackFrame.html
[`ExceptionStackFrame`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.ExceptionStackFrame.html
### Behind the Scenes
The `x86-interrupt` calling convention is a powerful abstraction that hides almost all of the messy details of the exception handling process. However, sometimes it's useful to know what's happening behind the curtain. Here is a short overview of the things that the `x86-interrupt` calling convention takes care of:
- **Retrieving the arguments**: Most calling conventions expect that the arguments are passed in registers. This is not possible for exception handlers, since we must not overwrite any register values before backing them up on the stack. Instead, the `x86-interrupt` calling convention is aware that the arguments already lie on the stack at a specific offset.
- **Returning using `iretq`**: Since the exception stack frame completely differs from stack frames of normal function calls, we can't return from handlers functions through the normal `ret` instruction. Instead, the `iretq` instruction must be used.
- **Handling the error code**: The error code, which is pushed for some exceptions, makes things much more complex. It changes the stack alignment (see the next point) and needs to be popped off the stack before returning. The `x86-interrupt` calling convention handles all that complexity. However, it doesn't know which handler function is used for which exception, so it needs to deduce that information from the number of function arguments. That means that the programmer is still responsible to use the correct function type for each exception. Luckily, the `Idt` type defined by the `x86_64` crate ensures that the correct function types are used.
- **Handling the error code**: The error code, which is pushed for some exceptions, makes things much more complex. It changes the stack alignment (see the next point) and needs to be popped off the stack before returning. The `x86-interrupt` calling convention handles all that complexity. However, it doesn't know which handler function is used for which exception, so it needs to deduce that information from the number of function arguments. That means that the programmer is still responsible to use the correct function type for each exception. Luckily, the `InterruptDescriptorTable` type defined by the `x86_64` crate ensures that the correct function types are used.
- **Aligning the stack**: There are some instructions (especially SSE instructions) that require a 16-byte stack alignment. The CPU ensures this alignment whenever an exception occurs, but for some exceptions it destroys it again later when it pushes an error code. The `x86-interrupt` calling convention takes care of this by realigning the stack in this case.
If you are interested in more details: We also have a series of posts that explains exception handling using [naked functions] linked [at the end of this post][too-much-magic].
@@ -204,16 +204,16 @@ If you are interested in more details: We also have a series of posts that expla
[too-much-magic]: #too-much-magic
## Implementation
Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We start by creating an `init_idt` function that creates a new `Idt`:
Now that we've understood the theory, it's time to handle CPU exceptions in our kernel. We start by creating an `init_idt` function that creates a new `InterruptDescriptorTable`:
``` rust
// in src/main.rs
extern crate x86_64;
use x86_64::structures::idt::Idt;
use x86_64::structures::idt::InterruptDescriptorTable;
pub fn init_idt() {
let mut idt = Idt::new();
let mut idt = InterruptDescriptorTable::new();
}
```
@@ -230,10 +230,10 @@ For our use case, we don't need to overwrite any instructions. Instead, we just
```rust
/// in src/main.rs
use x86_64::structures::idt::{Idt, ExceptionStackFrame};
use x86_64::structures::idt::{InterruptDescriptorTable, ExceptionStackFrame};
pub fn init_idt() {
let mut idt = Idt::new();
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
}
@@ -263,16 +263,16 @@ error[E0658]: x86-interrupt ABI is experimental and subject to change (see issue
This error occurs because the `x86-interrupt` calling convention is still unstable. To use it anyway, we have to explicitly enable it by adding `#![feature(abi_x86_interrupt)]` on the top of our `main.rs`.
### Loading the IDT
In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `Idt` struct of the `x86_64` provides a [`load`][Idt::load] method function for that. Let's try to use it:
In order that the CPU uses our new interrupt descriptor table, we need to load it using the [`lidt`] instruction. The `InterruptDescriptorTable` struct of the `x86_64` provides a [`load`][InterruptDescriptorTable::load] method function for that. Let's try to use it:
[`lidt`]: http://x86.renejeschke.de/html/file_module_x86_id_156.html
[Idt::load]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/struct.Idt.html#method.load
[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
```rust
// in src/main.rs
pub fn init_idt() {
let mut idt = Idt::new();
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.load();
}
@@ -294,7 +294,7 @@ error: `idt` does not live long enough
So the `load` methods expects a `&'static self`, that is a reference that is valid for the complete runtime of the program. The reason is that the CPU will access this table on every interrupt until we load a different IDT. So using a shorter lifetime than `'static` could lead to use-after-free bugs.
In fact, this is exactly what happens here. Our `idt` is created on the stack, so it is only valid inside the `init` function. Afterwards the stack memory is reused for other functions, so the CPU would interpret random stack memory as IDT. Luckily, the `Idt::load` method encodes this lifetime requirement in its function definition, so that the Rust compiler is able to prevent this possible bug at compile time.
In fact, this is exactly what happens here. Our `idt` is created on the stack, so it is only valid inside the `init` function. Afterwards the stack memory is reused for other functions, so the CPU would interpret random stack memory as IDT. Luckily, the `InterruptDescriptorTable::load` method encodes this lifetime requirement in its function definition, so that the Rust compiler is able to prevent this possible bug at compile time.
In order to fix this problem, we need to store our `idt` at a place where it has a `'static` lifetime. To achieve this we could allocate our IDT on the heap using [`Box`] and then convert it to a `'static` reference, but we are writing an OS kernel and thus don't have a heap (yet).
@@ -304,7 +304,7 @@ In order to fix this problem, we need to store our `idt` at a place where it has
As an alternative we could try to store the IDT as a `static`:
```rust
static IDT: Idt = Idt::new();
static IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
pub fn init_idt() {
IDT.breakpoint.set_handler_fn(breakpoint_handler);
@@ -317,7 +317,7 @@ However, there is a problem: Statics are immutable, so we can't modify the break
[`static mut`]: https://doc.rust-lang.org/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
```rust
static mut IDT: Option<Idt> = Idt::new();
static mut IDT: Option<InterruptDescriptorTable> = InterruptDescriptorTable::new();
pub fn init_idt() {
unsafe {
@@ -345,8 +345,8 @@ We already imported the `lazy_static` crate when we [created an abstraction for
extern crate lazy_static;
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};
@@ -431,9 +431,7 @@ extern "x86-interrupt" fn breakpoint_handler(_: &mut ExceptionStackFrame) {
// […]
```
For space reasons we don't show the full content here. You can find the full file [in this gist].
[in this gist]: https://gist.github.com/phil-opp/ff80a6bfdfcc0e2e90bf3e566c58e3cf
For space reasons we don't show the full content here. You can find the full file [on Github](https://github.com/phil-opp/blog_os/blob/master/src/bin/test-exception-breakpoint.rs).
It is basically a copy of our `main.rs` with some modifications to `_start` and `breakpoint_handler`. The most interesting part is the `BREAKPOINT_HANDLER_CALLER` static. It is an [`AtomicUsize`], an integer type that can be safely concurrently modifies because all of its operations are atomic. We increment it when the `breakpoint_handler` is called and verify in our `_start` function that the handler was called exactly once.
@@ -444,10 +442,10 @@ The [`Ordering`] parameter specifies the desired guarantees of the atomic operat
[`Ordering`]: https://doc.rust-lang.org/core/sync/atomic/enum.Ordering.html
## Too much Magic?
The `x86-interrupt` calling convention and the [`Idt`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own `Idt` type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date.
The `x86-interrupt` calling convention and the [`InterruptDescriptorTable`] type made the exception handling process relatively straightforward and painless. If this was too much magic for you and you like to learn all the gory details of exception handling, we got you covered: Our [“Handling Exceptions with Naked Functions”] series shows how to handle exceptions without the `x86-interrupt` calling convention and also creates its own IDT type. Historically, these posts were the main exception handling posts before the `x86-interrupt` calling convention and the `x86_64` crate existed. Note that these posts are based on the [first edition] of this blog and might be out of date.
[“Handling Exceptions with Naked Functions”]: ./first-edition/extra/naked-exceptions/_index.md
[`Idt`]: https://docs.rs/x86_64/0.2.3/x86_64/structures/idt/struct.Idt.html
[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.2.8/x86_64/structures/idt/struct.InterruptDescriptorTable.html
[first edition]: ./first-edition/_index.md
## What's next?

View File

@@ -63,8 +63,8 @@ A double fault is a normal exception with an error code, so we can specify a han
// in src/main.rs
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt.double_fault.set_handler_fn(double_fault_handler); // new
idt
@@ -222,7 +222,7 @@ The _Privilege Stack Table_ is used by the CPU when the privilege level changes.
### Creating a TSS
Let's create a new TSS that contains a separate double fault stack in its interrupt stack table. For that we need a TSS struct. Fortunately, the `x86_64` crate already contains a [`TaskStateSegment` struct] that we can use.
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.2.3/x86_64/structures/tss/struct.TaskStateSegment.html
[`TaskStateSegment` struct]: https://docs.rs/x86_64/0.2.8/x86_64/structures/tss/struct.TaskStateSegment.html
We create the TSS in a new `gdt` module (the name will make sense later):
@@ -374,8 +374,8 @@ pub fn init() {
We reload the code segment register using [`set_cs`] and to load the TSS using [`load_tss`]. The functions are marked as `unsafe`, so we need an `unsafe` block to invoke them. The reason is that it might be possible to break memory safety by loading invalid selectors.
[`set_cs`]: https://docs.rs/x86_64/0.2.3/x86_64/instructions/segmentation/fn.set_cs.html
[`load_tss`]: https://docs.rs/x86_64/0.2.3/x86_64/instructions/tables/fn.load_tss.html
[`set_cs`]: https://docs.rs/x86_64/0.2.8/x86_64/instructions/segmentation/fn.set_cs.html
[`load_tss`]: https://docs.rs/x86_64/0.2.8/x86_64/instructions/tables/fn.load_tss.html
Now that we loaded a valid TSS and interrupt stack table, we can set the stack index for our double fault handler in the IDT:
@@ -383,8 +383,8 @@ Now that we loaded a valid TSS and interrupt stack table, we can set the stack i
// in src/main.rs
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault.set_handler_fn(double_fault_handler)
@@ -404,9 +404,9 @@ That's it! Now the CPU should switch to the double fault stack whenever a double
From now on we should never see a triple fault again!
To ensure that we don't accidentally break the above, we should add a integration test for this. We don't show the code here for space reasons, but you can find it [in this gist][stack overflow test]. The idea is to do a `serial_println!("ok");` from the double fault handler to ensure that it is called. The rest of the file is is very similar to our `main.rs`.
To ensure that we don't accidentally break the above, we should add a integration test for this. We don't show the code here for space reasons, but you can find it [on Github][stack overflow test]. The idea is to do a `serial_println!("ok");` from the double fault handler to ensure that it is called. The rest of the file is is very similar to our `main.rs`.
[stack overflow test]: https://gist.github.com/phil-opp/9600f367f10615219f3f22110a9a92eb
[stack overflow test]: https://github.com/phil-opp/blog_os/blob/master/src/bin/test-exception-double-fault-stack-overflow.rs
## Summary
In this post we learned what a double fault is and under which conditions it occurs. We added a basic double fault handler that prints an error message and added an integration test for it.

View File

@@ -58,11 +58,11 @@ pub fn panic(info: &PanicInfo) -> ! {
loop {}
}
use x86_64::structures::idt::{ExceptionStackFrame, Idt};
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
idt
};

View File

@@ -52,11 +52,11 @@ pub fn panic(info: &PanicInfo) -> ! {
loop {}
}
use x86_64::structures::idt::{ExceptionStackFrame, Idt};
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
unsafe {
idt.double_fault
.set_handler_fn(double_fault_handler)

View File

@@ -42,11 +42,11 @@ pub fn panic(info: &PanicInfo) -> ! {
loop {}
}
use x86_64::structures::idt::{ExceptionStackFrame, Idt};
use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable};
lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault