From 3faa0cfcad6134061c3c75ba179554fbbfbfdd65 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 14 Jul 2018 14:05:47 +0200 Subject: [PATCH 1/3] Don't ignore Cargo.lock since this is not a library --- .gitignore | 1 - Cargo.lock | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 63cac0bd..30023c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,5 @@ # Generated by Cargo /target/ -Cargo.lock bootimage.bin diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..084f0709 --- /dev/null +++ b/Cargo.lock @@ -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.7 (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.7 (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.7" +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.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f14cca51c44bb0f4b5d8048789c46ae2df9bf04cc30dae5242584fa16802ef83" From 0f07341dea24179e2d9aa4ff1568ea6def10fc7a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 14 Jul 2018 14:32:33 +0200 Subject: [PATCH 2/3] Update to x86_64 0.2.8: The Idt type was renamed to InterruptDescriptorTable --- Cargo.lock | 8 +- Cargo.toml | 2 +- .../posts/05-integration-tests/index.md | 2 +- .../posts/06-cpu-exceptions/index.md | 92 +++++++++---------- .../posts/07-double-faults/index.md | 14 +-- src/bin/test-exception-breakpoint.rs | 6 +- ...t-exception-double-fault-stack-overflow.rs | 6 +- src/main.rs | 6 +- 8 files changed, 68 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 084f0709..f05c83f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ dependencies = [ "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.7 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -57,7 +57,7 @@ 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.7 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -77,7 +77,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "x86_64" -version = "0.2.7" +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)", @@ -99,4 +99,4 @@ dependencies = [ "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.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f14cca51c44bb0f4b5d8048789c46ae2df9bf04cc30dae5242584fa16802ef83" +"checksum x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "51b347fd81faca2e19366605a2bb52aa2f0e1572835678e3355b66aab18650e6" diff --git a/Cargo.toml b/Cargo.toml index 56729e68..2a2f1db4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/blog/content/second-edition/posts/05-integration-tests/index.md b/blog/content/second-edition/posts/05-integration-tests/index.md index 7a2b8352..baec3e66 100644 --- a/blog/content/second-edition/posts/05-integration-tests/index.md +++ b/blog/content/second-edition/posts/05-integration-tests/index.md @@ -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 diff --git a/blog/content/second-edition/posts/06-cpu-exceptions/index.md b/blog/content/second-edition/posts/06-cpu-exceptions/index.md index 7f5f310f..d428db78 100644 --- a/blog/content/second-edition/posts/06-cpu-exceptions/index.md +++ b/blog/content/second-edition/posts/06-cpu-exceptions/index.md @@ -24,7 +24,7 @@ On x86 there are about 20 different CPU exception types. The most important are: - **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. - **Invalid Opcode**: This exception occurs when the current instruction is invalid, for example when we try to use newer [SSE instructions] on an old CPU that does not support them. -- **General Protection Fault**: This is the exception with the broadest range of causes. It occurs on various kinds of access violations such as trying to executing a privileged instruction in user level code or writing reserved fields in configuration registers. +- **General Protection Fault**: This is the exception with the broadest range of causes. It occurs on various kinds of access violations such as trying to executing a privileged instruction in user level code or writing reserved fields in configuration registers. - **Double Fault**: When an exception occurs, the CPU tries to call the corresponding handler function. If another 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. @@ -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, - pub debug: IdtEntry, - pub non_maskable_interrupt: IdtEntry, - pub breakpoint: IdtEntry, - pub overflow: IdtEntry, - pub bound_range_exceeded: IdtEntry, - pub invalid_opcode: IdtEntry, - pub device_not_available: IdtEntry, - pub double_fault: IdtEntry, - pub invalid_tss: IdtEntry, - pub segment_not_present: IdtEntry, - pub stack_segment_fault: IdtEntry, - pub general_protection_fault: IdtEntry, - pub page_fault: IdtEntry, - pub x87_floating_point: IdtEntry, - pub alignment_check: IdtEntry, - pub machine_check: IdtEntry, - pub simd_floating_point: IdtEntry, - pub virtualization: IdtEntry, - pub security_exception: IdtEntry, +pub struct InterruptDescriptorTable { + pub divide_by_zero: Entry, + pub debug: Entry, + pub non_maskable_interrupt: Entry, + pub breakpoint: Entry, + pub overflow: Entry, + pub bound_range_exceeded: Entry, + pub invalid_opcode: Entry, + pub device_not_available: Entry, + pub double_fault: Entry, + pub invalid_tss: Entry, + pub segment_not_present: Entry, + pub stack_segment_fault: Entry, + pub general_protection_fault: Entry, + pub page_fault: Entry, + pub x87_floating_point: Entry, + pub alignment_check: Entry, + pub machine_check: Entry, + pub simd_floating_point: Entry, + pub virtualization: Entry, + pub security_exception: Entry, // some fields omitted } ``` -The fields have the type [`IdtEntry`], 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`], 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`]: 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`]: 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::new(); +static mut IDT: Option = 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 }; @@ -444,10 +444,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? diff --git a/blog/content/second-edition/posts/07-double-faults/index.md b/blog/content/second-edition/posts/07-double-faults/index.md index 61fd53fc..4ed97b9d 100644 --- a/blog/content/second-edition/posts/07-double-faults/index.md +++ b/blog/content/second-edition/posts/07-double-faults/index.md @@ -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) diff --git a/src/bin/test-exception-breakpoint.rs b/src/bin/test-exception-breakpoint.rs index 06e71f1c..03906cbd 100644 --- a/src/bin/test-exception-breakpoint.rs +++ b/src/bin/test-exception-breakpoint.rs @@ -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 }; diff --git a/src/bin/test-exception-double-fault-stack-overflow.rs b/src/bin/test-exception-double-fault-stack-overflow.rs index 50ea960f..560f8f34 100644 --- a/src/bin/test-exception-double-fault-stack-overflow.rs +++ b/src/bin/test-exception-double-fault-stack-overflow.rs @@ -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) diff --git a/src/main.rs b/src/main.rs index 361b7e7b..9b56c433 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 From 934c7f341302f030f2f8cce44f93be168afda7eb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 14 Jul 2018 14:34:21 +0200 Subject: [PATCH 3/3] Link to Github instead of gists --- blog/content/second-edition/posts/06-cpu-exceptions/index.md | 4 +--- blog/content/second-edition/posts/07-double-faults/index.md | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/blog/content/second-edition/posts/06-cpu-exceptions/index.md b/blog/content/second-edition/posts/06-cpu-exceptions/index.md index d428db78..4806068d 100644 --- a/blog/content/second-edition/posts/06-cpu-exceptions/index.md +++ b/blog/content/second-edition/posts/06-cpu-exceptions/index.md @@ -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. diff --git a/blog/content/second-edition/posts/07-double-faults/index.md b/blog/content/second-edition/posts/07-double-faults/index.md index 4ed97b9d..1bf38837 100644 --- a/blog/content/second-edition/posts/07-double-faults/index.md +++ b/blog/content/second-edition/posts/07-double-faults/index.md @@ -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.