mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37: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
|
// in src/interrupts/mod.rs
|
||||||
|
|
||||||
extern "C" fn divide_by_zero_handler() -> ! {
|
extern "C" fn divide_by_zero_handler() -> ! {
|
||||||
let stack_frame: *const ExceptionStackFrame;
|
let stack_frame: &ExceptionStackFrame;
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("mov $0, rsp" : "=r"(stack_frame) ::: "intel");
|
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 {}
|
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{:#?}",
|
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}",
|
||||||
@@ -433,7 +432,7 @@ macro_rules! handler {
|
|||||||
sub rsp, 8 // align the stack pointer
|
sub rsp, 8 // align the stack pointer
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as extern "C" fn(
|
:: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame) -> !)
|
&ExceptionStackFrame) -> !)
|
||||||
: "rdi" : "intel");
|
: "rdi" : "intel");
|
||||||
::core::intrinsics::unreachable();
|
::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:
|
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 };
|
let stack_frame = unsafe { &*stack_frame };
|
||||||
@@ -527,7 +526,7 @@ macro_rules! handler_with_error_code {
|
|||||||
sub rsp, 8 // align the stack pointer
|
sub rsp, 8 // align the stack pointer
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as extern "C" fn(
|
:: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame, u64) -> !)
|
&ExceptionStackFrame, u64) -> !)
|
||||||
: "rdi","rsi" : "intel");
|
: "rdi","rsi" : "intel");
|
||||||
::core::intrinsics::unreachable();
|
::core::intrinsics::unreachable();
|
||||||
}
|
}
|
||||||
@@ -544,7 +543,7 @@ Let's write a page fault handler which analyzes and prints the error code:
|
|||||||
```rust
|
```rust
|
||||||
// in src/interrupts/mod.rs
|
// 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) -> !
|
||||||
{
|
{
|
||||||
println!(
|
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:
|
Now we can improve our page fault error message by using the new `PageFaultErrorCode`. We also print the accessed memory address:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
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) -> !
|
||||||
{
|
{
|
||||||
use x86::controlregs;
|
use x86::controlregs;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Let's start by defining a handler function for the breakpoint exception:
|
|||||||
```rust
|
```rust
|
||||||
// in src/interrupts/mod.rs
|
// 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 };
|
let stack_frame = unsafe { &*stack_frame };
|
||||||
println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
|
println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
|
||||||
@@ -157,7 +157,7 @@ macro_rules! handler {
|
|||||||
sub rsp, 8 // align the stack pointer
|
sub rsp, 8 // align the stack pointer
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as extern "C" fn(
|
:: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame)) // no longer diverging
|
&ExceptionStackFrame)) // no longer diverging
|
||||||
: "rdi" : "intel", "volatile");
|
: "rdi" : "intel", "volatile");
|
||||||
|
|
||||||
// new
|
// 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
|
// in src/interrupts/mod.rs
|
||||||
|
|
||||||
extern "C" fn divide_by_zero_handler(
|
extern "C" fn divide_by_zero_handler(
|
||||||
- stack_frame: *const ExceptionStackFrame) -> ! {...}
|
- stack_frame: &ExceptionStackFrame) -> ! {...}
|
||||||
+ stack_frame: *const ExceptionStackFrame) {...}
|
+ stack_frame: &ExceptionStackFrame) {...}
|
||||||
|
|
||||||
extern "C" fn invalid_opcode_handler(
|
extern "C" fn invalid_opcode_handler(
|
||||||
- stack_frame: *const ExceptionStackFrame) -> ! {...}
|
- stack_frame: &ExceptionStackFrame) -> ! {...}
|
||||||
+ stack_frame: *const ExceptionStackFrame) {...}
|
+ stack_frame: &ExceptionStackFrame) {...}
|
||||||
|
|
||||||
extern "C" fn breakpoint_handler(
|
extern "C" fn breakpoint_handler(
|
||||||
- stack_frame: *const ExceptionStackFrame) -> ! {
|
- stack_frame: &ExceptionStackFrame) -> ! {
|
||||||
+ stack_frame: *const ExceptionStackFrame) {
|
+ stack_frame: &ExceptionStackFrame) {
|
||||||
println!(...);
|
println!(...);
|
||||||
- loop {}
|
- loop {}
|
||||||
}
|
}
|
||||||
@@ -386,7 +386,7 @@ macro_rules! handler {
|
|||||||
// sub rsp, 8 (stack is aligned already)
|
// sub rsp, 8 (stack is aligned already)
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as
|
:: "i"($name as
|
||||||
extern "C" fn(*const ExceptionStackFrame))
|
extern "C" fn(&ExceptionStackFrame))
|
||||||
: "rdi" : "intel", "volatile");
|
: "rdi" : "intel", "volatile");
|
||||||
|
|
||||||
restore_scratch_registers!();
|
restore_scratch_registers!();
|
||||||
@@ -742,7 +742,7 @@ macro_rules! handler_with_error_code {
|
|||||||
sub rsp, 8 // align the stack pointer
|
sub rsp, 8 // align the stack pointer
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as extern "C" fn(
|
:: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame, u64))
|
&ExceptionStackFrame, u64))
|
||||||
: "rdi","rsi" : "intel");
|
: "rdi","rsi" : "intel");
|
||||||
asm!("iretq" :::: "intel", "volatile");
|
asm!("iretq" :::: "intel", "volatile");
|
||||||
::core::intrinsics::unreachable();
|
::core::intrinsics::unreachable();
|
||||||
@@ -760,7 +760,7 @@ Now we can make our `page_fault_handler` non-diverging:
|
|||||||
```diff
|
```diff
|
||||||
// in src/interrupts/mod.rs
|
// 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) -> ! { ... }
|
||||||
+ error_code: u64) { ... }
|
+ error_code: u64) { ... }
|
||||||
```
|
```
|
||||||
@@ -783,7 +783,7 @@ macro_rules! handler_with_error_code {
|
|||||||
call $0
|
call $0
|
||||||
add rsp, 8 // undo stack pointer alignment
|
add rsp, 8 // undo stack pointer alignment
|
||||||
" :: "i"($name as extern "C" fn(
|
" :: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame, u64))
|
&ExceptionStackFrame, u64))
|
||||||
: "rdi","rsi" : "intel");
|
: "rdi","rsi" : "intel");
|
||||||
restore_scratch_registers!();
|
restore_scratch_registers!();
|
||||||
asm!("iretq" :::: "intel", "volatile");
|
asm!("iretq" :::: "intel", "volatile");
|
||||||
@@ -816,7 +816,7 @@ macro_rules! handler_with_error_code {
|
|||||||
call $0
|
call $0
|
||||||
add rsp, 8 // undo stack pointer alignment
|
add rsp, 8 // undo stack pointer alignment
|
||||||
" :: "i"($name as extern "C" fn(
|
" :: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame, u64))
|
&ExceptionStackFrame, u64))
|
||||||
: "rdi","rsi" : "intel");
|
: "rdi","rsi" : "intel");
|
||||||
restore_scratch_registers!();
|
restore_scratch_registers!();
|
||||||
asm!("add rsp, 8 // pop error code
|
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).
|
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
|
#### Returning from Page Faults
|
||||||
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.
|
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" >}}
|
We see that the same error message is printed over and over again. Here is what happens:
|
||||||
// in src/interrupts/mod.rs
|
|
||||||
|
|
||||||
extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame,
|
- The CPU executes `rust_main` and tries to access `0xdeadbeaf`. This causes a page fault.
|
||||||
error_code: u64)
|
- 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.
|
||||||
use x86::controlregs;
|
- The page fault handler prints the error message and returns.
|
||||||
println!(...);
|
|
||||||
|
|
||||||
// new
|
… and so on. Thus, our code indefinitely jumps between the page fault handler and the instruction that accesses `0xdeadbeaf`.
|
||||||
unsafe {
|
|
||||||
if controlregs::cr2() == 0xdeadbeaf {
|
|
||||||
let stack_frame = &mut *(stack_frame as *mut ExceptionStackFrame);
|
|
||||||
stack_frame.instruction_pointer += 7;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {}
|
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.
|
||||||
}
|
|
||||||
{{< / 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 :).
|
|
||||||
|
|
||||||
## What's next?
|
## 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.
|
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
|
add rdi, 9*8 // calculate exception stack frame pointer
|
||||||
call $0"
|
call $0"
|
||||||
:: "i"($name as extern "C" fn(
|
:: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame))
|
&ExceptionStackFrame))
|
||||||
: "rdi" : "intel");
|
: "rdi" : "intel");
|
||||||
|
|
||||||
restore_scratch_registers!();
|
restore_scratch_registers!();
|
||||||
@@ -74,7 +74,7 @@ macro_rules! handler_with_error_code {
|
|||||||
call $0
|
call $0
|
||||||
add rsp, 8 // undo stack pointer alignment
|
add rsp, 8 // undo stack pointer alignment
|
||||||
" :: "i"($name as extern "C" fn(
|
" :: "i"($name as extern "C" fn(
|
||||||
*const ExceptionStackFrame, u64))
|
&ExceptionStackFrame, u64))
|
||||||
: "rdi","rsi" : "intel");
|
: "rdi","rsi" : "intel");
|
||||||
restore_scratch_registers!();
|
restore_scratch_registers!();
|
||||||
asm!("add rsp, 8 // pop error code
|
asm!("add rsp, 8 // pop error code
|
||||||
@@ -113,24 +113,21 @@ struct ExceptionStackFrame {
|
|||||||
stack_segment: u64,
|
stack_segment: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn divide_by_zero_handler(stack_frame: *const ExceptionStackFrame) {
|
extern "C" fn divide_by_zero_handler(stack_frame: &ExceptionStackFrame) {
|
||||||
let stack_frame = unsafe { &*stack_frame };
|
|
||||||
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame);
|
println!("\nEXCEPTION: DIVIDE BY ZERO\n{:#?}", stack_frame);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
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{:#?}",
|
println!("\nEXCEPTION: BREAKPOINT at {:#x}\n{:#?}",
|
||||||
stack_frame.instruction_pointer,
|
stack_frame.instruction_pointer,
|
||||||
stack_frame);
|
stack_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 };
|
|
||||||
println!("\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}",
|
println!("\nEXCEPTION: INVALID OPCODE at {:#x}\n{:#?}",
|
||||||
stack_frame.instruction_pointer,
|
stack_frame.instruction_pointer,
|
||||||
*stack_frame);
|
stack_frame);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,8 +141,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn page_fault_handler(stack_frame: *const ExceptionStackFrame, error_code: u64) {
|
extern "C" fn page_fault_handler(stack_frame: &ExceptionStackFrame, error_code: u64) {
|
||||||
let stack_frame = unsafe { &*stack_frame };
|
|
||||||
use x86::controlregs;
|
use x86::controlregs;
|
||||||
println!("\nEXCEPTION: PAGE FAULT while accessing {:#x}\nerror code: \
|
println!("\nEXCEPTION: PAGE FAULT while accessing {:#x}\nerror code: \
|
||||||
{:?}\n{:#?}",
|
{:?}\n{:#?}",
|
||||||
|
|||||||
Reference in New Issue
Block a user