Replace unsupported highlight syntax with normal code blocks

This commit is contained in:
Philipp Oppermann
2017-06-09 14:43:03 +02:00
parent 945a766f0c
commit 59594b8622
5 changed files with 66 additions and 65 deletions

View File

@@ -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: 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] #[naked]
extern "C" fn divide_by_zero_wrapper() -> ! { extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe { unsafe {
@@ -205,7 +205,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! {
: "rdi" : "intel"); : "rdi" : "intel");
} }
} }
{{< / highlight >}}<!--end_--> ```
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. 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: 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] #[naked]
extern "C" fn divide_by_zero_wrapper() -> ! { extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe { unsafe {
@@ -231,7 +231,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! {
::core::intrinsics::unreachable(); ::core::intrinsics::unreachable();
} }
} }
{{< / highlight >}}<!--end_--> ```
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!) 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! ### It works!
The last step is to update the interrupt descriptor table (IDT) to use our new wrapper function: 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 // in src/interrupts/mod.rs
lazy_static! { lazy_static! {
@@ -251,7 +251,7 @@ lazy_static! {
idt idt
}; };
} }
{{< / highlight >}} ```
Now we see a correct exception stack frame when we execute `make run`: 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`: 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 // in src/interrupts/mod.rs
extern "C" fn divide_by_zero_handler(...) { extern "C" fn divide_by_zero_handler(...) {
@@ -283,7 +283,7 @@ extern "C" fn divide_by_zero_handler(...) {
println!(...); println!(...);
loop {} 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. 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: 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] #[naked]
extern "C" fn divide_by_zero_wrapper() -> ! { extern "C" fn divide_by_zero_wrapper() -> ! {
unsafe { unsafe {
@@ -418,7 +418,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! {
::core::intrinsics::unreachable(); ::core::intrinsics::unreachable();
} }
} }
{{< / highlight >}}<!--end_--> ```
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. 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: 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 // in src/interrupts/mod.rs
lazy_static! { lazy_static! {
@@ -460,7 +460,7 @@ lazy_static! {
idt idt
}; };
} }
{{< / highlight >}} ```
Note that the `handler!` macro needs to be defined above the static `IDT`, because macros are only available after their definition. 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): 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 // in src/interrupts/mod.rs
lazy_static! { lazy_static! {
@@ -575,7 +575,7 @@ lazy_static! {
idt idt
}; };
} }
{{< / highlight >}} ```
Page faults have the vector number 14, so we set the 14th IDT entry. Page faults have the vector number 14, so we set the 14th IDT entry.

View File

@@ -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): 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 // in src/interrupts/mod.rs
lazy_static! { lazy_static! {
@@ -72,14 +72,14 @@ lazy_static! {
idt idt
}; };
} }
{{< / highlight >}} ```
We set the IDT entry with number 3 since it's the vector number of the breakpoint exception. We set the IDT entry with number 3 since it's the vector number of the breakpoint exception.
#### Testing it #### Testing it
In order to test it, we insert an `int3` instruction in our `rust_main`: 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 // in src/lib.rs
... ...
#[macro_use] // needed for the `int!` macro #[macro_use] // needed for the `int!` macro
@@ -97,7 +97,7 @@ pub extern "C" fn rust_main(...) {
println!("It did not crash!"); println!("It did not crash!");
loop {} loop {}
} }
{{< / highlight >}} ```
When we execute `make run`, we see the following: 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: 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:" > objdump -d build/kernel-x86_64.bin | grep -B1 "110970:"
11096f: cc int3 11096f: cc int3
110970: 48 c7 01 2a 00 00 00 movq $0x2a,(%rcx) 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. 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 ### Implementation
Let's update our `handler!` macro to support non-diverging exception handlers: 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 // in src/interrupts/mod.rs
macro_rules! handler { macro_rules! handler {
@@ -175,7 +175,7 @@ macro_rules! handler {
wrapper wrapper
}} }}
} }
{{< / highlight >}}<!--end*--> ```
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. 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: 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 // in src/interrupts/mod.rs
macro_rules! handler { macro_rules! handler {
@@ -405,7 +405,7 @@ macro_rules! handler {
wrapper wrapper
}} }}
} }
{{< / highlight >}}<!--end*--> ```
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. 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 #### 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: 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", "llvm-target": "x86_64-unknown-linux-gnu",
"data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", "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", "os": "none",
"features": "-mmx,-sse" "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. 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`: 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", "llvm-target": "x86_64-unknown-linux-gnu",
... ...
"features": "-mmx,-sse,+soft-float" "features": "-mmx,-sse,+soft-float"
} }
{{< / highlight >}} ```
The plus prefix tells LLVM to enable the `soft-float` feature. 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 ### 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: 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", "llvm-target": "x86_64-unknown-linux-gnu",
... ...
"features": "-mmx,-sse,+soft-float", "features": "-mmx,-sse,+soft-float",
"disable-redzone": true "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. 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 ## 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: 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 // in src/interrupts/mod.rs
macro_rules! handler_with_error_code { macro_rules! handler_with_error_code {
@@ -762,7 +762,7 @@ macro_rules! handler_with_error_code {
wrapper wrapper
}} }}
} }
{{< / highlight >}}<!--end*--> ```
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. 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: 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 // in src/interrupts/mod.rs
macro_rules! handler_with_error_code { macro_rules! handler_with_error_code {
@@ -804,14 +804,15 @@ macro_rules! handler_with_error_code {
wrapper wrapper
}} }}
} }
{{< / highlight >}}<!--end*--> ```
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]. 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. [^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: 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 // in src/interrupts/mod.rs
macro_rules! handler_with_error_code { macro_rules! handler_with_error_code {
@@ -838,7 +839,7 @@ macro_rules! handler_with_error_code {
wrapper wrapper
}} }}
} }
{{< / highlight >}}<!--end*--> ```
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`. 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`.

