mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 14:27:49 +00:00
Merge pull request #252 from phil-opp/stack_frame-reference
Exceptions: Take the ExceptionStackFrame per reference
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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`:
|
||||

|
||||
|
||||
{{< 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 >}}<!--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.
|
||||
|
||||
@@ -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{:#?}",
|
||||
|
||||
Reference in New Issue
Block a user