= Idt::new();
+
+pub fn init_idt() {
+ unsafe {
+ IDT.breakpoint.set_handler_fn(breakpoint_handler);
+ IDT.load();
+ }
+}
+```
+
+This variant compiles without errors but it's far from idiomatic. `static mut`s are very prone to data races, so we need an [`unsafe` block] on each access.
+
+[`unsafe` block]: https://doc.rust-lang.org/book/unsafe.html#unsafe-superpowers
+
+#### 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.
+
+We already imported the `lazy_static` crate when we [created an abstraction for the VGA text buffer][vga text buffer lazy static]. So we can directly use the `lazy_static!` macro to create our static IDT:
+
+[vga text buffer lazy static]: ./second-edition/posts/03-vga-text-buffer/index.md#lazy-statics
+
+```rust
+// in src/main.rs
+
+#[macro_use]
+extern crate lazy_static;
+
+lazy_static! {
+ static ref IDT: Idt = {
+ let mut idt = Idt::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt
+ };
+}
+
+pub fn init_idt() {
+ IDT.load();
+}
+```
+
+Note how this solution requires no `unsafe` blocks. The `lazy_static!` macro does use `unsafe` behind the scenes, but it is abstracted away in a safe interface.
+
+### Testing it
+Now we should be able to handle breakpoint exceptions! Let's try it in our `_start` function:
+
+```rust
+// in src/main.rs
+
+#[cfg(not(test))]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ init_idt();
+
+ // invoke a breakpoint exception
+ x86_64::instructions::int3();
+
+ println!("It did not crash!");
+ loop {}
+}
+```
+
+When we run it in QEMU now (using `bootimage run`), we see the following:
+
+
+
+It works! The CPU successfully invokes our breakpoint handler, which prints the message, and then returns back to the `_start` function, where the `It did not crash!` message is printed.
+
+We see that the exception stack frame tells us the instruction and stack pointers at the time when the exception occured. This information is very useful when debugging unexpected exceptions.
+
+### Adding a Test
+
+Let's create an integration test that ensures that the above continues to work. For that we create a file named `test-exception-breakpoint.rs`:
+
+```rust
+// in src/bin/test-exception-breakpoint.rs
+
+use blog_os::exit_qemu;
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);
+
+#[cfg(not(test))]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init_idt();
+
+ // invoke a breakpoint exception
+ x86_64::instructions::int3();
+
+ match BREAKPOINT_HANDLER_CALLED.load(Ordering::SeqCst) {
+ 1 => serial_println!("ok"),
+ 0 => {
+ serial_println!("failed");
+ serial_println!("Breakpoint handler was not called.");
+ }
+ other => {
+ serial_println!("failed");
+ serial_println!("Breakpoint handler was called {} times", other);
+ }
+ }
+
+ unsafe { exit_qemu(); }
+ loop {}
+}
+
+extern "x86-interrupt" fn breakpoint_handler(_: &mut ExceptionStackFrame) {
+ BREAKPOINT_HANDLER_CALLED.fetch_add(1, Ordering::SeqCst);
+}
+
+// […]
+```
+
+For space reasons we don't show the full content here. You can find the full file [in this gist].
+
+[in this gist]: https://gist.github.com/phil-opp/ff80a6bfdfcc0e2e90bf3e566c58e3cf
+
+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.
+
+[`AtomicUsize`]: https://doc.rust-lang.org/core/sync/atomic/struct.AtomicUsize.html
+
+The [`Ordering`] parameter specifies the desired guarantees of the atomic operations. The `SeqCst` ordering means “sequential consistent” and gives the strongest guarantees. It is a good default, because weaker orderings can have undesired effects.
+
+[`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.
+
+[“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
+[first edition]: ./first-edition/_index.md
+
+## What's next?
+We've successfully caught our first exception and returned from it! The next step is to add handlers for other common exceptions such as page faults. We also need to make sure that we never cause a [triple fault], since it causes a complete system reset. The next post explains how we can avoid this by correctly catching [double faults].
+
+[triple fault]: http://wiki.osdev.org/Triple_Fault
+[double faults]: http://wiki.osdev.org/Double_Fault#Double_Fault
diff --git a/blog/content/second-edition/posts/06-cpu-exceptions/qemu-breakpoint-exception.png b/blog/content/second-edition/posts/06-cpu-exceptions/qemu-breakpoint-exception.png
new file mode 100644
index 00000000..d4bcf243
Binary files /dev/null and b/blog/content/second-edition/posts/06-cpu-exceptions/qemu-breakpoint-exception.png differ
diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html
index dab45d79..a5cca2b5 100644
--- a/blog/templates/second-edition/index.html
+++ b/blog/templates/second-edition/index.html
@@ -34,6 +34,11 @@
{{ macros::post_link(page=posts.4) }}
+Exceptions
+
+ {{ macros::post_link(page=posts.5) }}
+
+
Subscribe
Receive notifications about new posts and other major changes.
diff --git a/src/bin/test-exception-breakpoint.rs b/src/bin/test-exception-breakpoint.rs
new file mode 100644
index 00000000..06e71f1c
--- /dev/null
+++ b/src/bin/test-exception-breakpoint.rs
@@ -0,0 +1,77 @@
+#![feature(panic_implementation)]
+#![feature(abi_x86_interrupt)]
+#![no_std]
+#![cfg_attr(not(test), no_main)]
+#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
+
+#[macro_use]
+extern crate blog_os;
+extern crate x86_64;
+#[macro_use]
+extern crate lazy_static;
+
+use blog_os::exit_qemu;
+use core::panic::PanicInfo;
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+static BREAKPOINT_HANDLER_CALLED: AtomicUsize = AtomicUsize::new(0);
+
+#[cfg(not(test))]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init_idt();
+
+ // invoke a breakpoint exception
+ x86_64::instructions::int3();
+
+ match BREAKPOINT_HANDLER_CALLED.load(Ordering::SeqCst) {
+ 1 => serial_println!("ok"),
+ 0 => {
+ serial_println!("failed");
+ serial_println!("Breakpoint handler was not called.");
+ }
+ other => {
+ serial_println!("failed");
+ serial_println!("Breakpoint handler was called {} times", other);
+ }
+ }
+
+ unsafe {
+ exit_qemu();
+ }
+
+ loop {}
+}
+
+/// This function is called on panic.
+#[cfg(not(test))]
+#[panic_implementation]
+#[no_mangle]
+pub fn panic(info: &PanicInfo) -> ! {
+ serial_println!("failed");
+ serial_println!("{}", info);
+
+ unsafe {
+ exit_qemu();
+ }
+
+ loop {}
+}
+
+use x86_64::structures::idt::{ExceptionStackFrame, Idt};
+
+lazy_static! {
+ static ref IDT: Idt = {
+ let mut idt = Idt::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt
+ };
+}
+
+pub fn init_idt() {
+ IDT.load();
+}
+
+extern "x86-interrupt" fn breakpoint_handler(_stack_frame: &mut ExceptionStackFrame) {
+ BREAKPOINT_HANDLER_CALLED.fetch_add(1, Ordering::SeqCst);
+}
diff --git a/src/main.rs b/src/main.rs
index 4c198bcd..47b305ab 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,10 +1,14 @@
#![feature(panic_implementation)] // required for defining the panic handler
+#![feature(abi_x86_interrupt)]
#![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
#[macro_use]
extern crate blog_os;
+extern crate x86_64;
+#[macro_use]
+extern crate lazy_static;
use core::panic::PanicInfo;
@@ -15,6 +19,12 @@ use core::panic::PanicInfo;
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");
+ init_idt();
+
+ // invoke a breakpoint exception
+ x86_64::instructions::int3();
+
+ println!("It did not crash!");
loop {}
}
@@ -26,3 +36,21 @@ pub fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}
+
+use x86_64::structures::idt::{ExceptionStackFrame, Idt};
+
+lazy_static! {
+ static ref IDT: Idt = {
+ let mut idt = Idt::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt
+ };
+}
+
+pub fn init_idt() {
+ IDT.load();
+}
+
+extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) {
+ println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
+}