diff --git a/blog/post/10-better-exception-messages.md b/blog/post/10-better-exception-messages.md index b670ce5a..e424ddfb 100644 --- a/blog/post/10-better-exception-messages.md +++ b/blog/post/10-better-exception-messages.md @@ -55,12 +55,11 @@ Now we need a way to find the memory address of this stack frame. When we look a // in src/interrupts/mod.rs extern "C" fn divide_by_zero_handler() -> ! { - let stack_frame: *const ExceptionStackFrame; + let stack_frame: &ExceptionStackFrame; unsafe { asm!("mov $0, rsp" : "=r"(stack_frame) ::: "intel"); - println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", - *stack_frame); - }; + } + println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame); loop {} } ``` @@ -169,7 +168,7 @@ extern "C" fn divide_by_zero_wrapper() -> ! { } } -extern "C" fn divide_by_zero_handler(stack_frame: *const ExceptionStackFrame) +extern "C" fn divide_by_zero_handler(stack_frame: &ExceptionStackFrame) -> ! { println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", @@ -433,7 +432,7 @@ macro_rules! handler { sub rsp, 8 // align the stack pointer call $0" :: "i"($name as extern "C" fn( - *const ExceptionStackFrame) -> !) + &ExceptionStackFrame) -> !) : "rdi" : "intel"); ::core::intrinsics::unreachable(); } @@ -442,7 +441,7 @@ macro_rules! handler { }} } ``` -The macro takes a single Rust identifier (`ident`) as argument and expands to a `{}` block (hence the double braces). The block defines a new wrapper function that calls the function `$name` and passes a pointer to the exception stack frame. Note that we're fixing the argument type to `*const ExceptionStackFrame`. If we used a `_` like before, the passed function could accept an arbitrary argument, which would lead to ugly bugs at runtime. +The macro takes a single Rust identifier (`ident`) as argument and expands to a `{}` block (hence the double braces). The block defines a new wrapper function that calls the function `$name` and passes a pointer to the exception stack frame. Note that we're fixing the argument type to `&ExceptionStackFrame`. If we used a `_` like before, the passed function could accept an arbitrary argument, which would lead to ugly bugs at runtime. Now we can remove the `divide_by_zero_wrapper` and use our new `handler!` macro instead: @@ -475,7 +474,7 @@ lazy_static! { }; } -extern "C" fn invalid_opcode_handler(stack_frame: *const ExceptionStackFrame) +extern "C" fn invalid_opcode_handler(stack_frame: &ExceptionStackFrame) -> ! { let stack_frame = unsafe { &*stack_frame }; @@ -527,7 +526,7 @@ macro_rules! handler_with_error_code { sub rsp, 8 // align the stack pointer call $0" :: "i"($name as extern "C" fn( - *const ExceptionStackFrame, u64) -> !) + &ExceptionStackFrame, u64) -> !) : "rdi","rsi" : "intel"); ::core::intrinsics::unreachable(); } @@ -544,7 +543,7 @@ Let's write a page fault handler which analyzes and prints the error code: ```rust // in src/interrupts/mod.rs -extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, +extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, error_code: u64) -> ! { println!( @@ -626,7 +625,7 @@ bitflags! { Now we can improve our page fault error message by using the new `PageFaultErrorCode`. We also print the accessed memory address: ```rust -extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, +extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, error_code: u64) -> ! { use x86::controlregs; diff --git a/blog/post/11-returning-from-exceptions.md b/blog/post/11-returning-from-exceptions.md index 501c3973..8e0813aa 100644 --- a/blog/post/11-returning-from-exceptions.md +++ b/blog/post/11-returning-from-exceptions.md @@ -40,7 +40,7 @@ Let's start by defining a handler function for the breakpoint exception: ```rust // in src/interrupts/mod.rs -extern "C" fn breakpoint_handler(stack_frame: *const ExceptionStackFrame) -> ! +extern "C" fn breakpoint_handler(stack_frame: &ExceptionStackFrame) -> ! { let stack_frame = unsafe { &*stack_frame }; println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}", @@ -157,7 +157,7 @@ macro_rules! handler { sub rsp, 8 // align the stack pointer call $0" :: "i"($name as extern "C" fn( - *const ExceptionStackFrame)) // no longer diverging + &ExceptionStackFrame)) // no longer diverging : "rdi" : "intel", "volatile"); // new @@ -180,16 +180,16 @@ We've changed the handler function type, so we need to adjust our existing excep // in src/interrupts/mod.rs extern "C" fn divide_by_zero_handler( -- stack_frame: *const ExceptionStackFrame) -> ! {...} -+ stack_frame: *const ExceptionStackFrame) {...} +- stack_frame: &ExceptionStackFrame) -> ! {...} ++ stack_frame: &ExceptionStackFrame) {...} extern "C" fn invalid_opcode_handler( -- stack_frame: *const ExceptionStackFrame) -> ! {...} -+ stack_frame: *const ExceptionStackFrame) {...} +- stack_frame: &ExceptionStackFrame) -> ! {...} ++ stack_frame: &ExceptionStackFrame) {...} extern "C" fn breakpoint_handler( -- stack_frame: *const ExceptionStackFrame) -> ! { -+ stack_frame: *const ExceptionStackFrame) { +- stack_frame: &ExceptionStackFrame) -> ! { ++ stack_frame: &ExceptionStackFrame) { println!(...); - loop {} } @@ -386,7 +386,7 @@ macro_rules! handler { // sub rsp, 8 (stack is aligned already) call $0" :: "i"($name as - extern "C" fn(*const ExceptionStackFrame)) + extern "C" fn(&ExceptionStackFrame)) : "rdi" : "intel", "volatile"); restore_scratch_registers!(); @@ -742,7 +742,7 @@ macro_rules! handler_with_error_code { sub rsp, 8 // align the stack pointer call $0" :: "i"($name as extern "C" fn( - *const ExceptionStackFrame, u64)) + &ExceptionStackFrame, u64)) : "rdi","rsi" : "intel"); asm!("iretq" :::: "intel", "volatile"); ::core::intrinsics::unreachable(); @@ -760,7 +760,7 @@ Now we can make our `page_fault_handler` non-diverging: ```diff // in src/interrupts/mod.rs - extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, + extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, - error_code: u64) -> ! { ... } + error_code: u64) { ... } ``` @@ -783,7 +783,7 @@ macro_rules! handler_with_error_code { call $0 add rsp, 8 // undo stack pointer alignment " :: "i"($name as extern "C" fn( - *const ExceptionStackFrame, u64)) + &ExceptionStackFrame, u64)) : "rdi","rsi" : "intel"); restore_scratch_registers!(); asm!("iretq" :::: "intel", "volatile"); @@ -816,7 +816,7 @@ macro_rules! handler_with_error_code { call $0 add rsp, 8 // undo stack pointer alignment " :: "i"($name as extern "C" fn( - *const ExceptionStackFrame, u64)) + &ExceptionStackFrame, u64)) : "rdi","rsi" : "intel"); restore_scratch_registers!(); asm!("add rsp, 8 // pop error code @@ -864,52 +864,21 @@ ExceptionStackFrame { ``` The error code should still be `CAUSED_BY_WRITE` and the exception stack frame values should also be correct (e.g. `code_segment` should be 8 and `stack_segment` should be 16). -### Page Faults as Breakpoints -We didn't test returns from the page fault handler yet. In order to test our `iretq` logic, we _temporary_ define accesses to `0xdeadbeaf` as legal. They should behave exactly like the breakpoint exception: Print an error message and then continue with the next instruction. +#### Returning from Page Faults +Let's see what happens if we comment out the trailing `loop` in our page fault handler: -Therefore we update our `page_fault_handler`: +![QEMU printing the same page fault message again and again](images/qemu-page-fault-return.png) -{{< highlight rust "hl_lines=10 11 12 13 14 15 16" >}} -// in src/interrupts/mod.rs +We see that the same error message is printed over and over again. Here is what happens: -extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, - error_code: u64) -{ - use x86::controlregs; - println!(...); +- The CPU executes `rust_main` and tries to access `0xdeadbeaf`. This causes a page fault. +- The page fault handler prints an error message and returns without fixing the cause of the exception (`0xdeadbeaf` is still unaccessible). +- The CPU restarts the instruction that caused the page fault and thus tries to access `0xdeadbeaf` again. Of course, this causes a page fault again. +- The page fault handler prints the error message and returns. - // new - unsafe { - if controlregs::cr2() == 0xdeadbeaf { - let stack_frame = &mut *(stack_frame as *mut ExceptionStackFrame); - stack_frame.instruction_pointer += 7; - return; - } - } +… and so on. Thus, our code indefinitely jumps between the page fault handler and the instruction that accesses `0xdeadbeaf`. - loop {} -} -{{< / highlight >}} - -If the accessed memory address is `0xdeadbeaf` (the CPU stores this information in the `cr2` register), we don't `loop` endlessly. Instead we update the stored instruction pointer and return. Remember, the normal behavior when returning from a page fault is to restart the failing instruction. We don't want that, so we manipulate the stored instruction pointer to point to the next instruction. - -In our case, the page fault is caused by the instruction at address `1114753`, which is `0x110281` in hexadecimal. Let's examine this instruction using `objdump`: - -``` -> objdump -d build/kernel-x86_64.bin | grep "110281" -110281: 48 c7 02 2a 00 00 00 movq $0x2a,(%rdx) -``` -It's a `movq` instruction with the 7 byte opcode `48 c7 02 2a 00 00 00`. So the next instruction starts 7 bytes after. - -Thus, we can jump to the next instruction in our `page_fault_handler` by adding `7` to the instruction pointer. _This is a horrible hack_, since the page fault could also be caused by other instructions with different opcode lengths. But it's good enough for a quick test. - -When we execute `make run` now, we should see the _“It did not crash”_ message after the page fault: - -![QEMU showing the the page fault error and the “It did not crash” message](images/qemu-page-fault-as-breakpoint.png) - -Our `iretq` logic seems to work! - -Let's quickly remove the `0xdeadbeaf` hack from our `page_fault_handler` again and pretend that we've never done that :). +This is a good thing! It means that our `iretq` logic is working correctly, since it returns to the correct instruction every time. So our `handler_with_error_code` macro seems to be correct. ## What's next? We are now able to catch exceptions and to return from them. However, there are still exceptions that completely crash our kernel by causing a [triple fault]. In the next post, we will fix this issue by handling a special type of exception: the [double fault]. Thus, we will be able to avoid random reboots in our kernel. diff --git a/src/interrupts/mod.rs b/src/interrupts/mod.rs index da2325b2..f19d02d0 100644 --- a/src/interrupts/mod.rs +++ b/src/interrupts/mod.rs @@ -49,7 +49,7 @@ macro_rules! handler { add rdi, 9*8 // calculate exception stack frame pointer call $0" :: "i"($name as extern "C" fn( - *const ExceptionStackFrame)) + &ExceptionStackFrame)) : "rdi" : "intel"); restore_scratch_registers!(); @@ -74,7 +74,7 @@ macro_rules! handler_with_error_code { call $0 add rsp, 8 // undo stack pointer alignment " :: "i"($name as extern "C" fn( - *const ExceptionStackFrame, u64)) + &ExceptionStackFrame, u64)) : "rdi","rsi" : "intel"); restore_scratch_registers!(); asm!("add rsp, 8 // pop error code @@ -113,24 +113,21 @@ struct ExceptionStackFrame { stack_segment: u64, } -extern "C" fn divide_by_zero_handler(stack_frame: *const ExceptionStackFrame) { - let stack_frame = unsafe { &*stack_frame }; +extern "C" fn divide_by_zero_handler(stack_frame: &ExceptionStackFrame) { println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame); loop {} } -extern "C" fn breakpoint_handler(stack_frame: *const ExceptionStackFrame) { - let stack_frame = unsafe { &*stack_frame }; +extern "C" fn breakpoint_handler(stack_frame: &ExceptionStackFrame) { println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}", stack_frame.instruction_pointer, stack_frame); } -extern "C" fn invalid_opcode_handler(stack_frame: *const ExceptionStackFrame) { - let stack_frame = unsafe { &*stack_frame }; +extern "C" fn invalid_opcode_handler(stack_frame: &ExceptionStackFrame) { println!("\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}", stack_frame.instruction_pointer, - *stack_frame); + stack_frame); loop {} } @@ -144,8 +141,7 @@ bitflags! { } } -extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, error_code: u64) { - let stack_frame = unsafe { &*stack_frame }; +extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, error_code: u64) { use x86::controlregs; println!("\nEXCEPTION: PAGE FAULT while accessing {:#x}\nerror code: \ {:?}\n{:#?}",