From 41b21914bee1fe656f670567fa82230cf84bccb0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 14 Jan 2019 21:50:39 +0100 Subject: [PATCH] The page fault section was moved to the paging-introduction post --- .../posts/10-advanced-paging/index.md | 99 +----------------- .../10-advanced-paging/qemu-page-fault.png | Bin 11599 -> 0 bytes 2 files changed, 1 insertion(+), 98 deletions(-) delete mode 100644 blog/content/second-edition/posts/10-advanced-paging/qemu-page-fault.png diff --git a/blog/content/second-edition/posts/10-advanced-paging/index.md b/blog/content/second-edition/posts/10-advanced-paging/index.md index 5a8eab14..2d764875 100644 --- a/blog/content/second-edition/posts/10-advanced-paging/index.md +++ b/blog/content/second-edition/posts/10-advanced-paging/index.md @@ -21,104 +21,7 @@ In the [previous post] we learned about the principles of paging and how the 4-l [previous post]: ./second-edition/posts/09-paging-introduction/index.md -This makes our kernel much safer, since every memory access that is out of bounds causes a page fault exception instead of writing to random physical memory. The bootloader even set the correct access permissions for each page, which means that only the pages containing code are executable and only data pages are writable. - -### Page Faults - -Let's try to cause a page fault by accessing some memory outside of our kernel! First, we create a page fault handler and register it in our IDT, so that we see a page fault exception instead of a generic [double fault] : - -[double fault]: ./second-edition/posts/07-double-faults/index.md - -```rust -// in src/interrupts.rs - -lazy_static! { - static ref IDT: InterruptDescriptorTable = { - let mut idt = InterruptDescriptorTable::new(); - - […] - - idt.page_fault.set_handler_fn(page_fault_handler); // new - - idt - }; -} - -use x86_64::structures::idt::PageFaultErrorCode; - -extern "x86-interrupt" fn page_fault_handler( - stack_frame: &mut ExceptionStackFrame, - _error_code: PageFaultErrorCode, -) { - use crate::hlt_loop; - use x86_64::registers::control::Cr2; - - println!("EXCEPTION: PAGE FAULT"); - println!("Accessed Address: {:?}", Cr2::read()); - println!("{:#?}", stack_frame); - hlt_loop(); -} -``` - -The [`CR2`] register is automatically set by the CPU on a page fault and contains the accessed virtual address that caused the page fault. We use the [`Cr2::read`] function of the `x86_64` crate to read and print it. Normally the [`PageFaultErrorCode`] type would provide more information about the type of memory access that caused the page fault, but there is currently an [LLVM bug] that passes an invalid error code, so we ignore it for now. We can't continue execution without resolving the page fault, so we enter a [`hlt_loop`] at the end. - -[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2 -[`Cr2::read`]: https://docs.rs/x86_64/0.3.5/x86_64/registers/control/struct.Cr2.html#method.read -[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.3.4/x86_64/structures/idt/struct.PageFaultErrorCode.html -[LLVM bug]: https://github.com/rust-lang/rust/issues/57270 -[`hlt_loop`]: ./second-edition/posts/08-hardware-interrupts/index.md#the - -Now we can try to access some memory outside our kernel: - -```rust -// in src/main.rs - -#[cfg(not(test))] -#[no_mangle] -pub extern "C" fn _start() -> ! { - use blog_os::interrupts::PICS; - - println!("Hello World{}", "!"); - - // set up the IDT first, otherwise we would enter a boot loop instead of - // invoking our page fault handler - blog_os::gdt::init(); - blog_os::interrupts::init_idt(); - unsafe { PICS.lock().initialize() }; - x86_64::instructions::interrupts::enable(); - - // new - let ptr = 0xdeadbeaf as *mut u32; - unsafe { *ptr = 42; } - - println!("It did not crash!"); - blog_os::hlt_loop(); -} -``` - -When we run it, we see that our page fault handler is called: - -![EXCEPTION: Page Fault, Accessed Address: VirtAddr(0xdeadbeaf), ExceptionStackFrame: {…}](qemu-page-fault.png) - -The `CR2` register indeed contains `0xdeadbeaf`, the address that we tried to access. This virtual address has no mapping in the page tables, so a page fault occured. - -We see that the current instruction pointer is `0x20430a`, so we know that this address points to a code page. Code pages are mapped read-only by the bootloader, so reading from this address works but writing causes a page fault. You can try this by changing the `0xdeadbeaf` pointer: - -```rust -// Note: The actual address might be different for you. Use the address that -// your page fault handler reports. -let ptr = 0x20430a as *mut u32; -// read from a code page -> works -unsafe { let x = *ptr; } -// write to a code page -> page fault -unsafe { *ptr = 42; } -``` - -### The Problem - -We just saw that it is a good thing that our kernel already runs on virtual addresses, as it improves safety. However, it also leads to a problem when we try to access the page tables from our kernel: Page tables use physical addresses internally that we can't access directly. - -We experienced that problem already [at the end of the previous post] when we tried to inspect the page tables from our kernel. The next section discusses the problem in detail and provides different approaches to a solution. +The problem that page tables use physical addresses internally, which we can't access directly from our kernel. We experienced that problem already [at the end of the previous post] when we tried to inspect the active page tables. The next section discusses the problem in detail and provides different approaches to a solution. [at the end of the previous post]: ./second-edition/posts/09-paging-introduction/index.md#try-it-out diff --git a/blog/content/second-edition/posts/10-advanced-paging/qemu-page-fault.png b/blog/content/second-edition/posts/10-advanced-paging/qemu-page-fault.png deleted file mode 100644 index 5657759750e232705f15b81b77c75fe66f5eb983..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11599 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 V#e3ZkF)%PNc)I$ztaD0e0szDyCwl+@