From 25d26b3b9312be8d4fcff5cc6a067e634217c665 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 27 Jul 2023 15:49:51 +0200 Subject: [PATCH] Arch-independent x86-modeled page fault handler. --- src/arch/x86_64/interrupt/exception.rs | 19 ++++--- src/arch/x86_64/interrupt/handler.rs | 16 ++++++ src/arch/x86_64/paging/mod.rs | 51 ------------------ src/memory/mod.rs | 72 +++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 61 deletions(-) diff --git a/src/arch/x86_64/interrupt/exception.rs b/src/arch/x86_64/interrupt/exception.rs index 8f7837b0a1..bd83aa90a4 100644 --- a/src/arch/x86_64/interrupt/exception.rs +++ b/src/arch/x86_64/interrupt/exception.rs @@ -1,6 +1,7 @@ use rmm::TableKind; use x86::irq::PageFaultError; +use crate::memory::GenericPfFlags; use crate::{ interrupt::stack_trace, paging::VirtualAddress, @@ -136,15 +137,17 @@ interrupt_error!(protection, |stack| { interrupt_error!(page, |stack| { let cr2 = VirtualAddress::new(unsafe { x86::controlregs::cr2() }); - let flags = PageFaultError::from_bits_truncate(stack.code as u32); + let arch_flags = PageFaultError::from_bits_truncate(stack.code as u32); + let mut generic_flags = GenericPfFlags::empty(); - if crate::paging::page_fault_handler(&mut stack.inner, flags, cr2).is_err() { - println!("Page fault: {:>016X}", cr2.data()); - println!(" Present: {}", flags.contains(PageFaultError::P)); - println!(" Write: {}", flags.contains(PageFaultError::WR)); - println!(" User: {}", flags.contains(PageFaultError::US)); - println!(" Reserved write: {}", flags.contains(PageFaultError::RSVD)); - println!(" Instruction fetch: {}", flags.contains(PageFaultError::ID)); + generic_flags.set(GenericPfFlags::PRESENT, arch_flags.contains(PageFaultError::P)); + generic_flags.set(GenericPfFlags::INVOLVED_WRITE, arch_flags.contains(PageFaultError::WR)); + generic_flags.set(GenericPfFlags::USER_NOT_SUPERVISOR, arch_flags.contains(PageFaultError::US)); + generic_flags.set(GenericPfFlags::INVL, arch_flags.contains(PageFaultError::RSVD)); + generic_flags.set(GenericPfFlags::INSTR_NOT_DATA, arch_flags.contains(PageFaultError::ID)); + + if crate::memory::page_fault_handler(&mut stack.inner, generic_flags, cr2).is_err() { + println!("Page fault: {:>016X} {:#?}", cr2.data(), arch_flags); stack.dump(); stack_trace(); ksignal(SIGSEGV); diff --git a/src/arch/x86_64/interrupt/handler.rs b/src/arch/x86_64/interrupt/handler.rs index b7962a27ed..1af333b0af 100644 --- a/src/arch/x86_64/interrupt/handler.rs +++ b/src/arch/x86_64/interrupt/handler.rs @@ -1,5 +1,6 @@ use core::mem; +use crate::memory::ArchIntCtx; use crate::syscall::IntRegisters; use super::super::flags::*; @@ -550,3 +551,18 @@ macro_rules! interrupt_error { } }; } + +impl ArchIntCtx for InterruptStack { + fn ip(&self) -> usize { + self.iret.rip + } + fn recover_and_efault(&mut self) { + // We were inside a usercopy function that failed. This is handled by setting rax to a + // nonzero value, and emulating the ret instruction. + self.scratch.rax = 1; + let ret_addr = unsafe { (self.iret.rsp as *const usize).read() }; + self.iret.rsp += 8; + self.iret.rip = ret_addr; + self.iret.rflags &= !(1 << 18); + } +} diff --git a/src/arch/x86_64/paging/mod.rs b/src/arch/x86_64/paging/mod.rs index 51881f0fbc..686f2593fd 100644 --- a/src/arch/x86_64/paging/mod.rs +++ b/src/arch/x86_64/paging/mod.rs @@ -141,54 +141,3 @@ pub fn round_down_pages(number: usize) -> usize { pub fn round_up_pages(number: usize) -> usize { number.next_multiple_of(PAGE_SIZE) } -pub struct Segv; - -pub fn page_fault_handler(stack: &mut InterruptStack, code: PageFaultError, faulting_address: VirtualAddress) -> Result<(), Segv> { - let faulting_page = Page::containing_address(faulting_address); - - let usercopy_region = __usercopy_start()..__usercopy_end(); - - // TODO: Most likely not necessary, but maybe also check that cr2 is not too close to USER_END. - let address_is_user = faulting_address.kind() == TableKind::User; - - let invalid_page_tables = code.contains(PageFaultError::RSVD); - let caused_by_user = code.contains(PageFaultError::US); - let caused_by_kernel = !caused_by_user; - let caused_by_write = code.contains(PageFaultError::WR); - let caused_by_instr_fetch = code.contains(PageFaultError::ID); - let is_usercopy = usercopy_region.contains(&{ stack.iret.rip }); - - let mode = match (caused_by_write, caused_by_instr_fetch) { - (true, false) => AccessMode::Write, - (false, false) => AccessMode::Read, - (false, true) => AccessMode::InstrFetch, - (true, true) => unreachable!("page fault cannot be caused by both instruction fetch and write"), - }; - - if invalid_page_tables { - // TODO: Better error code than Segv? - return Err(Segv); - } - - if address_is_user && (caused_by_user || is_usercopy) { - match try_correcting_page_tables(faulting_page, mode) { - Ok(()) => return Ok(()), - Err(PfError::Oom) => todo!("oom"), - Err(PfError::Segv | PfError::RecursionLimitExceeded) => (), - Err(PfError::NonfatalInternalError) => todo!(), - } - } - - if address_is_user && caused_by_kernel && mode != AccessMode::InstrFetch && is_usercopy { - // We were inside a usercopy function that failed. This is handled by setting rax to a - // nonzero value, and emulating the ret instruction. - stack.scratch.rax = 1; - let ret_addr = unsafe { (stack.iret.rsp as *const usize).read() }; - stack.iret.rsp += 8; - stack.iret.rip = ret_addr; - stack.iret.rflags &= !(1 << 18); - return Ok(()); - } - - Err(Segv) -} diff --git a/src/memory/mod.rs b/src/memory/mod.rs index ef09de1fad..7b0e6eb4a1 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -7,7 +7,10 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use crate::arch::rmm::LockedAllocator; use crate::common::try_box_slice_new; -use crate::context::memory::init_frame; +use crate::context; +use crate::context::memory::{init_frame, AccessMode, try_correcting_page_tables, PfError}; +use crate::kernel_executable_offsets::{__usercopy_start, __usercopy_end}; +use crate::paging::Page; pub use crate::paging::{PAGE_SIZE, PhysicalAddress}; use crate::rmm::areas; @@ -15,7 +18,7 @@ use alloc::boxed::Box; use alloc::vec::Vec; use rmm::{ FrameAllocator, - FrameCount, + FrameCount, VirtualAddress, TableKind, }; use spin::RwLock; use crate::syscall::flag::{PartialAllocStrategy, PhysallocFlags}; @@ -385,3 +388,68 @@ pub fn get_page_info(frame: Frame) -> Option<&'static PageInfo> { .map(|(base, section)| PageInfoHandle { section, idx: frame.offset_from(*base) }) */ } +pub struct Segv; + +bitflags! { + /// Arch-generic page fault flags, modeled after x86's error code. + /// + /// This may change when arch-specific features are utilized better. + pub struct GenericPfFlags: u32 { + const PRESENT = 1 << 0; + const INVOLVED_WRITE = 1 << 1; + const USER_NOT_SUPERVISOR = 1 << 2; + const INSTR_NOT_DATA = 1 << 3; + // "reserved bits" on x86 + const INVL = 1 << 31; + } +} + +pub trait ArchIntCtx { + fn ip(&self) -> usize; + fn recover_and_efault(&mut self); +} + +pub fn page_fault_handler(stack: &mut impl ArchIntCtx, code: GenericPfFlags, faulting_address: VirtualAddress) -> Result<(), Segv> { + let faulting_page = Page::containing_address(faulting_address); + + let usercopy_region = __usercopy_start()..__usercopy_end(); + + // TODO: Most likely not necessary, but maybe also check that the faulting address is not too + // close to USER_END. + let address_is_user = faulting_address.kind() == TableKind::User; + + let invalid_page_tables = code.contains(GenericPfFlags::INVL); + let caused_by_user = code.contains(GenericPfFlags::USER_NOT_SUPERVISOR); + let caused_by_kernel = !caused_by_user; + let caused_by_write = code.contains(GenericPfFlags::INVOLVED_WRITE); + let caused_by_instr_fetch = code.contains(GenericPfFlags::INSTR_NOT_DATA); + let is_usercopy = usercopy_region.contains(&stack.ip()); + + let mode = match (caused_by_write, caused_by_instr_fetch) { + (true, false) => AccessMode::Write, + (false, false) => AccessMode::Read, + (false, true) => AccessMode::InstrFetch, + (true, true) => unreachable!("page fault cannot be caused by both instruction fetch and write"), + }; + + if invalid_page_tables { + // TODO: Better error code than Segv? + return Err(Segv); + } + + if address_is_user && (caused_by_user || is_usercopy) { + match context::memory::try_correcting_page_tables(faulting_page, mode) { + Ok(()) => return Ok(()), + Err(PfError::Oom) => todo!("oom"), + Err(PfError::Segv | PfError::RecursionLimitExceeded) => (), + Err(PfError::NonfatalInternalError) => todo!(), + } + } + + if address_is_user && caused_by_kernel && mode != AccessMode::InstrFetch && is_usercopy { + stack.recover_and_efault(); + return Ok(()); + } + + Err(Segv) +}