Arch-independent x86-modeled page fault handler.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user