diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md index 90b1549f..66d6f606 100644 --- a/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md +++ b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md @@ -196,7 +196,7 @@ 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: -{{< highlight rust "hl_lines=4 5 6" >}} +```rust #[naked] extern "C" fn divide_by_zero_wrapper() -> ! { unsafe { @@ -205,7 +205,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! { : "rdi" : "intel"); } } -{{< / highlight >}} +``` 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. @@ -221,7 +221,7 @@ error: computation may converge in a function marked as diverging ``` 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: -{{< highlight rust "hl_lines=7" >}} +```rust #[naked] extern "C" fn divide_by_zero_wrapper() -> ! { unsafe { @@ -231,7 +231,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! { ::core::intrinsics::unreachable(); } } -{{< / highlight >}} +``` 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!) @@ -241,7 +241,7 @@ The [intrinsics::unreachable] function is unstable, so we need to add `#![featur ### It works! The last step is to update the interrupt descriptor table (IDT) to use our new wrapper function: -{{< highlight rust "hl_lines=6" >}} +```rust // in src/interrupts/mod.rs lazy_static! { @@ -251,7 +251,7 @@ lazy_static! { idt }; } -{{< / highlight >}} +``` Now we see a correct exception stack frame when we execute `make run`: @@ -272,7 +272,7 @@ Now we should be able to boot from this USB stick. When we do it, we see that it 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`: -{{< highlight rust "hl_lines=4 5 6" >}} +```rust // in src/interrupts/mod.rs extern "C" fn divide_by_zero_handler(...) { @@ -283,7 +283,7 @@ extern "C" fn divide_by_zero_handler(...) { println!(...); loop {} } -{{< / highlight >}} +``` 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. @@ -406,7 +406,7 @@ In order to fix this bug, we need to make sure that the stack pointer is correct 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: -{{< highlight rust "hl_lines=5" >}} +```rust #[naked] extern "C" fn divide_by_zero_wrapper() -> ! { unsafe { @@ -418,7 +418,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! { ::core::intrinsics::unreachable(); } } -{{< / highlight >}} +``` 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. @@ -450,7 +450,7 @@ The macro takes a single Rust identifier (`ident`) as argument and expands to a Now we can remove the `divide_by_zero_wrapper` and use our new `handler!` macro instead: -{{< highlight rust "hl_lines=6" >}} +```rust // in src/interrupts/mod.rs lazy_static! { @@ -460,7 +460,7 @@ lazy_static! { idt }; } -{{< / highlight >}} +``` Note that the `handler!` macro needs to be defined above the static `IDT`, because macros are only available after their definition. @@ -560,7 +560,7 @@ extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, We need to register our new handler function in the static interrupt descriptor table (IDT): -{{< highlight rust "hl_lines=10" >}} +```rust // in src/interrupts/mod.rs lazy_static! { @@ -575,7 +575,7 @@ lazy_static! { idt }; } -{{< / highlight >}} +``` Page faults have the vector number 14, so we set the 14th IDT entry. diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md index b0699f34..98c72900 100644 --- a/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md +++ b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md @@ -57,7 +57,7 @@ We print an error message and also output the instruction pointer and the rest o We need to register our new handler function in the interrupt descriptor table (IDT): -{{< highlight rust "hl_lines=8" >}} +```rust // in src/interrupts/mod.rs lazy_static! { @@ -72,14 +72,14 @@ lazy_static! { idt }; } -{{< / highlight >}} +``` 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`: -{{< highlight rust "hl_lines=3 12 13" >}} +```rust // in src/lib.rs ... #[macro_use] // needed for the `int!` macro @@ -97,7 +97,7 @@ pub extern "C" fn rust_main(...) { println!("It did not crash!"); loop {} } -{{< / highlight >}} +``` When we execute `make run`, we see the following: @@ -139,18 +139,18 @@ EXCEPTION: BREAKPOINT at 0x110970 So let's disassemble the instruction at `0x110970` and its predecessor: -{{< highlight shell "hl_lines=3" >}} +```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) -{{< / highlight >}} +``` 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: -{{< highlight rust "hl_lines=12 16 17 18" >}} +```rust // in src/interrupts/mod.rs macro_rules! handler { @@ -175,7 +175,7 @@ macro_rules! handler { wrapper }} } -{{< / highlight >}} +``` 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. @@ -377,7 +377,7 @@ We need to declare these macros _above_ our `handler` macro, since macros are on Now we can use these macros to fix our `handler!` macro: -{{< highlight rust "hl_lines=8 10 11 17 19" >}} +```rust // in src/interrupts/mod.rs macro_rules! handler { @@ -405,7 +405,7 @@ macro_rules! handler { wrapper }} } -{{< / highlight >}} +``` 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. @@ -518,7 +518,7 @@ The other fields are used for conditional compilation. This allows crate authors #### 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: -{{< highlight json "hl_lines=8" >}} +```json { "llvm-target": "x86_64-unknown-linux-gnu", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", @@ -528,7 +528,7 @@ In order to disable the multimedia extensions, we create a new target named `x86 "os": "none", "features": "-mmx,-sse" } -{{< / highlight >}} +``` 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. @@ -611,13 +611,13 @@ Remember when we discussed calling conventions above? The calling convention def 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`: -{{< highlight json "hl_lines=4" >}} +```json { "llvm-target": "x86_64-unknown-linux-gnu", ... "features": "-mmx,-sse,+soft-float" } -{{< / highlight >}} +``` The plus prefix tells LLVM to enable the `soft-float` feature. @@ -724,14 +724,14 @@ _This will not work._ The problem is that the CPU pushes the exception stack fra ### 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: -{{< highlight json "hl_lines=5" >}} +```json { "llvm-target": "x86_64-unknown-linux-gnu", ... "features": "-mmx,-sse,+soft-float", "disable-redzone": true } -{{< / highlight >}} +``` We add one additional option at the end: `"disable-redzone": true`. As you might guess, this option disables the red zone optimization. @@ -740,7 +740,7 @@ 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: -{{< highlight rust "hl_lines=13 15" >}} +```rust // in src/interrupts/mod.rs macro_rules! handler_with_error_code { @@ -762,7 +762,7 @@ macro_rules! handler_with_error_code { wrapper }} } -{{< / highlight >}} +``` 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. @@ -778,7 +778,7 @@ Now we can make our `page_fault_handler` non-diverging: 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: -{{< highlight rust "hl_lines=8 11 14 18" >}} +```rust // in src/interrupts/mod.rs macro_rules! handler_with_error_code { @@ -804,14 +804,15 @@ macro_rules! handler_with_error_code { wrapper }} } -{{< / highlight >}} +``` + 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: -{{< highlight rust "hl_lines=9 19" >}} +```rust // in src/interrupts/mod.rs macro_rules! handler_with_error_code { @@ -838,7 +839,7 @@ macro_rules! handler_with_error_code { wrapper }} } -{{< / highlight >}} +``` 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`. diff --git a/blog/content/posts/01-multiboot-kernel/index.md b/blog/content/posts/01-multiboot-kernel/index.md index fb98baf7..26622202 100644 --- a/blog/content/posts/01-multiboot-kernel/index.md +++ b/blog/content/posts/01-multiboot-kernel/index.md @@ -139,7 +139,7 @@ To create the ELF _executable_, we need to [link] the object files together. We [link]: https://en.wikipedia.org/wiki/Linker_(computing) [linker script]: https://sourceware.org/binutils/docs/ld/Scripts.html -``` +```ld ENTRY(start) SECTIONS { diff --git a/blog/content/posts/04-printing-to-screen/index.md b/blog/content/posts/04-printing-to-screen/index.md index 01258bec..2cd90e61 100644 --- a/blog/content/posts/04-printing-to-screen/index.md +++ b/blog/content/posts/04-printing-to-screen/index.md @@ -214,10 +214,10 @@ To fix it, we can implement the [Copy] trait for the `ColorCode` type. The easie [Copy]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html [derive macro]: http://rustbyexample.com/trait/derive.html -{{< highlight rust "hl_lines=1" >}} +```rust #[derive(Debug, Clone, Copy)] struct ColorCode(u8); -{{< / highlight >}} +``` We also derive the [Clone] trait, since it's a requirement for `Copy`, and the [Debug] trait, which allows us to print this field for debugging purposes. @@ -230,7 +230,7 @@ However, the [documentation for Copy] says: _“if your type can implement Copy, [documentation for Copy]: https://doc.rust-lang.org/core/marker/trait.Copy.html#when-should-my-type-be-copy -{{< highlight rust "hl_lines=2 6" >}} +```rust #[allow(dead_code)] #[derive(Debug, Clone, Copy)] #[repr(u8)] @@ -239,7 +239,7 @@ pub enum Color {...} #[derive(Debug, Clone, Copy)] #[repr(C)] struct ScreenChar {...} -{{< / highlight >}} +``` ### Try it out! To write some characters to the screen, you can create a temporary function: @@ -315,7 +315,7 @@ Instead of a `ScreenChar`, we're now using a `Volatile`. (The `Volat This means that we have to update our `Writer::write_byte` method: -{{< highlight rust "hl_lines=8 11" >}} +```rust impl Writer { pub fn write_byte(&mut self, byte: u8) { match byte { @@ -333,7 +333,7 @@ impl Writer { } ... } -{{< / highlight >}} +``` Instead of a normal assignment using `=`, we're now using the `write` method. This guarantees that the compiler will never optimize away this write. @@ -375,14 +375,14 @@ The `Ok(())` is just a `Ok` Result containing the `()` type. We can drop the `pu Now we can use Rust's built-in `write!`/`writeln!` formatting macros: -{{< highlight rust "hl_lines=2 4 5 6" >}} +```rust // in the `print_something` function use core::fmt::Write; let mut writer = Writer {...}; writer.write_byte(b'H'); writer.write_str("ello! "); write!(writer, "The numbers are {} and {}", 42, 1.0/3.0); -{{< / highlight >}} +``` Now you should see a `Hello! The numbers are 42 and 0.3333333333333333` at the bottom of the screen. @@ -563,7 +563,7 @@ pub fn clear_screen() { ### Hello World using `println` To use `println` in `lib.rs`, we need to import the macros of the VGA buffer module first. Therefore we add a `#[macro_use]` attribute to the module declaration: -{{< highlight rust "hl_lines=3 9 10" >}} +```rust // in src/lib.rs #[macro_use] @@ -577,7 +577,7 @@ pub extern fn rust_main() { loop{} } -{{< / highlight >}} +``` Since we imported the macros at crate level, they are available in all modules and thus provide an easy and safe interface to the VGA buffer. diff --git a/blog/content/posts/10-double-faults/index.md b/blog/content/posts/10-double-faults/index.md index 543c7712..8067ba80 100644 --- a/blog/content/posts/10-double-faults/index.md +++ b/blog/content/posts/10-double-faults/index.md @@ -26,7 +26,7 @@ A double fault behaves like a normal exception. It has the vector number `8` and ### Triggering a Double Fault Let's provoke a double fault by triggering an exception for that we didn't define a handler function: -{{< highlight rust "hl_lines=10" >}} +```rust // in src/lib.rs #[no_mangle] @@ -43,7 +43,7 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { println!("It did not crash!"); loop {} } -{{< / highlight >}} +``` We try to write to address `0xdeadbeaf`, but the corresponding page is not present in the page tables. Thus, a page fault occurs. We haven't registered a page fault handler in our [IDT], so a double fault occurs. @@ -63,7 +63,7 @@ So in order to prevent this triple fault, we need to either provide a handler fu ### A Double Fault Handler A double fault is a normal exception with an error code, so we can use our `handler_with_error_code` macro to create a wrapper function: -{{< highlight rust "hl_lines=8 14" >}} +```rust // in src/interrupts.rs lazy_static! { @@ -84,7 +84,7 @@ extern "x86-interrupt" fn double_fault_handler( println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); loop {} } -{{< / highlight >}} +``` Our handler prints a short error message and dumps the exception stack frame. The error code of the double fault handler is always zero, so there's no reason to print it. @@ -160,7 +160,7 @@ So the CPU tries to call our _double fault handler_ now. However, on a double fa Let's try it ourselves! We can easily provoke a kernel stack overflow by calling a function that recurses endlessly: -{{< highlight rust "hl_lines=9 10 11 14" >}} +```rust // in src/lib.rs #[no_mangle] @@ -179,7 +179,7 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { println!("It did not crash!"); loop {} } -{{< / highlight >}} +``` When we try this code in QEMU, we see that the system enters a boot-loop again. @@ -402,7 +402,7 @@ impl Add for Page { #### Allocating a Double Fault Stack Now we can allocate a new double fault stack by passing the memory controller to our `interrupts::init` function: -{{< highlight rust "hl_lines=8 11 12 21 22 23" >}} +```rust // in src/lib.rs #[no_mangle] @@ -429,7 +429,7 @@ pub fn init(memory_controller: &mut MemoryController) { IDT.load(); } -{{< / highlight >}} +``` We allocate a 4096 bytes stack (one page) for our double fault handler. Now we just need some way to tell the CPU that it should use this stack for handling double faults. @@ -470,7 +470,7 @@ use x86_64::structures::tss::TaskStateSegment; Let's create a new TSS in our `interrupts::init` function: -{{< highlight rust "hl_lines=3 9 10" >}} +```rust // in src/interrupts.rs use x86_64::VirtualAddress; @@ -487,7 +487,7 @@ pub fn init(memory_controller: &mut MemoryController) { IDT.load(); } -{{< / highlight >}} +``` We define that the 0th IST entry is the double fault stack (any other IST index would work too). We create a new TSS through the `TaskStateSegment::new` function and load the top address (stacks grow downwards) of the double fault stack into the 0th entry. @@ -737,7 +737,7 @@ We now have a double fault stack and are able to create and load a TSS (which co We already created a new TSS in our `interrupts::init` function. Now we can load this TSS by creating a new GDT: -{{< highlight rust "hl_lines=10 11 12 13" >}} +```rust // in src/interrupts/mod.rs pub fn init(memory_controller: &mut MemoryController) { @@ -755,7 +755,7 @@ pub fn init(memory_controller: &mut MemoryController) { IDT.load(); } -{{< / highlight >}} +``` However, when we try to compile it, the following errors occur: @@ -814,7 +814,7 @@ The `Once` type allows us to initialize a `static` at runtime. It is safe becaus So let's rewrite our `interrupts::init` function to use the static `TSS` and `GDT`: -{{< highlight rust "hl_lines=5 9 10 12 17 18" >}} +```rust pub fn init(memory_controller: &mut MemoryController) { let double_fault_stack = memory_controller.alloc_stack(1) .expect("could not allocate double fault stack"); @@ -837,7 +837,7 @@ pub fn init(memory_controller: &mut MemoryController) { IDT.load(); } -{{< / highlight >}} +``` Now it should compile again! @@ -850,7 +850,7 @@ We're almost done. We successfully loaded our new GDT, which contains a TSS desc 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: -{{< highlight rust "hl_lines=3 4 5 8 9 12 13 20 22" >}} +```rust // in src/interrupts/mod.rs pub fn init(memory_controller: &mut MemoryController) { use x86_64::structures::gdt::SegmentSelector; @@ -877,7 +877,7 @@ pub fn init(memory_controller: &mut MemoryController) { IDT.load(); } -{{< / highlight >}} +``` 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`]. @@ -886,7 +886,7 @@ We first set the descriptors to `empty` and then update them from inside the clo 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: -{{< highlight rust "hl_lines=7 9" >}} +```rust // in src/interrupt/mod.rs lazy_static! { @@ -900,7 +900,7 @@ lazy_static! { ... }; } -{{< / highlight >}} +``` 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.