Arch-independent x86-modeled page fault handler.

This commit is contained in:
4lDO2
2023-07-27 15:49:51 +02:00
parent 7a3b453fbb
commit 25d26b3b93
4 changed files with 97 additions and 61 deletions
+11 -8
View File
@@ -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);
+16
View File
@@ -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);
}
}
-51
View File
@@ -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)
}
+70 -2
View File
@@ -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)
}