From 66e10facc1f7be85aec0115ffa381f9a0fff48bd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 9 Jun 2017 14:09:56 +0200 Subject: [PATCH] Move images next to their corresponding posts --- .../index.md} | 4 +- .../normal-vs-interrupt-function-return.svg | 0 .../qemu-divide-error-println.png | Bin .../exception-stack-frame.svg | 0 .../index.md} | 10 +- .../qemu-divide-by-zero-stack-frame.png | Bin .../qemu-page-fault-error-code.png | Bin .../qemu-page-fault-handler.png | Bin .../qemu-print-stack-frame-try.png | Bin .../exception-stack-frame.svg | 2 + .../function-stack-frame.svg | 0 .../index.md} | 18 +- .../qemu-breakpoint-handler.png | Bin .../qemu-breakpoint-return-page-fault.png | Bin .../qemu-breakpoint-return.png | Bin .../qemu-page-fault-return.png | Bin .../red-zone-overwrite.svg | 0 .../red-zone.svg | 0 .../xmm-overwrite.svg | 0 .../extra/set-up-gdb}/gdb-tui-screenshot.png | Bin .../{set-up-gdb.md => set-up-gdb/index.md} | 2 +- .../posts/01-multiboot-kernel/index.md | 2 +- .../posts/01-multiboot-kernel}/qemu-ok.png | Bin .../X86_Paging_64bit.svg | 0 .../posts/02-entering-longmode/index.md | 2 +- blog/content/posts/03-set-up-rust/index.md | 4 +- .../03-set-up-rust/red-zone-overwrite.svg | 92 ++ .../content/posts/03-set-up-rust/red-zone.svg | 62 ++ .../fixed-println-deadlock.png | Bin .../posts/04-printing-to-screen/index.md | 6 +- .../vga-H-lower-left.png | Bin .../vga-hello-world.png | Bin .../posts/05-allocating-frames/index.md | 2 +- .../qemu-memory-areas-and-kernel-sections.png | Bin .../posts/06-page-tables/X86_Paging_64bit.svg | 929 ++++++++++++++++++ blog/content/posts/06-page-tables/index.md | 10 +- .../recursive_mapping_access_p1.svg | 0 .../recursive_mapping_access_p3.svg | 0 .../recursive_mapping_access_p4.svg | 0 .../06-page-tables}/x86_address_structure.svg | 0 .../cyclic_mapping_inactive_tables.svg | 0 .../posts/07-remap-the-kernel/index.md | 10 +- ...ive_mapping_access_p1_invalid_chaining.svg | 0 .../recursive_mapping_access_p3.svg | 819 +++++++++++++++ ...rsive_mapping_access_p3_inactive_table.svg | 0 ...ecursive_mapping_inactive_table_scheme.svg | 0 .../posts/08-kernel-heap}/allocate.svg | 0 .../posts/08-kernel-heap}/deallocate.svg | 0 blog/content/posts/08-kernel-heap/index.md | 14 +- .../posts/08-kernel-heap}/initialization.svg | 0 .../merge-holes-and-allocate.svg | 0 .../posts/08-kernel-heap}/overview.svg | 0 .../posts/08-kernel-heap}/split-hole.svg | 0 .../exception-stack-frame.svg | 2 + .../function-stack-frame.svg | 2 + .../posts/09-handling-exceptions/index.md | 6 +- .../qemu-breakpoint-exception.png | Bin .../posts/10-double-faults}/boot-loop.gif | Bin blog/content/posts/10-double-faults/index.md | 6 +- .../qemu-catch-double-fault.png | Bin .../qemu-double-fault-on-stack-overflow.png | Bin blog/static/images/qemu-empty-IST-entry.png | Bin 17532 -> 0 bytes 62 files changed, 1956 insertions(+), 48 deletions(-) rename blog/content/extra/handling-exceptions-with-naked-fns/{catching-exceptions.md => 01-catching-exceptions/index.md} (99%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions}/normal-vs-interrupt-function-return.svg (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions}/qemu-divide-error-println.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages}/exception-stack-frame.svg (100%) rename blog/content/extra/handling-exceptions-with-naked-fns/{better-exception-messages.md => 02-better-exception-messages/index.md} (98%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages}/qemu-divide-by-zero-stack-frame.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages}/qemu-page-fault-error-code.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages}/qemu-page-fault-handler.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages}/qemu-print-stack-frame-try.png (100%) create mode 100644 blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/exception-stack-frame.svg rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/function-stack-frame.svg (100%) rename blog/content/extra/handling-exceptions-with-naked-fns/{returning-from-exceptions.md => 03-returning-from-exceptions/index.md} (98%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/qemu-breakpoint-handler.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/qemu-breakpoint-return-page-fault.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/qemu-breakpoint-return.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/qemu-page-fault-return.png (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/red-zone-overwrite.svg (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/red-zone.svg (100%) rename blog/{static/images => content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions}/xmm-overwrite.svg (100%) rename blog/{static/images => content/extra/set-up-gdb}/gdb-tui-screenshot.png (100%) rename blog/content/extra/{set-up-gdb.md => set-up-gdb/index.md} (98%) rename blog/{static/images => content/posts/01-multiboot-kernel}/qemu-ok.png (100%) rename blog/{static/images => content/posts/02-entering-longmode}/X86_Paging_64bit.svg (100%) create mode 100644 blog/content/posts/03-set-up-rust/red-zone-overwrite.svg create mode 100644 blog/content/posts/03-set-up-rust/red-zone.svg rename blog/{static/images => content/posts/04-printing-to-screen}/fixed-println-deadlock.png (100%) rename blog/{static/images => content/posts/04-printing-to-screen}/vga-H-lower-left.png (100%) rename blog/{static/images => content/posts/04-printing-to-screen}/vga-hello-world.png (100%) rename blog/{static/images => content/posts/05-allocating-frames}/qemu-memory-areas-and-kernel-sections.png (100%) create mode 100644 blog/content/posts/06-page-tables/X86_Paging_64bit.svg rename blog/{static/images => content/posts/06-page-tables}/recursive_mapping_access_p1.svg (100%) rename blog/{static/images => content/posts/06-page-tables}/recursive_mapping_access_p3.svg (100%) rename blog/{static/images => content/posts/06-page-tables}/recursive_mapping_access_p4.svg (100%) rename blog/{static/images => content/posts/06-page-tables}/x86_address_structure.svg (100%) rename blog/{static/images => content/posts/07-remap-the-kernel}/cyclic_mapping_inactive_tables.svg (100%) rename blog/{static/images => content/posts/07-remap-the-kernel}/recursive_mapping_access_p1_invalid_chaining.svg (100%) create mode 100644 blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3.svg rename blog/{static/images => content/posts/07-remap-the-kernel}/recursive_mapping_access_p3_inactive_table.svg (100%) rename blog/{static/images => content/posts/07-remap-the-kernel}/recursive_mapping_inactive_table_scheme.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/allocate.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/deallocate.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/initialization.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/merge-holes-and-allocate.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/overview.svg (100%) rename blog/{static/images/linked-list-allocator => content/posts/08-kernel-heap}/split-hole.svg (100%) create mode 100644 blog/content/posts/09-handling-exceptions/exception-stack-frame.svg create mode 100644 blog/content/posts/09-handling-exceptions/function-stack-frame.svg rename blog/{static/images => content/posts/09-handling-exceptions}/qemu-breakpoint-exception.png (100%) rename blog/{static/images => content/posts/10-double-faults}/boot-loop.gif (100%) rename blog/{static/images => content/posts/10-double-faults}/qemu-catch-double-fault.png (100%) rename blog/{static/images => content/posts/10-double-faults}/qemu-double-fault-on-stack-overflow.png (100%) delete mode 100644 blog/static/images/qemu-empty-IST-entry.png diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/catching-exceptions.md b/blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/index.md similarity index 99% rename from blog/content/extra/handling-exceptions-with-naked-fns/catching-exceptions.md rename to blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/index.md index 3dfbf37c..a638d1e3 100644 --- a/blog/content/extra/handling-exceptions-with-naked-fns/catching-exceptions.md +++ b/blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/index.md @@ -241,7 +241,7 @@ It is important that the function is [diverging], i.e. it must never return. The [diverging]: https://doc.rust-lang.org/book/functions.html#diverging-functions -![normal function return vs interrupt function return](/images/normal-vs-interrupt-function-return.svg) +![normal function return vs interrupt function return](normal-vs-interrupt-function-return.svg) If our handler function returned normally, it would try to pop the return address from the stack. But it might get some completely different value then. For example, the CPU pushes an error code for some exceptions. Bad things would happen if we interpreted this error code as return address and jumped to it. Therefore interrupt handler functions must diverge[^fn-must-diverge]. @@ -550,7 +550,7 @@ pub extern "C" fn rust_main(...) { It works! We see a `EXCEPTION: DIVIDE BY ZERO` message at the bottom of our screen: -![QEMU screenshot with `EXCEPTION: DIVIDE BY ZERO` message](images/qemu-divide-error-println.png) +![QEMU screenshot with `EXCEPTION: DIVIDE BY ZERO` message](qemu-divide-error-println.png) ## What's next? We've successfully caught our first exception! However, our `EXCEPTION: DIVIDE BY ZERO` message doesn't contain much information about the cause of the exception. The next post improves the situation by printing i.a. the current stack pointer and address of the causing instruction. We will also explore other exceptions such as page faults, for which the CPU pushes an _error code_ on the stack. diff --git a/blog/static/images/normal-vs-interrupt-function-return.svg b/blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/normal-vs-interrupt-function-return.svg similarity index 100% rename from blog/static/images/normal-vs-interrupt-function-return.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/normal-vs-interrupt-function-return.svg diff --git a/blog/static/images/qemu-divide-error-println.png b/blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/qemu-divide-error-println.png similarity index 100% rename from blog/static/images/qemu-divide-error-println.png rename to blog/content/extra/handling-exceptions-with-naked-fns/01-catching-exceptions/qemu-divide-error-println.png diff --git a/blog/static/images/exception-stack-frame.svg b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/exception-stack-frame.svg similarity index 100% rename from blog/static/images/exception-stack-frame.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/exception-stack-frame.svg diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/better-exception-messages.md b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md similarity index 98% rename from blog/content/extra/handling-exceptions-with-naked-fns/better-exception-messages.md rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md index 843d1cbb..90b1549f 100644 --- a/blog/content/extra/handling-exceptions-with-naked-fns/better-exception-messages.md +++ b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/index.md @@ -24,7 +24,7 @@ An exception signals that something is wrong with the currently-executed instruc This routine involves reading the interrupt descriptor table and invoking the registered handler function. But first, the CPU pushes various information onto the stack, which describe the current state and provide information about the cause of the exception: -![exception stack frame](images/exception-stack-frame.svg) +![exception stack frame](exception-stack-frame.svg) The pushed information contain the instruction and stack pointer, the current CPU flags, and (for some exceptions) an error code, which contains further information about the cause of the exception. Let's look at the fields in detail: @@ -86,7 +86,7 @@ So the inline assembly loads the stack pointer value to `stack_frame` at the ver ### Testing it Let's try it by executing `make run`: -![qemu printing an ExceptionStackFrame with strange values](images/qemu-print-stack-frame-try.png) +![qemu printing an ExceptionStackFrame with strange values](qemu-print-stack-frame-try.png) Those `ExceptionStackFrame` values look very wrong. The instruction pointer definitely shouldn't be 1 and the code segment should be `0x8` instead of some big number. So what's going on here? @@ -255,7 +255,7 @@ lazy_static! { Now we see a correct exception stack frame when we execute `make run`: -![QEMU showing correct divide by zero stack frame](images/qemu-divide-by-zero-stack-frame.png) +![QEMU showing correct divide by zero stack frame](qemu-divide-by-zero-stack-frame.png) ## Testing on real Hardware Virtual machines such as QEMU are very convenient to quickly test our kernel. However, they might behave a bit different than real hardware in some situations. So we should test our kernel on real hardware, too. @@ -602,7 +602,7 @@ pub extern "C" fn rust_main(multiboot_information_address: usize) { We get the following output: -![QEMU: page fault with error code 2 and stack frame dump](images/qemu-page-fault-handler.png) +![QEMU: page fault with error code 2 and stack frame dump](qemu-page-fault-handler.png) ### The Page Fault Error Code “Error code 2” is not really an useful error message. Let's improve this by creating a `PageFaultErrorCode` type: @@ -647,7 +647,7 @@ The `from_bits` function tries to convert the `u64` into a `PageFaultErrorCode`. Now we get a useful error message when a page fault occurs, which allows us to debug it more easily: -![QEMU: output is now `PAGE FAULT with error code CAUSED_BY_WRITE`](images/qemu-page-fault-error-code.png) +![QEMU: output is now `PAGE FAULT with error code CAUSED_BY_WRITE`](qemu-page-fault-error-code.png) As expected, the page fault was caused by write to `0xdeadbeaf`. The `PROTECTION_VIOLATION` flag is not set, so the accessed page was not present. diff --git a/blog/static/images/qemu-divide-by-zero-stack-frame.png b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-divide-by-zero-stack-frame.png similarity index 100% rename from blog/static/images/qemu-divide-by-zero-stack-frame.png rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-divide-by-zero-stack-frame.png diff --git a/blog/static/images/qemu-page-fault-error-code.png b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-page-fault-error-code.png similarity index 100% rename from blog/static/images/qemu-page-fault-error-code.png rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-page-fault-error-code.png diff --git a/blog/static/images/qemu-page-fault-handler.png b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-page-fault-handler.png similarity index 100% rename from blog/static/images/qemu-page-fault-handler.png rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-page-fault-handler.png diff --git a/blog/static/images/qemu-print-stack-frame-try.png b/blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-print-stack-frame-try.png similarity index 100% rename from blog/static/images/qemu-print-stack-frame-try.png rename to blog/content/extra/handling-exceptions-with-naked-fns/02-better-exception-messages/qemu-print-stack-frame-try.png diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/exception-stack-frame.svg b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/exception-stack-frame.svg new file mode 100644 index 00000000..c099dff5 --- /dev/null +++ b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/exception-stack-frame.svg @@ -0,0 +1,2 @@ + +
New Stack
Pointer
[Not supported by viewer]
Stack Alignment (variable)
Stack Alignment (variable)
Stack Segment (SS)
Stack Segment (SS)
Stack Frame of the Handler Function
Stack Frame of the Handler Function
Stack Pointer (RSP)
Stack Pointer (RSP)
RFLAGS
RFLAGS
Code Segment (CS)
Code Segment (CS)
Instruction Pointer (RIP)
Instruction Pointer (RIP)
Error Code (optional)
Error Code (optional)
8 Byte
8 Byte
Old Stack
Pointer
[Not supported by viewer]
\ No newline at end of file diff --git a/blog/static/images/function-stack-frame.svg b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/function-stack-frame.svg similarity index 100% rename from blog/static/images/function-stack-frame.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/function-stack-frame.svg diff --git a/blog/content/extra/handling-exceptions-with-naked-fns/returning-from-exceptions.md b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md similarity index 98% rename from blog/content/extra/handling-exceptions-with-naked-fns/returning-from-exceptions.md rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md index b4c12d71..5fdbc89d 100644 --- a/blog/content/extra/handling-exceptions-with-naked-fns/returning-from-exceptions.md +++ b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/index.md @@ -101,20 +101,20 @@ pub extern "C" fn rust_main(...) { When we execute `make run`, we see the following: -![QEMU showing `EXCEPTION: BREAKPOINT at 0x110970` and a dump of the exception stack frame](images/qemu-breakpoint-handler.png) +![QEMU showing `EXCEPTION: BREAKPOINT at 0x110970` and a dump of the exception stack frame](qemu-breakpoint-handler.png) It works! Now we “just” need to return from the breakpoint handler somehow so that we see the `It did not crash` message again. ## Returning from Exceptions So how do we return from exceptions? To make it easier, we look at a normal function return first: -![function stack frame](images/function-stack-frame.svg) +![function stack frame](function-stack-frame.svg) When calling a function, the `call` instruction pushes the return address on the stack. When the called function is finished, it can return to the parent function through the `ret` instruction, which pops the return address from the stack and then jumps to it. The exception stack frame, in contrast, looks a bit different: -![exception stack frame](images/exception-stack-frame.svg) +![exception stack frame](exception-stack-frame.svg) Instead of pushing a return address, the CPU pushes the stack and instruction pointers (with their segment descriptors), the RFLAGS register, and an optional error code. It also aligns the stack pointer to a 16 byte boundary before pushing values. @@ -204,7 +204,7 @@ Note that we also removed the `loop {}` at the end of our `breakpoint_handler` s ### Testing Let's try our new `iretq` logic: -![QEMU output with `EXCEPTION BREAKPOINT` and `EXCEPTION PAGE FAULT` but no `It did not crash`](images/qemu-breakpoint-return-page-fault.png) +![QEMU output with `EXCEPTION BREAKPOINT` and `EXCEPTION PAGE FAULT` but no `It did not crash`](qemu-breakpoint-return-page-fault.png) Instead of the expected _“It did not crash”_ message after the breakpoint exception, we get a page fault. The strange thing is that our kernel tried to access address `0x1`, which should never happen. So it seems like we messed up something important. @@ -414,7 +414,7 @@ Note that we no longer need to manually align the stack pointer, because we're p ### Testing it again Let's test it again with our corrected `handler!` macro: -![QEMU output with `EXCEPTION BREAKPOINT` and `It did not crash`](images/qemu-breakpoint-return.png) +![QEMU output with `EXCEPTION BREAKPOINT` and `It did not crash`](qemu-breakpoint-return.png) The page fault is gone and we see the _“It did not crash”_ message again! @@ -436,7 +436,7 @@ However, auto-vectorization causes a problem for us: Most of the multimedia regi We don't use any multimedia registers explicitly, but the Rust compiler might auto-vectorize our code (including the exception handlers). Thus we could silently clobber the multimedia registers, which leads to the same problems as above: -![example: program uses mm0, mm1, and mm2. Then the exception handler clobbers mm1.](images/xmm-overwrite.svg) +![example: program uses mm0, mm1, and mm2. Then the exception handler clobbers mm1.](xmm-overwrite.svg) This example shows a program that is using the first three multimedia registers (`mm0` to `mm2`). At some point, an exception occurs and control is transfered to the exception handler. The exception handler uses `mm1` for its own data and thus overwrites the previous value. When the exception is resolved, the CPU continues the interrupted program again. However, the program is now corrupt since it relies on the original `mm1` value. @@ -691,7 +691,7 @@ The [red zone] is an optimization of the [System V ABI] that allows functions to [red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone -![stack frame with red zone](images/red-zone.svg) +![stack frame with red zone](red-zone.svg) The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the local variables. @@ -699,7 +699,7 @@ The red zone is defined as the 128 bytes below the adjusted stack pointer. The f However, this optimization leads to huge problems with exceptions. Let's assume that an exception occurs while a function uses the red zone: -![red zone overwritten by exception handler](images/red-zone-overwrite.svg) +![red zone overwritten by exception handler](red-zone-overwrite.svg) The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. It might fail or cause another exception, but it could also lead to strange bugs that [take weeks to debug]. @@ -878,7 +878,7 @@ The error code should still be `CAUSED_BY_WRITE` and the exception stack frame v #### Returning from Page Faults Let's see what happens if we comment out the trailing `loop` in our page fault handler: -![QEMU printing the same page fault message again and again](images/qemu-page-fault-return.png) +![QEMU printing the same page fault message again and again](qemu-page-fault-return.png) We see that the same error message is printed over and over again. Here is what happens: diff --git a/blog/static/images/qemu-breakpoint-handler.png b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-handler.png similarity index 100% rename from blog/static/images/qemu-breakpoint-handler.png rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-handler.png diff --git a/blog/static/images/qemu-breakpoint-return-page-fault.png b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-return-page-fault.png similarity index 100% rename from blog/static/images/qemu-breakpoint-return-page-fault.png rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-return-page-fault.png diff --git a/blog/static/images/qemu-breakpoint-return.png b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-return.png similarity index 100% rename from blog/static/images/qemu-breakpoint-return.png rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-breakpoint-return.png diff --git a/blog/static/images/qemu-page-fault-return.png b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-page-fault-return.png similarity index 100% rename from blog/static/images/qemu-page-fault-return.png rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/qemu-page-fault-return.png diff --git a/blog/static/images/red-zone-overwrite.svg b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/red-zone-overwrite.svg similarity index 100% rename from blog/static/images/red-zone-overwrite.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/red-zone-overwrite.svg diff --git a/blog/static/images/red-zone.svg b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/red-zone.svg similarity index 100% rename from blog/static/images/red-zone.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/red-zone.svg diff --git a/blog/static/images/xmm-overwrite.svg b/blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/xmm-overwrite.svg similarity index 100% rename from blog/static/images/xmm-overwrite.svg rename to blog/content/extra/handling-exceptions-with-naked-fns/03-returning-from-exceptions/xmm-overwrite.svg diff --git a/blog/static/images/gdb-tui-screenshot.png b/blog/content/extra/set-up-gdb/gdb-tui-screenshot.png similarity index 100% rename from blog/static/images/gdb-tui-screenshot.png rename to blog/content/extra/set-up-gdb/gdb-tui-screenshot.png diff --git a/blog/content/extra/set-up-gdb.md b/blog/content/extra/set-up-gdb/index.md similarity index 98% rename from blog/content/extra/set-up-gdb.md rename to blog/content/extra/set-up-gdb/index.md index 2f8f6197..f8b08e3f 100644 --- a/blog/content/extra/set-up-gdb.md +++ b/blog/content/extra/set-up-gdb/index.md @@ -68,7 +68,7 @@ After connecting to QEMU, you can use various gdb commands to control execution - `print` or `p`: Prints the value of a variable. You can use Cs `*` and `&` operators. To print in hexadecimal, use `p/x`. - `tui enable`: Enables the text user interface, which provides a graphical interface (see below). To disable it again, run `tui disable`. -![gdb text user interface](images/gdb-tui-screenshot.png) +![gdb text user interface](gdb-tui-screenshot.png) Of course there are many more commands. Feel free to send a PR if you think this list is missing something important. For a more complete GDB overview, check out [Beej's Quick Guide][bggdb] or the [website for Harvard's CS161 course][CS161]. diff --git a/blog/content/posts/01-multiboot-kernel/index.md b/blog/content/posts/01-multiboot-kernel/index.md index 7dab5adb..fb98baf7 100644 --- a/blog/content/posts/01-multiboot-kernel/index.md +++ b/blog/content/posts/01-multiboot-kernel/index.md @@ -232,7 +232,7 @@ Now it's time to boot our OS. We will use [QEMU]: ``` qemu-system-x86_64 -cdrom os.iso ``` -![qemu output](/images/qemu-ok.png) +![qemu output](qemu-ok.png) Notice the green `OK` in the upper left corner. If it does not work for you, take a look at the comment section. diff --git a/blog/static/images/qemu-ok.png b/blog/content/posts/01-multiboot-kernel/qemu-ok.png similarity index 100% rename from blog/static/images/qemu-ok.png rename to blog/content/posts/01-multiboot-kernel/qemu-ok.png diff --git a/blog/static/images/X86_Paging_64bit.svg b/blog/content/posts/02-entering-longmode/X86_Paging_64bit.svg similarity index 100% rename from blog/static/images/X86_Paging_64bit.svg rename to blog/content/posts/02-entering-longmode/X86_Paging_64bit.svg diff --git a/blog/content/posts/02-entering-longmode/index.md b/blog/content/posts/02-entering-longmode/index.md index a7c4b970..cabba10b 100644 --- a/blog/content/posts/02-entering-longmode/index.md +++ b/blog/content/posts/02-entering-longmode/index.md @@ -215,7 +215,7 @@ As I don't like these names, I will call them P4, P3, P2, and P1 from now on. Each page table contains 512 entries and one entry is 8 bytes, so they fit exactly in one page (`512*8 = 4096`). To translate a virtual address to a physical address the CPU[^hardware_lookup] will do the following[^virtual_physical_translation_source]: -![translation of virtual to physical addresses in 64 bit mode](/images/X86_Paging_64bit.svg) +![translation of virtual to physical addresses in 64 bit mode](X86_Paging_64bit.svg) 1. Get the address of the P4 table from the CR3 register 2. Use bits 39-47 (9 bits) as an index into P4 (`2^9 = 512 = number of entries`) diff --git a/blog/content/posts/03-set-up-rust/index.md b/blog/content/posts/03-set-up-rust/index.md index 96ee4560..2f7098b2 100644 --- a/blog/content/posts/03-set-up-rust/index.md +++ b/blog/content/posts/03-set-up-rust/index.md @@ -151,7 +151,7 @@ The [red zone] is an optimization of the [System V ABI] that allows functions to [red zone]: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64#the-red-zone [System V ABI]: http://wiki.osdev.org/System_V_ABI -![stack frame with red zone](images/red-zone.svg) +![stack frame with red zone](red-zone.svg) The image shows the stack frame of a function with `n` local variables. On function entry, the stack pointer is adjusted to make room on the stack for the local variables. @@ -159,7 +159,7 @@ The red zone is defined as the 128 bytes below the adjusted stack pointer. The f However, this optimization leads to huge problems with exceptions or hardware interrupts. Let's assume that an exception occurs while a function uses the red zone: -![red zone overwritten by exception handler](images/red-zone-overwrite.svg) +![red zone overwritten by exception handler](red-zone-overwrite.svg) The CPU and the exception handler overwrite the data in red zone. But this data is still needed by the interrupted function. So the function won't work correctly anymore when we return from the exception handler. This might lead to strange bugs that [take weeks to debug]. diff --git a/blog/content/posts/03-set-up-rust/red-zone-overwrite.svg b/blog/content/posts/03-set-up-rust/red-zone-overwrite.svg new file mode 100644 index 00000000..d3be0805 --- /dev/null +++ b/blog/content/posts/03-set-up-rust/red-zone-overwrite.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + Old Stack Pointer + + + + + + + + + + + + + + + + + + + Return Address + + + + + + + + + + Local Variable 1 + + + + + + + Local Variables 2..n + + + + + + + + + Red Zone + + + 128 bytes + + + + + + + Exception Stack Frame + + + + + + + Register Backup + + + + + + + Handler Function Stack Frame + + + + + + + + + New Stack Pointer + + diff --git a/blog/content/posts/03-set-up-rust/red-zone.svg b/blog/content/posts/03-set-up-rust/red-zone.svg new file mode 100644 index 00000000..c5194ca8 --- /dev/null +++ b/blog/content/posts/03-set-up-rust/red-zone.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + Stack Pointer + + + + + + + + + + + + + + + + + + + Return Address + + + + + + + + + + Local Variable 1 + + + + + + + Local Variables 2..n + + + + + + + + + Red Zone + + + 128 bytes + + diff --git a/blog/static/images/fixed-println-deadlock.png b/blog/content/posts/04-printing-to-screen/fixed-println-deadlock.png similarity index 100% rename from blog/static/images/fixed-println-deadlock.png rename to blog/content/posts/04-printing-to-screen/fixed-println-deadlock.png diff --git a/blog/content/posts/04-printing-to-screen/index.md b/blog/content/posts/04-printing-to-screen/index.md index e52d1c17..01258bec 100644 --- a/blog/content/posts/04-printing-to-screen/index.md +++ b/blog/content/posts/04-printing-to-screen/index.md @@ -259,7 +259,7 @@ It just creates a new Writer that points to the VGA buffer at `0xb8000`. Then it [byte character]: https://doc.rust-lang.org/reference.html#characters-and-strings -![QEMU output with a green `H` in the lower left corner](images/vga-H-lower-left.png) +![QEMU output with a green `H` in the lower left corner](vga-H-lower-left.png) ### Volatile We just saw that our `H` was printed correctly. However, it might not work with future Rust compilers that optimize more aggressively. @@ -583,7 +583,7 @@ Since we imported the macros at crate level, they are available in all modules a As expected, we now see a _“Hello World!”_ on a cleared screen: -![QEMU printing “Hello World!” on a cleared screen](images/vga-hello-world.png) +![QEMU printing “Hello World!” on a cleared screen](vga-hello-world.png) ### Deadlocks Whenever we use locks, we must be careful to not accidentally introduce _deadlocks_. A [deadlock] occurs when a thread/program waits for a lock that will never be released. Normally, this happens when multiple threads access multiple locks. For example, when thread A holds lock 1 and tries to acquire lock 2 and -- at the same time -- thread B holds lock 2 and tries to acquire lock 1. @@ -633,7 +633,7 @@ Now the macro only evaluates the arguments (through `format_args!`) and passes t Thus, we fixed the deadlock: -![QEMU printing “inner” and then “outer”](images/fixed-println-deadlock.png) +![QEMU printing “inner” and then “outer”](fixed-println-deadlock.png) We see that both “inner” and “outer” are printed. diff --git a/blog/static/images/vga-H-lower-left.png b/blog/content/posts/04-printing-to-screen/vga-H-lower-left.png similarity index 100% rename from blog/static/images/vga-H-lower-left.png rename to blog/content/posts/04-printing-to-screen/vga-H-lower-left.png diff --git a/blog/static/images/vga-hello-world.png b/blog/content/posts/04-printing-to-screen/vga-hello-world.png similarity index 100% rename from blog/static/images/vga-hello-world.png rename to blog/content/posts/04-printing-to-screen/vga-hello-world.png diff --git a/blog/content/posts/05-allocating-frames/index.md b/blog/content/posts/05-allocating-frames/index.md index 3a7a67bd..f2122e19 100644 --- a/blog/content/posts/05-allocating-frames/index.md +++ b/blog/content/posts/05-allocating-frames/index.md @@ -177,7 +177,7 @@ These lines are taken from the default linker script of `ld`, which can be obtai Now there are only 12 sections left and we get a much more useful output: -![qemu output](/images/qemu-memory-areas-and-kernel-sections.png) +![qemu output](qemu-memory-areas-and-kernel-sections.png) If you like, you can compare this output to the `objdump -h build/kernel-x86_64.bin` output. You will see that the start addresses and sizes match exactly for each section. The sections with flags `0x0` are mostly debug sections, so they don't need to be loaded. And the last few sections of the QEMU output aren't in the `objdump` output because they are special sections such as string tables. diff --git a/blog/static/images/qemu-memory-areas-and-kernel-sections.png b/blog/content/posts/05-allocating-frames/qemu-memory-areas-and-kernel-sections.png similarity index 100% rename from blog/static/images/qemu-memory-areas-and-kernel-sections.png rename to blog/content/posts/05-allocating-frames/qemu-memory-areas-and-kernel-sections.png diff --git a/blog/content/posts/06-page-tables/X86_Paging_64bit.svg b/blog/content/posts/06-page-tables/X86_Paging_64bit.svg new file mode 100644 index 00000000..1a8ba15d --- /dev/null +++ b/blog/content/posts/06-page-tables/X86_Paging_64bit.svg @@ -0,0 +1,929 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CR3 register + 32 + 39 + 40 + 47 + + + + + 0 + 8 + 16 + 24 + 31 + 15 + 7 + 23 + + ... + + + ... + + + 4K memory page + + + P2 entry + + ... + + + ... + + P2 table + + ... + + + ... + + P3 entry + + P3 table + P1 entry + + ... + + + ... + + P1 table + + ... + + + ... + + + P4 entry + + + + P4 table + + 9 + 9 + + 9 + 9 + 12 + + + + diff --git a/blog/content/posts/06-page-tables/index.md b/blog/content/posts/06-page-tables/index.md index 64aaa587..87be5fa3 100644 --- a/blog/content/posts/06-page-tables/index.md +++ b/blog/content/posts/06-page-tables/index.md @@ -21,7 +21,7 @@ _Paging_ is a memory management scheme that separates virtual and physical memor The x86 architecture uses a 4-level page table in 64-bit mode. A virtual address has the following structure: -![structure of a virtual address on x86](/images/x86_address_structure.svg) +![structure of a virtual address on x86](x86_address_structure.svg) The bits 48–63 are so-called _sign extension_ bits and must be copies of bit 47. The following 36 bits define the page table indexes (9 bits per table) and the last 12 bits specify the offset in the 4KiB page. @@ -29,7 +29,7 @@ Each table has 2^9 = 512 entries and each entry is 8 byte. Thus a page table fit To translate an address, the CPU reads the P4 address from the CR3 register. Then it uses the indexes to walk the tables: -![translation of virtual to physical addresses in 64 bit mode](/images/X86_Paging_64bit.svg) +![translation of virtual to physical addresses in 64 bit mode](X86_Paging_64bit.svg) The P4 entry points to a P3 table, where the next 9 bits of the address are used to select an entry. The P3 entry then points to a P2 table and the P2 entry points to a P1 table. The P1 entry, which is specified through bits 12–20, finally points to the physical frame. @@ -233,19 +233,19 @@ We will solve the problem in another way using a trick called _recursive mapping ### Recursive Mapping The trick is to map the P4 table recursively: The last entry doesn't point to a P3 table, but to the P4 table itself. We can use this entry to remove a translation level so that we land on a page table instead. For example, we can “loop” once to access a P1 table: -![access P1 table through recursive paging](/images/recursive_mapping_access_p1.svg) +![access P1 table through recursive paging](recursive_mapping_access_p1.svg) By selecting the 511th P4 entry, which points points to the P4 table itself, the P4 table is used as the P3 table. Similarly, the P3 table is used as a P2 table and the P2 table is treated like a P1 table. Thus the P1 table becomes the target page and can be accessed through the offset. It's also possible to access P2 tables by looping twice. And if we select the 511th entry three times, we can access and modify P3 tables: -![access P3 table through recursive paging](/images/recursive_mapping_access_p3.svg) +![access P3 table through recursive paging](recursive_mapping_access_p3.svg) So we just need to specify the desired P3 table in the address through the P1 index. By choosing the 511th entry multiple times, we stay on the P4 table until the address's P1 index becomes the actual P4 index. To access the P4 table itself, we loop once more and thus never leave the frame: -![access P4 table through recursive paging](/images/recursive_mapping_access_p4.svg) +![access P4 table through recursive paging](recursive_mapping_access_p4.svg) So we can access and modify page tables of all levels by just setting one P4 entry once. Most work is done by the CPU, we just the recursive entry to remove one or more translation levels. It may seem a bit strange at first, but it's a clean and simple solution once you wrapped your head around it. diff --git a/blog/static/images/recursive_mapping_access_p1.svg b/blog/content/posts/06-page-tables/recursive_mapping_access_p1.svg similarity index 100% rename from blog/static/images/recursive_mapping_access_p1.svg rename to blog/content/posts/06-page-tables/recursive_mapping_access_p1.svg diff --git a/blog/static/images/recursive_mapping_access_p3.svg b/blog/content/posts/06-page-tables/recursive_mapping_access_p3.svg similarity index 100% rename from blog/static/images/recursive_mapping_access_p3.svg rename to blog/content/posts/06-page-tables/recursive_mapping_access_p3.svg diff --git a/blog/static/images/recursive_mapping_access_p4.svg b/blog/content/posts/06-page-tables/recursive_mapping_access_p4.svg similarity index 100% rename from blog/static/images/recursive_mapping_access_p4.svg rename to blog/content/posts/06-page-tables/recursive_mapping_access_p4.svg diff --git a/blog/static/images/x86_address_structure.svg b/blog/content/posts/06-page-tables/x86_address_structure.svg similarity index 100% rename from blog/static/images/x86_address_structure.svg rename to blog/content/posts/06-page-tables/x86_address_structure.svg diff --git a/blog/static/images/cyclic_mapping_inactive_tables.svg b/blog/content/posts/07-remap-the-kernel/cyclic_mapping_inactive_tables.svg similarity index 100% rename from blog/static/images/cyclic_mapping_inactive_tables.svg rename to blog/content/posts/07-remap-the-kernel/cyclic_mapping_inactive_tables.svg diff --git a/blog/content/posts/07-remap-the-kernel/index.md b/blog/content/posts/07-remap-the-kernel/index.md index 2939003e..6bd7998b 100644 --- a/blog/content/posts/07-remap-the-kernel/index.md +++ b/blog/content/posts/07-remap-the-kernel/index.md @@ -329,11 +329,11 @@ Recursive mapping works by mapping the last P4 entry to the P4 table itself. Thu For example, accessing a P3 table requires lopping three times: -![access active P3 table through recursive mapping](/images/recursive_mapping_access_p3.svg) +![access active P3 table through recursive mapping](recursive_mapping_access_p3.svg) We can use the same mechanism to access inactive tables. The trick is to change the recursive mapping of the active P4 table to point to the inactive P4 table: -![access inactive P3 table through recursive mapping](/images/recursive_mapping_access_p3_inactive_table.svg) +![access inactive P3 table through recursive mapping](recursive_mapping_access_p3_inactive_table.svg) Now the inactive table can be accessed exactly as the active table, even the magic addresses are the same. This allows us to use the `ActivePageTable` interface and the existing mapping methods for inactive tables, too. Note that everything besides the recursive mapping continues to work exactly as before since we've never changed the active table in the CPU. @@ -364,7 +364,7 @@ It overwrites the 511th P4 entry and points it to the inactive table frame. Then Now that the recursive mapping points to the given inactive table, we execute the closure in the new context. The closure can call all active table methods such as `translate` or `map_to`. It could even call `with` again and chain another inactive table! Wait… that would not work: -![access inactive P3 table through recursive mapping](/images/recursive_mapping_access_p1_invalid_chaining.svg) +![access inactive P3 table through recursive mapping](recursive_mapping_access_p1_invalid_chaining.svg) Here the closure called `with` again and thus changed the recursive mapping of the inactive table to point to a second inactive table. Now we want to modify the P1 of the _second_ inactive table, but instead we land on the P1 of the _first_ inactive table since we never follow the pointer to the second table. Only when modifying the P2, P3, or P4 table we really access the second inactive table. This inconsistency would break our mapping functions completely. @@ -460,13 +460,13 @@ Why is it unsafe? Because reading the CR3 register leads to a CPU exception if t Now that we have a backup of the original P4 frame, we need a way to restore it after the closure has run. So we need to somehow modify the 511th entry of the original P4 frame, which is still the active table in the CPU. But we can't access it because the recursive mapping now points to the inactive table: -![it's not possible to access the original P4 through recursive mapping anymore](/images/recursive_mapping_inactive_table_scheme.svg) +![it's not possible to access the original P4 through recursive mapping anymore](recursive_mapping_inactive_table_scheme.svg) It's just not possible to access the active P4 entry in 4 steps, so we can't reach it through the 4-level page table. We could try to overwrite the recursive mapping of the _inactive_ P4 table and point it back to the original P4 frame: -![cyclic map active and inactive P4 tables](/images/cyclic_mapping_inactive_tables.svg) +![cyclic map active and inactive P4 tables](cyclic_mapping_inactive_tables.svg) Now we can reach the active P4 entry in 4 steps and could restore the original mapping in the active table. But this hack has a drawback: The inactive table is now invalid since it is no longer recursive mapped. We would need to fix it by using a temporary page again (as above). diff --git a/blog/static/images/recursive_mapping_access_p1_invalid_chaining.svg b/blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p1_invalid_chaining.svg similarity index 100% rename from blog/static/images/recursive_mapping_access_p1_invalid_chaining.svg rename to blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p1_invalid_chaining.svg diff --git a/blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3.svg b/blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3.svg new file mode 100644 index 00000000..33a7fb64 --- /dev/null +++ b/blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3.svg @@ -0,0 +1,819 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 32 + 39 + 40 + 47 + + + + + 0 + 8 + 16 + 24 + 31 + 15 + 7 + 23 + + + + ... + + + P4 table + + 9 + 9 + + 9 + 9 + 12 + + + + + + + P4 entry + Recursive + Mapping + + + ... + + 111111111111111111111111111 + + + + P3 table + + + P4 entry + + + + 511 + + + + + + + + + + + + diff --git a/blog/static/images/recursive_mapping_access_p3_inactive_table.svg b/blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3_inactive_table.svg similarity index 100% rename from blog/static/images/recursive_mapping_access_p3_inactive_table.svg rename to blog/content/posts/07-remap-the-kernel/recursive_mapping_access_p3_inactive_table.svg diff --git a/blog/static/images/recursive_mapping_inactive_table_scheme.svg b/blog/content/posts/07-remap-the-kernel/recursive_mapping_inactive_table_scheme.svg similarity index 100% rename from blog/static/images/recursive_mapping_inactive_table_scheme.svg rename to blog/content/posts/07-remap-the-kernel/recursive_mapping_inactive_table_scheme.svg diff --git a/blog/static/images/linked-list-allocator/allocate.svg b/blog/content/posts/08-kernel-heap/allocate.svg similarity index 100% rename from blog/static/images/linked-list-allocator/allocate.svg rename to blog/content/posts/08-kernel-heap/allocate.svg diff --git a/blog/static/images/linked-list-allocator/deallocate.svg b/blog/content/posts/08-kernel-heap/deallocate.svg similarity index 100% rename from blog/static/images/linked-list-allocator/deallocate.svg rename to blog/content/posts/08-kernel-heap/deallocate.svg diff --git a/blog/content/posts/08-kernel-heap/index.md b/blog/content/posts/08-kernel-heap/index.md index 5b16f8ae..ed2fcf5d 100644 --- a/blog/content/posts/08-kernel-heap/index.md +++ b/blog/content/posts/08-kernel-heap/index.md @@ -622,25 +622,25 @@ We will choose another solution with different tradoffs. It's not clearly “bet A freed memory block is not used anymore and no one needs the stored information. It is still mapped to a virtual address and backed by a physical page. So we just store the information about the freed block _in the block itself_. We keep a pointer to the first block and store a pointer to the next block in each block. Thus, we create a single linked list: -![Linked List Allocator](/images/linked-list-allocator/overview.svg) +![Linked List Allocator](overview.svg) In the following, we call a freed block a _hole_. Each hole stores its size and a pointer to the next hole. If a hole is larger than needed, we leave the remaining memory unused. By storing a pointer to the first hole, we are able to traverse the complete list. #### Initialization When the heap is created, all of its memory is unused. Thus, it forms a single large hole: -![Heap Initialization](/images/linked-list-allocator/initialization.svg) +![Heap Initialization](initialization.svg) The optional pointer to the next hole is set to `None`. #### Allocation In order to allocate a block of memory, we need to find a hole that satisfies the size and alignment requirements. If the found hole is larger than required, we split it into two smaller holes. For example, when we allocate a 24 byte block right after initialization, we split the single hole into a hole of size 24 and a hole with the remaining size: -![split hole](/images/linked-list-allocator/split-hole.svg) +![split hole](split-hole.svg) Then we use the new 24 byte hole to perform the allocation: -![24 bytes allocated](/images/linked-list-allocator/allocate.svg) +![24 bytes allocated](allocate.svg) To find a suitable hole, we can use several search strategies: @@ -659,11 +659,11 @@ However, both best fit and worst fit have a significant problem: They need to sc #### Deallocation To deallocate a block of memory, we can just insert its corresponding hole somewhere into the list. However, we need to merge adjacent holes. Otherwise, we are unable to reuse the freed memory for larger allocations. For example: -![deallocate memory, which leads to adjacent holes](/images/linked-list-allocator/deallocate.svg) +![deallocate memory, which leads to adjacent holes](deallocate.svg) In order to use these adjacent holes for a large allocation, we need to merge them to a single large hole first: -![merge adjacent holes and allocate large block](/images/linked-list-allocator/merge-holes-and-allocate.svg) +![merge adjacent holes and allocate large block](merge-holes-and-allocate.svg) The easiest way to ensure that adjacent holes are always merged, is to keep the hole list sorted by address. Thus, we only need to check the predecessor and the successor in the list when we free a memory block. If they are adjacent to the freed block, we merge the corresponding holes. Else, we insert the freed block as a new hole at the correct position. @@ -677,7 +677,7 @@ The detailed implementation would go beyond the scope of this post, since it con I created the [linked_list_allocator] crate to handle all of these cases. It consists of a [Heap struct] that provides an `allocate_first_fit` and a `deallocate` method. If you are interested in the implementation details, check out the [source code][linked_list_allocator source]. [linked_list_allocator]: https://crates.io/crates/linked_list_allocator -[Heap struct]: http://phil-opp.github.io/linked-list-allocator/linked_list_allocator/struct.Heap.html +[Heap struct]: http://phil-opp.github.io/linked_list_allocator/struct.Heap.html [linked_list_allocator source]: https://github.com/phil-opp/linked-list-allocator So we just need to implement Rust's allocation modules and integrate it into our kernel. We start by creating a new `hole_list_allocator` crate inside the `libs` directory: diff --git a/blog/static/images/linked-list-allocator/initialization.svg b/blog/content/posts/08-kernel-heap/initialization.svg similarity index 100% rename from blog/static/images/linked-list-allocator/initialization.svg rename to blog/content/posts/08-kernel-heap/initialization.svg diff --git a/blog/static/images/linked-list-allocator/merge-holes-and-allocate.svg b/blog/content/posts/08-kernel-heap/merge-holes-and-allocate.svg similarity index 100% rename from blog/static/images/linked-list-allocator/merge-holes-and-allocate.svg rename to blog/content/posts/08-kernel-heap/merge-holes-and-allocate.svg diff --git a/blog/static/images/linked-list-allocator/overview.svg b/blog/content/posts/08-kernel-heap/overview.svg similarity index 100% rename from blog/static/images/linked-list-allocator/overview.svg rename to blog/content/posts/08-kernel-heap/overview.svg diff --git a/blog/static/images/linked-list-allocator/split-hole.svg b/blog/content/posts/08-kernel-heap/split-hole.svg similarity index 100% rename from blog/static/images/linked-list-allocator/split-hole.svg rename to blog/content/posts/08-kernel-heap/split-hole.svg diff --git a/blog/content/posts/09-handling-exceptions/exception-stack-frame.svg b/blog/content/posts/09-handling-exceptions/exception-stack-frame.svg new file mode 100644 index 00000000..c099dff5 --- /dev/null +++ b/blog/content/posts/09-handling-exceptions/exception-stack-frame.svg @@ -0,0 +1,2 @@ + +
New Stack
Pointer
[Not supported by viewer]
Stack Alignment (variable)
Stack Alignment (variable)
Stack Segment (SS)
Stack Segment (SS)
Stack Frame of the Handler Function
Stack Frame of the Handler Function
Stack Pointer (RSP)
Stack Pointer (RSP)
RFLAGS
RFLAGS
Code Segment (CS)
Code Segment (CS)
Instruction Pointer (RIP)
Instruction Pointer (RIP)
Error Code (optional)
Error Code (optional)
8 Byte
8 Byte
Old Stack
Pointer
[Not supported by viewer]
\ No newline at end of file diff --git a/blog/content/posts/09-handling-exceptions/function-stack-frame.svg b/blog/content/posts/09-handling-exceptions/function-stack-frame.svg new file mode 100644 index 00000000..1316739a --- /dev/null +++ b/blog/content/posts/09-handling-exceptions/function-stack-frame.svg @@ -0,0 +1,2 @@ + +
Stack Frame of the Handler Function
Stack Frame of the Handler Function
Return Address
Return Address
8 Byte
8 Byte
Old Stack Pointer
[Not supported by viewer]
New Stack Pointer
[Not supported by viewer]
\ No newline at end of file diff --git a/blog/content/posts/09-handling-exceptions/index.md b/blog/content/posts/09-handling-exceptions/index.md index 4b9eb95c..75cbb001 100644 --- a/blog/content/posts/09-handling-exceptions/index.md +++ b/blog/content/posts/09-handling-exceptions/index.md @@ -160,7 +160,7 @@ Since we don't know when an exception occurs, we can't backup any registers befo ### The Exception Stack Frame On a normal function call (using the `call` instruction), the CPU pushes the return address before jumping to the target function. On function return (using the `ret` instruction), the CPU pops this return address and jumps to it. So the stack frame of a normal function call looks like this: -![function stack frame](images/function-stack-frame.svg) +![function stack frame](function-stack-frame.svg) For exception and interrupt handlers, however, pushing a return address would not suffice, since interrupt handlers often run in a different context (stack pointer, CPU flags, etc.). Instead, the CPU performs the following steps when an interrupt occurs: @@ -176,7 +176,7 @@ For exception and interrupt handlers, however, pushing a return address would no So the _exception stack frame_ looks like this: -![exception stack frame](images/exception-stack-frame.svg) +![exception stack frame](exception-stack-frame.svg) 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. @@ -406,7 +406,7 @@ pub extern "C" fn rust_main(...) { When we run it in QEMU now (using `make run`), we see the following: -![QEMU printing `EXCEPTION: BREAKPOINT` and the exception stack frame](images/qemu-breakpoint-exception.png) +![QEMU printing `EXCEPTION: BREAKPOINT` and the exception stack frame](qemu-breakpoint-exception.png) It works! The CPU successfully invokes our breakpoint handler, which prints the message, and then returns back to the `rust_main` function, where the `It did not crash!` message is printed. diff --git a/blog/static/images/qemu-breakpoint-exception.png b/blog/content/posts/09-handling-exceptions/qemu-breakpoint-exception.png similarity index 100% rename from blog/static/images/qemu-breakpoint-exception.png rename to blog/content/posts/09-handling-exceptions/qemu-breakpoint-exception.png diff --git a/blog/static/images/boot-loop.gif b/blog/content/posts/10-double-faults/boot-loop.gif similarity index 100% rename from blog/static/images/boot-loop.gif rename to blog/content/posts/10-double-faults/boot-loop.gif diff --git a/blog/content/posts/10-double-faults/index.md b/blog/content/posts/10-double-faults/index.md index 5daea505..543c7712 100644 --- a/blog/content/posts/10-double-faults/index.md +++ b/blog/content/posts/10-double-faults/index.md @@ -49,7 +49,7 @@ We try to write to address `0xdeadbeaf`, but the corresponding page is not prese When we start our kernel now, we see that it enters an endless boot loop: -![boot loop](images/boot-loop.gif) +![boot loop](boot-loop.gif) The reason for the boot loop is the following: @@ -90,7 +90,7 @@ Our handler prints a short error message and dumps the exception stack frame. Th When we start our kernel now, we should see that the double fault handler is invoked: -![QEMU printing `EXCEPTION: DOUBLE FAULT` and the exception stack frame](images/qemu-catch-double-fault.png) +![QEMU printing `EXCEPTION: DOUBLE FAULT` and the exception stack frame](qemu-catch-double-fault.png) It worked! Here is what happens this time: @@ -906,7 +906,7 @@ The `set_stack_index` method is unsafe because the the caller must ensure that t That's it! Now the CPU should switch to the double fault stack whenever a double fault occurs. Thus, we are able to catch _all_ double faults, including kernel stack overflows: -![QEMU printing `EXCEPTION: DOUBLE FAULT` and a dump of the exception stack frame](images/qemu-double-fault-on-stack-overflow.png) +![QEMU printing `EXCEPTION: DOUBLE FAULT` and a dump of the exception stack frame](qemu-double-fault-on-stack-overflow.png) From now on we should never see a triple fault again! diff --git a/blog/static/images/qemu-catch-double-fault.png b/blog/content/posts/10-double-faults/qemu-catch-double-fault.png similarity index 100% rename from blog/static/images/qemu-catch-double-fault.png rename to blog/content/posts/10-double-faults/qemu-catch-double-fault.png diff --git a/blog/static/images/qemu-double-fault-on-stack-overflow.png b/blog/content/posts/10-double-faults/qemu-double-fault-on-stack-overflow.png similarity index 100% rename from blog/static/images/qemu-double-fault-on-stack-overflow.png rename to blog/content/posts/10-double-faults/qemu-double-fault-on-stack-overflow.png diff --git a/blog/static/images/qemu-empty-IST-entry.png b/blog/static/images/qemu-empty-IST-entry.png deleted file mode 100644 index 0276c6ccbe74d97b812d0cd1ade7cec7a8ba1a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17532 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