= Once::new();
+```
+The `Once` type allows us to initialize a `static` at runtime. It is safe because the only way to access the static value is through the provided methods ([call_once][Once::call_once], [try][Once::try], and [wait][Once::wait]). Thus, no value can be read before initialization and the value can only be initialized once.
+
+[Once::call_once]: https://docs.rs/spin/0.4.5/spin/struct.Once.html#method.call_once
+[Once::try]: https://docs.rs/spin/0.4.5/spin/struct.Once.html#method.try
+[Once::wait]: https://docs.rs/spin/0.4.5/spin/struct.Once.html#method.wait
+
+(The `Once` was added in spin 0.4, so you're probably need to update your spin dependency.)
+
+So let's rewrite our `interrupts::init` function to use the static `TSS` and `GDT`:
+
+```rust
+pub fn init(memory_controller: &mut MemoryController) {
+ let double_fault_stack = memory_controller.alloc_stack(1)
+ .expect("could not allocate double fault stack");
+
+ let tss = TSS.call_once(|| {
+ let mut tss = TaskStateSegment::new();
+ tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] = VirtualAddress(
+ double_fault_stack.top());
+ tss
+ });
+
+ let gdt = GDT.call_once(|| {
+ let mut gdt = gdt::Gdt::new();
+ let code_selector = gdt.add_entry(gdt::Descriptor::
+ kernel_code_segment());
+ let tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&tss));
+ gdt
+ });
+ gdt.load();
+
+ IDT.load();
+}
+```
+
+Now it should compile again!
+
+#### The final Steps
+We're almost done. We successfully loaded our new GDT, which contains a TSS descriptor. Now there are just a few steps left:
+
+1. We changed our GDT, so we should reload the `cs`, the code segment register. This required since the old segment selector could point a different GDT descriptor now (e.g. a TSS descriptor).
+2. We loaded a GDT that contains a TSS selector, but we still need to tell the CPU that it should use that TSS.
+3. As soon as our TSS is loaded, the CPU has access to a valid interrupt stack table (IST). Then we can tell the CPU that it should use our new double fault stack by modifying our double fault IDT entry.
+
+For the first two steps, we need access to the `code_selector` and `tss_selector` variables outside of the closure. We can achieve this by moving the `let` declarations out of the closure:
+
+```rust
+// in src/interrupts/mod.rs
+pub fn init(memory_controller: &mut MemoryController) {
+ use x86_64::structures::gdt::SegmentSelector;
+ use x86_64::instructions::segmentation::set_cs;
+ use x86_64::instructions::tables::load_tss;
+ ...
+
+ let mut code_selector = SegmentSelector(0);
+ let mut tss_selector = SegmentSelector(0);
+ let gdt = GDT.call_once(|| {
+ let mut gdt = gdt::Gdt::new();
+ code_selector = gdt.add_entry(gdt::Descriptor::kernel_code_segment());
+ tss_selector = gdt.add_entry(gdt::Descriptor::tss_segment(&tss));
+ gdt
+ });
+ gdt.load();
+
+ unsafe {
+ // reload code segment register
+ set_cs(code_selector);
+ // load TSS
+ load_tss(tss_selector);
+ }
+
+ IDT.load();
+}
+```
+
+We first set the descriptors to `empty` and then update them from inside the closure (which implicitly borrows them as `&mut`). Now we're able to reload the code segment register using [`set_cs`] and to load the TSS using [`load_tss`].
+
+[`set_cs`]: https://docs.rs/x86/0.8.0/x86/shared/segmentation/fn.set_cs.html
+[`load_tss`]: https://docs.rs/x86/0.8.0/x86/shared/task/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:
+
+```rust
+// in src/interrupt/mod.rs
+
+lazy_static! {
+ static ref IDT: idt::Idt = {
+ let mut idt = idt::Idt::new();
+ ...
+ unsafe {
+ idt.double_fault.set_handler_fn(double_fault_handler)
+ .set_stack_index(DOUBLE_FAULT_IST_INDEX as u16);
+ }
+ ...
+ };
+}
+```
+
+The `set_stack_index` method is unsafe because the the caller must ensure that the used index is valid and not already used for another exception.
+
+That's it! Now the CPU should switch to the double fault stack whenever a double fault occurs. Thus, we are able to catch _all_ double faults, including kernel stack overflows:
+
+
+
+From now on we should never see a triple fault again!
+
+## What's next?
+The next posts will explain how to handle interrupts from external devices such as timers, keyboards, or network controllers. These hardware interrupts are very similar to exceptions, e.g. they are also dispatched through the IDT. However, unlike exceptions, they don't arise directly on the CPU. Instead, an _interrupt controller_ aggregates these interrupts and forwards them to CPU depending on their priority. In the next posts we will explore the two interrupt controller variants on x86: the [Intel 8259] \(“PIC”) and the [APIC]. This will allow us to react to keyboard and mouse input.
+
+[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259
+[APIC]: https://en.wikipedia.org/wiki/Advanced_Programmable_Interrupt_Controller
+
+
+TODO update date
\ No newline at end of file
diff --git a/blog/content/second-edition/posts/07-double-faults/qemu-catch-double-fault.png b/blog/content/second-edition/posts/07-double-faults/qemu-catch-double-fault.png
new file mode 100644
index 00000000..7ed508d4
Binary files /dev/null and b/blog/content/second-edition/posts/07-double-faults/qemu-catch-double-fault.png differ
diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html
index a5cca2b5..601192e8 100644
--- a/blog/templates/second-edition/index.html
+++ b/blog/templates/second-edition/index.html
@@ -37,6 +37,7 @@
Exceptions
{{ macros::post_link(page=posts.5) }}
+ {{ macros::post_link(page=posts.6) }}
diff --git a/src/lib.rs b/src/lib.rs
index 8260af01..d38dc5ba 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,6 +15,7 @@ extern crate std;
pub mod serial;
pub mod vga_buffer;
+pub mod tss;
pub unsafe fn exit_qemu() {
use x86_64::instructions::port::Port;
diff --git a/src/main.rs b/src/main.rs
index 47b305ab..72a022a2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,8 +21,12 @@ pub extern "C" fn _start() -> ! {
init_idt();
- // invoke a breakpoint exception
- x86_64::instructions::int3();
+ fn stack_overflow() {
+ stack_overflow(); // for each recursion, the return address is pushed
+ }
+
+ // trigger a stack overflow
+ stack_overflow();
println!("It did not crash!");
loop {}
@@ -43,6 +47,7 @@ lazy_static! {
static ref IDT: Idt = {
let mut idt = Idt::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.double_fault.set_handler_fn(double_fault_handler);
idt
};
}
@@ -54,3 +59,10 @@ pub fn init_idt() {
extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
+
+extern "x86-interrupt" fn double_fault_handler(
+ stack_frame: &mut ExceptionStackFrame, _error_code: u64)
+{
+ println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
+ loop {}
+}
diff --git a/src/tss.rs b/src/tss.rs
new file mode 100644
index 00000000..51c16f7a
--- /dev/null
+++ b/src/tss.rs
@@ -0,0 +1,14 @@
+use x86_64::structures::tss::TaskStateSegment;
+
+static DOUBLE_FAULT_STACK: [u8; 4096] = [0; 4096];
+const DOUBLE_FAULT_IST_INDEX: usize = 0;
+
+pub fn init() {
+ let mut tss = TaskStateSegment::new();
+ tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX] = {
+ let stack_start = &DOUBLE_FAULT_STACK as *const [u8; _] as usize;
+ let stack_size = DOUBLE_FAULT_STACK.len();
+ let stack_end = stack_start + stack_size;
+ stack_end
+ };
+}