mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Test the handler_with_error_code macro in a different way
We now take stack_frame as a immutable reference and we shouldn't write to a &. So the old test example (page faults as breakpoints) becomes even more hacky. Instead, we're now deliberately entering an endless page fault loop to check our iretq logic.
This commit is contained in:
@@ -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`:
|
||||

|
||||
|
||||
{{< 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: &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 >}}<!--end*-->
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user