From e1a90fe3abbb2ec28f07d5d016b4b587658460d9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 3 Aug 2016 16:42:47 +0200 Subject: [PATCH] Final improvements --- .../2016-08-03-better-exception-messages.md | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/blog/post/2016-08-03-better-exception-messages.md b/blog/post/2016-08-03-better-exception-messages.md index 2a1edd0b..dbbda37a 100644 --- a/blog/post/2016-08-03-better-exception-messages.md +++ b/blog/post/2016-08-03-better-exception-messages.md @@ -83,7 +83,7 @@ Let's try it by executing `make run`: ![qemu printing an ExceptionStackFrame with strange values](images/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 `1129552`. So what's going on here? +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? ### Debugging It seems like we somehow got the pointer wrong. The `ExceptionStackFrame` type and our inline assembly seem correct, so something must be modifying `rsp` before we load it into `stack_frame`. @@ -159,6 +159,8 @@ We can't use Rust code in naked functions, but we still want to use Rust in our Our new two-stage exception handler looks like this: ```rust +// in src/interrupts/mod.rs + #[naked] extern "C" fn divide_by_zero_wrapper() -> ! { unsafe { @@ -291,22 +293,20 @@ ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000 EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 [...] CPU Reset (CPU 0) -RAX=000000000011fac8 RBX=0000000000000800 RCX=1d1d1d1d1d1d1d1d -RDX=0000000000000000 RSI=0000000000119d70 RDI=000000000011fb58 -RBP=000000000011fb48 RSP=000000000011f9c8 -R8 =0000000000000000 R9 =0000000000000100 R10=000000000011f500 -R11=000000000011f800 R12=0000000000000000 R13=0000000000000000 -R14=0000000000000000 R15=0000000000000000 -RIP=000000000010db23 RFL=00210002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 +RAX=0000000000118cb8 RBX=0000000000000800 RCX=1d1d1d1d1d1d1d1d RDX=0..0000000 +RSI=0000000000112cd0 RDI=0000000000118d38 RBP=0000000000118d28 RSP=0..0118c68 +R8 =0000000000000000 R9 =0000000000000100 R10=0000000000118700 R11=0..0118a00 +R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0..0000000 +RIP=000000000010cf08 RFL=00210002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0 [...] ``` -The first two resets occur while the CPU is still in 32-bit mode (`EAX` instead of `RAX`), so we ignore them. The third reset is the interesting one, because it occurs in 64-bit mode. The register dump tells us that the instruction pointer (`rip`) was `0x10db23` just before the reset. This might be the address of the instruction that caused the triple fault. +The first two resets occur while the CPU is still in 32-bit mode (`EAX` instead of `RAX`), so we ignore them. The third reset is the interesting one, because it occurs in 64-bit mode. The register dump tells us that the instruction pointer (`rip`) was `0x10cf08` just before the reset. This might be the address of the instruction that caused the triple fault. We can find the corresponding instruction by disassembling our kernel: -```shell -objdump -d build/kernel-x86_64.bin | grep "10db23:" - 10db23: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp) +``` +objdump -d build/kernel-x86_64.bin | grep "10cf08:" + 10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp) ``` The [movaps] instruction is an [SSE] instruction that moves aligned 128bit values. It can fail for a number of reasons: @@ -331,10 +331,10 @@ Let's look at the above `-d cpu_reset` dump again and check the value of `rbp`: ``` CPU Reset (CPU 0) RAX=[...] RBX=[...] RCX=[...] RDX=[...] -RSI=[...] RDI=[...] RBP=000000000011fb48 RSP=[...] +RSI=[...] RDI=[...] RBP=0000000000118d28 RSP=[...] ... ``` -`RBP` is `0x11fb48`, which is _not_ 16-byte aligned. So this is the reason for the triple fault. (It seems like QEMU doesn't check the alignment for `movaps`, but real hardware of course does.) +`RBP` is `0x118d28`, which is _not_ 16-byte aligned. So this is the reason for the triple fault. (It seems like QEMU doesn't check the alignment for `movaps`, but real hardware of course does.) But how did we end up with a misaligned `rbp` register? @@ -342,22 +342,20 @@ But how did we end up with a misaligned `rbp` register? In order to solve this mystery, we need to look at the disassembly of the preceding code: ``` -> objdump -d build/kernel-x86_64.bin | grep -B12 "10db23:" -000000000010daf0 <_ZN7blog_os10interrupts12divide_by_zero_handler1735E>: - 10daf0: 55 push %rbp - 10daf1: 48 89 e5 mov %rsp,%rbp - 10daf4: 48 81 ec 80 01 00 00 sub $0x180,%rsp - 10dafb: 48 8d 45 80 lea -0x80(%rbp),%rax - 10daff: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx - 10db06: 1d 1d 1d - 10db09: 48 89 4d 88 mov %rcx,-0x78(%rbp) - 10db0d: 48 89 4d 80 mov %rcx,-0x80(%rbp) - 10db11: 48 89 8d f0 fe ff ff mov %rcx,-0x110(%rbp) - 10db18: 48 89 7d f8 mov %rdi,-0x8(%rbp) - 10db1c: 0f 10 05 8d b5 00 00 movups 0xb58d(%rip),%xmm0 - 10db23: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp) +> objdump -d build/kernel-x86_64.bin | grep -B10 "10cf08:" +000000000010cee0 <_ZN7blog_os10interrupts22divide_by_zero_handler17hE>: + 10cee0: 55 push %rbp + 10cee1: 48 89 e5 mov %rsp,%rbp + 10cee4: 48 81 ec c0 00 00 00 sub $0xc0,%rsp + 10ceeb: 48 8d 45 90 lea -0x70(%rbp),%rax + 10ceef: 48 b9 1d 1d 1d 1d 1d movabs $0x1d1d1d1d1d1d1d1d,%rcx + 10cef6: 1d 1d 1d + 10cef9: 48 89 4d 90 mov %rcx,-0x70(%rbp) + 10cefd: 48 89 7d f8 mov %rdi,-0x8(%rbp) + 10cf01: 0f 10 05 a8 51 00 00 movups 0x51a8(%rip),%xmm0 + 10cf08: 0f 29 45 b0 movaps %xmm0,-0x50(%rbp) ``` -At the last line (`0x10db23`) we have the `movaps` instruction, which caused the triple fault. The exception occurs inside our `divide_by_zero_handler` function. We see that `rbp` is loaded with the value of `rsp` at the beginning. The `rbp` register holds the so-called _base pointer_, which points to the beginning of the stack frame. It is used in the rest of the function to address variables and other values on the stack. +At the last line we have the `movaps` instruction, which caused the triple fault. The exception occurs inside our `divide_by_zero_handler` function. We see that `rbp` is loaded with the value of `rsp` at the beginning (at `0x10cee1`). The `rbp` register holds the so-called _base pointer_, which points to the beginning of the stack frame. It is used in the rest of the function to address variables and other values on the stack. The base pointer is initialized directly from the stack pointer (`rsp`) after pushing the old base pointer. There is no special alignment code, so the compiler blindly assumes that `(rsp - 8)`[^fn-rsp-8] is always 16-byte aligned. This seems to be wrong in our case. But why does the compiler assume this? @@ -497,6 +495,8 @@ When a divide-by-zero exception occurs, we immediately know the reason: Someone Since the CPU pushes an additional error code, the stack frame is different and our `handler!` macro is not applicable. Therefore we create a new `handler_with_error_code!` macro for them: ```rust +// in src/interrupts/mod.rs + macro_rules! handler_with_error_code { ($name: ident) => {{ #[naked] @@ -522,6 +522,8 @@ The difference to the `handler!` macro is the additional error code argument. Th 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, error_code: u64) -> ! {