View File

@@ -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) [link]: https://en.wikipedia.org/wiki/Linker_(computing)
[linker script]: https://sourceware.org/binutils/docs/ld/Scripts.html [linker script]: https://sourceware.org/binutils/docs/ld/Scripts.html
``` ```ld
ENTRY(start) ENTRY(start)
SECTIONS { SECTIONS {

View File

@@ -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 [Copy]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
[derive macro]: http://rustbyexample.com/trait/derive.html [derive macro]: http://rustbyexample.com/trait/derive.html
{{< highlight rust "hl_lines=1" >}} ```rust
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct ColorCode(u8); 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. 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 [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)] #[allow(dead_code)]
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[repr(u8)] #[repr(u8)]
@@ -239,7 +239,7 @@ pub enum Color {...}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[repr(C)] #[repr(C)]
struct ScreenChar {...} struct ScreenChar {...}
{{< / highlight >}} ```
### Try it out! ### Try it out!
To write some characters to the screen, you can create a temporary function: 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<ScreenChar>`. (The `Volat
This means that we have to update our `Writer::write_byte` method: This means that we have to update our `Writer::write_byte` method:
{{< highlight rust "hl_lines=8 11" >}} ```rust
impl Writer { impl Writer {
pub fn write_byte(&mut self, byte: u8) { pub fn write_byte(&mut self, byte: u8) {
match byte { 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. 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: 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 // in the `print_something` function
use core::fmt::Write; use core::fmt::Write;
let mut writer = Writer {...}; let mut writer = Writer {...};
writer.write_byte(b'H'); writer.write_byte(b'H');
writer.write_str("ello! "); writer.write_str("ello! ");
write!(writer, "The numbers are {} and {}", 42, 1.0/3.0); 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. 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` ### 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: 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 // in src/lib.rs
#[macro_use] #[macro_use]
@@ -577,7 +577,7 @@ pub extern fn rust_main() {
loop{} 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. 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.

View File

@@ -26,7 +26,7 @@ A double fault behaves like a normal exception. It has the vector number `8` and
### Triggering a Double Fault ### Triggering a Double Fault
Let's provoke a double fault by triggering an exception for that we didn't define a handler function: 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 // in src/lib.rs
#[no_mangle] #[no_mangle]
@@ -43,7 +43,7 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) {
println!("It did not crash!"); println!("It did not crash!");
loop {} 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. 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 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: 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 // in src/interrupts.rs
lazy_static! { lazy_static! {
@@ -84,7 +84,7 @@ extern "x86-interrupt" fn double_fault_handler(
println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); println!("\nEXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
loop {} loop {}
} }
{{< / highlight >}}<!--end_--> ```
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. 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: 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 // in src/lib.rs
#[no_mangle] #[no_mangle]
@@ -179,7 +179,7 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) {
println!("It did not crash!"); println!("It did not crash!");
loop {} loop {}
} }
{{< / highlight >}} ```
When we try this code in QEMU, we see that the system enters a boot-loop again. When we try this code in QEMU, we see that the system enters a boot-loop again.
@@ -402,7 +402,7 @@ impl Add<usize> for Page {
#### Allocating a Double Fault Stack #### Allocating a Double Fault Stack
Now we can allocate a new double fault stack by passing the memory controller to our `interrupts::init` function: 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 // in src/lib.rs
#[no_mangle] #[no_mangle]
@@ -429,7 +429,7 @@ pub fn init(memory_controller: &mut MemoryController) {
IDT.load(); 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. 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: Let's create a new TSS in our `interrupts::init` function:
{{< highlight rust "hl_lines=3 9 10" >}} ```rust
// in src/interrupts.rs // in src/interrupts.rs
use x86_64::VirtualAddress; use x86_64::VirtualAddress;
@@ -487,7 +487,7 @@ pub fn init(memory_controller: &mut MemoryController) {
IDT.load(); 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. 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: 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 // in src/interrupts/mod.rs
pub fn init(memory_controller: &mut MemoryController) { pub fn init(memory_controller: &mut MemoryController) {
@@ -755,7 +755,7 @@ pub fn init(memory_controller: &mut MemoryController) {
IDT.load(); IDT.load();
} }
{{< / highlight >}} ```
However, when we try to compile it, the following errors occur: 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`: 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) { pub fn init(memory_controller: &mut MemoryController) {
let double_fault_stack = memory_controller.alloc_stack(1) let double_fault_stack = memory_controller.alloc_stack(1)
.expect("could not allocate double fault stack"); .expect("could not allocate double fault stack");
@@ -837,7 +837,7 @@ pub fn init(memory_controller: &mut MemoryController) {
IDT.load(); IDT.load();
} }
{{< / highlight >}} ```
Now it should compile again! 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: 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 // in src/interrupts/mod.rs
pub fn init(memory_controller: &mut MemoryController) { pub fn init(memory_controller: &mut MemoryController) {
use x86_64::structures::gdt::SegmentSelector; use x86_64::structures::gdt::SegmentSelector;
@@ -877,7 +877,7 @@ pub fn init(memory_controller: &mut MemoryController) {
IDT.load(); 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`]. 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: 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 // in src/interrupt/mod.rs
lazy_static! { 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. 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.