From effe02bd45c2fe4a058b2606cc50d4bbe2c47ff1 Mon Sep 17 00:00:00 2001 From: jD91mZM2 <me@krake.one> Date: Mon, 1 Jul 2019 22:50:19 +0000 Subject: [PATCH] Remove change I am faaairly certain I did NOT add :O I'm guessing it's some issue after a rebase or something... --- src/arch/x86_64/interrupt/exception.rs | 47 +++- src/arch/x86_64/interrupt/irq.rs | 21 +- src/arch/x86_64/interrupt/syscall.rs | 220 ++++++++-------- src/arch/x86_64/macros.rs | 46 ++++ src/common/mod.rs | 1 + src/common/unique.rs | 33 +++ src/context/context.rs | 16 +- src/context/switch.rs | 2 +- src/lib.rs | 3 + src/ptrace.rs | 208 +++++++++++++++ src/scheme/mod.rs | 24 +- src/scheme/proc.rs | 351 +++++++++++++++++++++++++ src/syscall/debug.rs | 39 +-- src/syscall/driver.rs | 3 +- src/syscall/process.rs | 38 ++- syscall | 2 +- 16 files changed, 877 insertions(+), 177 deletions(-) create mode 100644 src/common/unique.rs create mode 100644 src/ptrace.rs create mode 100644 src/scheme/proc.rs diff --git a/src/arch/x86_64/interrupt/exception.rs b/src/arch/x86_64/interrupt/exception.rs index 864a4c32..7d58efd9 100644 --- a/src/arch/x86_64/interrupt/exception.rs +++ b/src/arch/x86_64/interrupt/exception.rs @@ -1,5 +1,10 @@ -use crate::interrupt::stack_trace; -use crate::syscall::flag::*; +use crate::{ + common::unique::Unique, + context, + interrupt::stack_trace, + ptrace, + syscall::flag::* +}; extern { fn ksignal(signal: usize); @@ -13,9 +18,41 @@ interrupt_stack_p!(divide_by_zero, stack, { }); interrupt_stack!(debug, stack, { - println!("Debug trap"); - stack.dump(); - ksignal(SIGTRAP); + match ptrace::breakpoint_callback_dryrun(true) { + Some(_) => { + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(stack))); + } + } + } + + let had_singlestep = stack.iret.rflags & (1 << 8) == 1 << 8; + stack.set_singlestep(false); + if ptrace::breakpoint_callback(true).is_none() { + // There is no guarantee that this is Some(_) just + // because the dryrun is Some(_). So, if there wasn't + // *actually* any breakpoint, restore the trap flag. + stack.set_singlestep(had_singlestep); + } + + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + context.regs = None; + } + } + }, + None => { + println!("Debug trap"); + stack.dump(); + ksignal(SIGTRAP); + } + } }); interrupt_stack!(non_maskable, stack, { diff --git a/src/arch/x86_64/interrupt/irq.rs b/src/arch/x86_64/interrupt/irq.rs index 6ffeb8c4..fbc5bb87 100644 --- a/src/arch/x86_64/interrupt/irq.rs +++ b/src/arch/x86_64/interrupt/irq.rs @@ -1,5 +1,6 @@ use core::sync::atomic::{AtomicUsize, Ordering}; +use crate::common::unique::Unique; use crate::context; use crate::context::timeout; use crate::device::pic; @@ -40,7 +41,7 @@ pub unsafe fn acknowledge(irq: usize) { } } -interrupt!(pit, { +interrupt_stack!(pit, stack, { // Saves CPU time by not sending IRQ event irq_trigger(0); const PIT_RATE: u64 = 2_250_286; @@ -61,7 +62,25 @@ interrupt!(pit, { timeout::trigger(); if PIT_TICKS.fetch_add(1, Ordering::SeqCst) >= 10 { + { + let contexts = crate::context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + // Make all registers available to e.g. the proc: + // scheme + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(stack))); + } + } + } let _ = context::switch(); + { + let contexts = crate::context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + context.regs = None; + } + } } }); diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs index 099904ff..2a4d68e7 100644 --- a/src/arch/x86_64/interrupt/syscall.rs +++ b/src/arch/x86_64/interrupt/syscall.rs @@ -1,25 +1,74 @@ +use crate::arch::macros::InterruptStack; use crate::arch::{gdt, pti}; -use crate::syscall; +use crate::common::unique::Unique; +use crate::{context, ptrace, syscall}; use x86::shared::msr; pub unsafe fn init() { msr::wrmsr(msr::IA32_STAR, ((gdt::GDT_KERNEL_CODE as u64) << 3) << 32); msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64); - msr::wrmsr(msr::IA32_FMASK, 1 << 9); + msr::wrmsr(msr::IA32_FMASK, 0x0300); // Clear trap flag and interrupt enable msr::wrmsr(msr::IA32_KERNEL_GS_BASE, &gdt::TSS as *const _ as u64); let efer = msr::rdmsr(msr::IA32_EFER); msr::wrmsr(msr::IA32_EFER, efer | 1); } +// Not a function pointer because it somehow messes up the returning +// from clone() (via clone_ret()). Not sure what the problem is. +macro_rules! with_interrupt_stack { + (unsafe fn $wrapped:ident($stack:ident) -> usize $code:block) => { + /// Because of how clones work, we need a function that returns a + /// usize. Here, `inner` will be this function. The child process in a + /// clone will terminate this function with a 0 return value, and it + /// might also have updated the interrupt_stack pointer. + #[inline(never)] + unsafe fn $wrapped(stack: *mut SyscallStack) { + let stack = &mut *stack; + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + if let Some(ref mut kstack) = context.kstack { + context.regs = Some((kstack.as_mut_ptr() as usize, Unique::new_unchecked(&mut stack.interrupt_stack))); + } + } + } + + let is_sysemu = ptrace::breakpoint_callback(false); + if !is_sysemu.unwrap_or(false) { + // If not on a sysemu breakpoint + let $stack = &mut *stack; + $stack.interrupt_stack.scratch.rax = $code; + + if is_sysemu.is_some() { + // Only callback if there was a pre-syscall + // callback too. + ptrace::breakpoint_callback(false); + } + } + + { + let contexts = context::contexts(); + if let Some(context) = contexts.current() { + let mut context = context.write(); + context.regs = None; + } + } + } + } +} + #[naked] pub unsafe extern fn syscall_instruction() { - #[inline(never)] - unsafe fn inner(stack: &mut SyscallStack) -> usize { - let rbp; - asm!("" : "={rbp}"(rbp) : : : "intel", "volatile"); - - syscall::syscall(stack.rax, stack.rdi, stack.rsi, stack.rdx, stack.r10, stack.r8, rbp, stack) + with_interrupt_stack! { + unsafe fn inner(stack) -> usize { + let rbp; + asm!("" : "={rbp}"(rbp) : : : "intel", "volatile"); + + let scratch = &stack.interrupt_stack.scratch; + syscall::syscall(scratch.rax, scratch.rdi, scratch.rsi, scratch.rdx, scratch.r10, scratch.r8, rbp, stack) + } } // Yes, this is magic. No, you don't need to understand @@ -40,76 +89,52 @@ pub unsafe extern fn syscall_instruction() { : : "intel", "volatile"); - // Push scratch registers - asm!("push rax - push rbx - push rcx - push rdx - push rdi - push rsi - push r8 - push r9 - push r10 - push r11 - push fs - mov r11, 0x18 - mov fs, r11" - : : : : "intel", "volatile"); - - // Get reference to stack variables - let rsp: usize; - asm!("" : "={rsp}"(rsp) : : : "intel", "volatile"); - - // Map kernel - pti::map(); - - let a = inner(&mut *(rsp as *mut SyscallStack)); - - // Unmap kernel - pti::unmap(); - - asm!("" : : "{rax}"(a) : : "intel", "volatile"); - - // Interrupt return - asm!("pop fs - pop r11 - pop r10 - pop r9 - pop r8 - pop rsi - pop rdi - pop rdx - pop rcx - pop rbx - add rsp, 8 - iretq" - : : : : "intel", "volatile"); + // Push scratch registers + scratch_push!(); + asm!("push fs + mov r11, 0x18 + mov fs, r11 + push rbx" + : : : : "intel", "volatile"); + + // Get reference to stack variables + let rsp: usize; + asm!("" : "={rsp}"(rsp) : : : "intel", "volatile"); + + // Map kernel + pti::map(); + + inner(rsp as *mut SyscallStack); + + // Unmap kernel + pti::unmap(); + + // Interrupt return + asm!("pop rbx + pop fs" + : : : : "intel", "volatile"); + scratch_pop!(); + asm!("iretq" : : : : "intel", "volatile"); } #[naked] pub unsafe extern fn syscall() { - #[inline(never)] - unsafe fn inner(stack: &mut SyscallStack) -> usize { - let rbp; - asm!("" : "={rbp}"(rbp) : : : "intel", "volatile"); - - syscall::syscall(stack.rax, stack.rbx, stack.rcx, stack.rdx, stack.rsi, stack.rdi, rbp, stack) + with_interrupt_stack! { + unsafe fn inner(stack) -> usize { + let rbp; + asm!("" : "={rbp}"(rbp) : : : "intel", "volatile"); + + let scratch = &stack.interrupt_stack.scratch; + syscall::syscall(scratch.rax, stack.rbx, scratch.rcx, scratch.rdx, scratch.rsi, scratch.rdi, rbp, stack) + } } // Push scratch registers - asm!("push rax - push rbx - push rcx - push rdx - push rdi - push rsi - push r8 - push r9 - push r10 - push r11 - push fs + scratch_push!(); + asm!("push fs mov r11, 0x18 - mov fs, r11" + mov fs, r11 + push rbx" : : : : "intel", "volatile"); // Get reference to stack variables @@ -119,56 +144,41 @@ pub unsafe extern fn syscall() { // Map kernel pti::map(); - let a = inner(&mut *(rsp as *mut SyscallStack)); + inner(rsp as *mut SyscallStack); // Unmap kernel pti::unmap(); - asm!("" : : "{rax}"(a) : : "intel", "volatile"); - // Interrupt return - asm!("pop fs - pop r11 - pop r10 - pop r9 - pop r8 - pop rsi - pop rdi - pop rdx - pop rcx - pop rbx - add rsp, 8 - iretq" - : : : : "intel", "volatile"); + asm!("pop rbx + pop fs" + : : : : "intel", "volatile"); + scratch_pop!(); + asm!("iretq" : : : : "intel", "volatile"); } #[allow(dead_code)] #[repr(packed)] pub struct SyscallStack { - pub fs: usize, - pub r11: usize, - pub r10: usize, - pub r9: usize, - pub r8: usize, - pub rsi: usize, - pub rdi: usize, - pub rdx: usize, - pub rcx: usize, pub rbx: usize, - pub rax: usize, - pub rip: usize, - pub cs: usize, - pub rflags: usize, + pub interrupt_stack: InterruptStack, + // Will only be present if syscall is called from another ring pub rsp: usize, pub ss: usize, } #[naked] -pub unsafe extern fn clone_ret() { - asm!(" - pop rbp - xor rax, rax - " - : : : : "intel", "volatile"); +pub unsafe extern "C" fn clone_ret() { + // The C x86_64 ABI specifies that rbp is pushed to save the old + // call frame. Popping rbp means we're using the parent's call + // frame and thus will not only return from this function but also + // from the function above this one. + // When this is called, the stack should have been + // interrupt->inner->syscall->clone + // then changed to + // interrupt->inner->clone_ret->clone + // so this will return from "inner". + + asm!("pop rbp" : : : : "intel", "volatile"); } diff --git a/src/arch/x86_64/macros.rs b/src/arch/x86_64/macros.rs index d41ee933..da0b0373 100644 --- a/src/arch/x86_64/macros.rs +++ b/src/arch/x86_64/macros.rs @@ -1,3 +1,5 @@ +use syscall::data::IntRegisters; + /// Print to console #[macro_export] macro_rules! print { @@ -204,6 +206,50 @@ impl InterruptStack { self.scratch.dump(); println!("FS: {:>016X}", { self.fs }); } + /// Saves all registers to a struct used by the proc: + /// scheme to read/write registers. + pub fn save(&self, all: &mut IntRegisters) { + all.fs = self.fs; + all.r11 = self.scratch.r11; + all.r10 = self.scratch.r10; + all.r9 = self.scratch.r9; + all.r8 = self.scratch.r8; + all.rsi = self.scratch.rsi; + all.rdi = self.scratch.rdi; + all.rdx = self.scratch.rdx; + all.rcx = self.scratch.rcx; + all.rax = self.scratch.rax; + all.rip = self.iret.rip; + all.cs = self.iret.cs; + all.eflags = self.iret.rflags; + } + /// Loads all registers from a struct used by the proc: + /// scheme to read/write registers. + pub fn load(&mut self, all: &IntRegisters) { + self.fs = all.fs; + self.scratch.r11 = all.r11; + self.scratch.r10 = all.r10; + self.scratch.r9 = all.r9; + self.scratch.r8 = all.r8; + self.scratch.rsi = all.rsi; + self.scratch.rdi = all.rdi; + self.scratch.rdx = all.rdx; + self.scratch.rcx = all.rcx; + self.scratch.rax = all.rax; + self.iret.rip = all.rip; + self.iret.cs = all.cs; + self.iret.rflags = all.eflags; + } + /// Enables the "Trap Flag" in the FLAGS register, causing the CPU + /// to send a Debug exception after the next instruction. This is + /// used for singlestep in the proc: scheme. + pub fn set_singlestep(&mut self, enabled: bool) { + if enabled { + self.iret.rflags |= 1 << 8; + } else { + self.iret.rflags &= !(1 << 8); + } + } } #[macro_export] diff --git a/src/common/mod.rs b/src/common/mod.rs index 29f6412e..7ad826b3 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,2 +1,3 @@ #[macro_use] pub mod int_like; +pub mod unique; diff --git a/src/common/unique.rs b/src/common/unique.rs new file mode 100644 index 00000000..214f5b53 --- /dev/null +++ b/src/common/unique.rs @@ -0,0 +1,33 @@ +use core::{fmt, ptr::NonNull}; + +/// A small wrapper around NonNull<T> that is Send + Sync, which is +/// only correct if the pointer is never accessed from multiple +/// locations across threads. Which is always, if the pointer is +/// unique. +pub struct Unique<T>(NonNull<T>); + +impl<T> Copy for Unique<T> {} +impl<T> Clone for Unique<T> { + fn clone(&self) -> Self { + *self + } +} +unsafe impl<T> Send for Unique<T> {} +unsafe impl<T> Sync for Unique<T> {} + +impl<T> Unique<T> { + pub fn new(ptr: *mut T) -> Self { + Self(NonNull::new(ptr).unwrap()) + } + pub unsafe fn new_unchecked(ptr: *mut T) -> Self { + Self(NonNull::new_unchecked(ptr)) + } + pub fn as_ptr(&self) -> *mut T { + self.0.as_ptr() + } +} +impl<T> fmt::Debug for Unique<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} diff --git a/src/context/context.rs b/src/context/context.rs index c27f7de3..269b4ee4 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -7,15 +7,16 @@ use core::cmp::Ordering; use core::mem; use spin::Mutex; -use crate::arch::paging::PAGE_SIZE; +use crate::arch::{macros::InterruptStack, paging::PAGE_SIZE}; +use crate::common::unique::Unique; use crate::context::arch; use crate::context::file::FileDescriptor; use crate::context::memory::{Grant, Memory, SharedMemory, Tls}; use crate::ipi::{ipi, IpiKind, IpiTarget}; use crate::scheme::{SchemeNamespace, FileHandle}; +use crate::sync::WaitMap; use crate::syscall::data::SigAction; use crate::syscall::flag::SIG_DFL; -use crate::sync::WaitMap; /// Unique identifier for a context (i.e. `pid`). use ::core::sync::atomic::AtomicUsize; @@ -165,6 +166,15 @@ pub struct Context { pub files: Arc<Mutex<Vec<Option<FileDescriptor>>>>, /// Signal actions pub actions: Arc<Mutex<Vec<(SigAction, usize)>>>, + /// The pointer to the user-space registers, saved after certain + /// interrupts. This pointer is somewhere inside kstack, and the + /// kstack address at the time of creation is the first element in + /// this tuple. + pub regs: Option<(usize, Unique<InterruptStack>)>, + /// A somewhat hacky way to initially stop a context when creating + /// a new instance of the proc: scheme, entirely separate from + /// signals or any other way to restart a process. + pub ptrace_stop: bool } impl Context { @@ -216,6 +226,8 @@ impl Context { }, 0 ); 128])), + regs: None, + ptrace_stop: false } } diff --git a/src/context/switch.rs b/src/context/switch.rs index 7d253657..cf55220f 100644 --- a/src/context/switch.rs +++ b/src/context/switch.rs @@ -55,7 +55,7 @@ unsafe fn update(context: &mut Context, cpu_id: usize) { unsafe fn runnable(context: &Context, cpu_id: usize) -> bool { // Switch to context if it needs to run, is not currently running, and is owned by the current CPU - !context.running && context.status == Status::Runnable && context.cpu_id == Some(cpu_id) + !context.running && !context.ptrace_stop && context.status == Status::Runnable && context.cpu_id == Some(cpu_id) } /// Switch to the next context diff --git a/src/lib.rs b/src/lib.rs index 109ea391..24b265af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,9 @@ pub mod memory; #[cfg(not(any(feature="doc", test)))] pub mod panic; +/// Process tracing +pub mod ptrace; + /// Schemes, filesystem handlers pub mod scheme; diff --git a/src/ptrace.rs b/src/ptrace.rs new file mode 100644 index 00000000..5f8cb276 --- /dev/null +++ b/src/ptrace.rs @@ -0,0 +1,208 @@ +use crate::{ + arch::macros::InterruptStack, + common::unique::Unique, + context::{self, Context, ContextId, Status}, + sync::WaitCondition +}; + +use alloc::{ + boxed::Box, + collections::BTreeMap, + sync::Arc +}; +use spin::{Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use syscall::error::*; + +// ____ _ _ _ +// | __ ) _ __ ___ __ _| | ___ __ ___ (_)_ __ | |_ ___ +// | _ \| '__/ _ \/ _` | |/ / '_ \ / _ \| | '_ \| __/ __| +// | |_) | | | __/ (_| | <| |_) | (_) | | | | | |_\__ \ +// |____/|_| \___|\__,_|_|\_\ .__/ \___/|_|_| |_|\__|___/ +// |_| + +struct Handle { + tracee: Arc<WaitCondition>, + tracer: Arc<WaitCondition>, + reached: bool, + + sysemu: bool, + singlestep: bool +} + +static BREAKPOINTS: Once<RwLock<BTreeMap<ContextId, Handle>>> = Once::new(); + +fn init_breakpoints() -> RwLock<BTreeMap<ContextId, Handle>> { + RwLock::new(BTreeMap::new()) +} +fn breakpoints() -> RwLockReadGuard<'static, BTreeMap<ContextId, Handle>> { + BREAKPOINTS.call_once(init_breakpoints).read() +} +fn breakpoints_mut() -> RwLockWriteGuard<'static, BTreeMap<ContextId, Handle>> { + BREAKPOINTS.call_once(init_breakpoints).write() +} + +fn inner_cont(pid: ContextId) -> Option<Handle> { + // Remove the breakpoint to both save space and also make sure any + // yet unreached but obsolete breakpoints don't stop the program. + let handle = breakpoints_mut().remove(&pid)?; + handle.tracee.notify(); + Some(handle) +} + +/// Continue the process with the specified ID +pub fn cont(pid: ContextId) { + inner_cont(pid); +} + +/// Create a new breakpoint for the specified tracee, optionally with a sysemu flag +pub fn set_breakpoint(pid: ContextId, sysemu: bool, singlestep: bool) { + let (tracee, tracer) = match inner_cont(pid) { + Some(breakpoint) => (breakpoint.tracee, breakpoint.tracer), + None => ( + Arc::new(WaitCondition::new()), + Arc::new(WaitCondition::new()) + ) + }; + + breakpoints_mut().insert(pid, Handle { + tracee, + tracer, + reached: false, + sysemu, + singlestep + }); +} + +/// Wait for the tracee to stop. +/// Note: Don't call while holding any locks, this will switch contexts +pub fn wait_breakpoint(pid: ContextId) -> Result<()> { + let tracer = { + let breakpoints = breakpoints(); + match breakpoints.get(&pid) { + Some(breakpoint) if !breakpoint.reached => Arc::clone(&breakpoint.tracer), + _ => return Ok(()) + } + }; + while !tracer.wait() {} + + let contexts = context::contexts(); + let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; + let context = context.read(); + if let Status::Exited(_) = context.status { + return Err(Error::new(ESRCH)); + } + Ok(()) +} + +/// Returns the same value as breakpoint_callback would do, but +/// doesn't actually perform the action. You should not rely too +/// heavily on this value, as the lock *is* released between this call +/// and another. +pub fn breakpoint_callback_dryrun(singlestep: bool) -> Option<bool> { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); + + let breakpoints = breakpoints(); + let breakpoint = breakpoints.get(&context.id)?; + if breakpoint.singlestep != singlestep { + return None; + } + Some(breakpoint.sysemu) +} + +/// Notify the tracer and await green flag to continue. +/// Note: Don't call while holding any locks, this will switch contexts +pub fn breakpoint_callback(singlestep: bool) -> Option<bool> { + // Can't hold any locks when executing wait() + let (tracee, sysemu) = { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); + + let mut breakpoints = breakpoints_mut(); + let breakpoint = breakpoints.get_mut(&context.id)?; + + // TODO: How should singlesteps interact with syscalls? How + // does Linux handle this? + + // if singlestep && !breakpoint.singlestep { + if breakpoint.singlestep != singlestep { + return None; + } + + breakpoint.tracer.notify(); + // In case no tracer is waiting, make sure the next one gets + // the memo + breakpoint.reached = true; + + ( + Arc::clone(&breakpoint.tracee), + breakpoint.sysemu + ) + }; + + while !tracee.wait() {} + + Some(sysemu) +} + +/// Call when a context is closed to alert any tracers +pub fn close(pid: ContextId) { + { + let breakpoints = breakpoints(); + if let Some(breakpoint) = breakpoints.get(&pid) { + breakpoint.tracer.notify(); + } + } + + breakpoints_mut().remove(&pid); +} + +// ____ _ _ +// | _ \ ___ __ _(_)___| |_ ___ _ __ ___ +// | |_) / _ \/ _` | / __| __/ _ \ '__/ __| +// | _ < __/ (_| | \__ \ || __/ | \__ \ +// |_| \_\___|\__, |_|___/\__\___|_| |___/ +// |___/ + +/// Return the InterruptStack pointer, but relative to the specified +/// stack instead of the original. +pub unsafe fn rebase_regs_ptr( + regs: Option<(usize, Unique<InterruptStack>)>, + kstack: Option<&Box<[u8]>> +) -> Option<*const InterruptStack> { + let (old_base, ptr) = regs?; + let new_base = kstack?.as_ptr() as usize; + Some((ptr.as_ptr() as usize - old_base + new_base) as *const _) +} +/// Return the InterruptStack pointer, but relative to the specified +/// stack instead of the original. +pub unsafe fn rebase_regs_ptr_mut( + regs: Option<(usize, Unique<InterruptStack>)>, + kstack: Option<&mut Box<[u8]>> +) -> Option<*mut InterruptStack> { + let (old_base, ptr) = regs?; + let new_base = kstack?.as_mut_ptr() as usize; + Some((ptr.as_ptr() as usize - old_base + new_base) as *mut _) +} + +/// Return a reference to the InterruptStack struct in memory. If the +/// kernel stack has been backed up by a signal handler, this instead +/// returns the struct inside that memory, as that will later be +/// restored and otherwise undo all your changes. See `update(...)` in +/// context/switch.rs. +pub unsafe fn regs_for(context: &Context) -> Option<&InterruptStack> { + Some(&*match context.ksig { + Some((_, _, ref kstack)) => rebase_regs_ptr(context.regs, kstack.as_ref())?, + None => context.regs?.1.as_ptr() + }) +} + +/// Mutable version of `regs_for` +pub unsafe fn regs_for_mut(context: &mut Context) -> Option<&mut InterruptStack> { + Some(&mut *match context.ksig { + Some((_, _, ref mut kstack)) => rebase_regs_ptr_mut(context.regs, kstack.as_mut())?, + None => context.regs?.1.as_ptr() + }) +} diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index 4cf99583..da5f5cd7 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -22,6 +22,7 @@ use self::irq::IrqScheme; use self::itimer::ITimerScheme; use self::memory::MemoryScheme; use self::pipe::PipeScheme; +use self::proc::ProcScheme; use self::root::RootScheme; use self::sys::SysScheme; use self::time::TimeScheme; @@ -51,6 +52,9 @@ pub mod memory; /// `pipe:` - used internally by the kernel to implement `pipe` pub mod pipe; +/// `proc:` - allows tracing processes and reading/writing their memory +pub mod proc; + /// `:` - allows the creation of userspace schemes, tightly dependent on `user` pub mod root; @@ -128,29 +132,21 @@ impl SchemeList { } /// Initialize the root namespace - #[cfg(not(feature="live"))] fn new_root(&mut self) { // Do common namespace initialization let ns = self.new_ns(); - // Debug, Initfs and IRQ are only available in the root namespace. Pipe is special + // These schemes should only be available on the root self.insert(ns, Box::new(*b"debug"), |scheme_id| Arc::new(Box::new(DebugScheme::new(scheme_id)))).unwrap(); self.insert(ns, Box::new(*b"initfs"), |_| Arc::new(Box::new(InitFsScheme::new()))).unwrap(); self.insert(ns, Box::new(*b"irq"), |scheme_id| Arc::new(Box::new(IrqScheme::new(scheme_id)))).unwrap(); - self.insert(ns, Box::new(*b"pipe"), |scheme_id| Arc::new(Box::new(PipeScheme::new(scheme_id)))).unwrap(); - } + self.insert(ns, Box::new(*b"proc"), |_| Arc::new(Box::new(ProcScheme::new()))).unwrap(); - /// Initialize the root namespace - with live disk - #[cfg(feature="live")] - fn new_root(&mut self) { - // Do common namespace initialization - let ns = self.new_ns(); + #[cfg(feature = "live")] { + self.insert(ns, Box::new(*b"disk/live"), |_| Arc::new(Box::new(self::live::DiskScheme::new()))).unwrap(); + } - // Debug, Disk, Initfs and IRQ are only available in the root namespace. Pipe is special - self.insert(ns, Box::new(*b"debug"), |scheme_id| Arc::new(Box::new(DebugScheme::new(scheme_id)))).unwrap(); - self.insert(ns, Box::new(*b"disk/live"), |_| Arc::new(Box::new(self::live::DiskScheme::new()))).unwrap(); - self.insert(ns, Box::new(*b"initfs"), |_| Arc::new(Box::new(InitFsScheme::new()))).unwrap(); - self.insert(ns, Box::new(*b"irq"), |scheme_id| Arc::new(Box::new(IrqScheme::new(scheme_id)))).unwrap(); + // Pipe is special and needs to be in the root namespace self.insert(ns, Box::new(*b"pipe"), |scheme_id| Arc::new(Box::new(PipeScheme::new(scheme_id)))).unwrap(); } diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs new file mode 100644 index 00000000..6edd4785 --- /dev/null +++ b/src/scheme/proc.rs @@ -0,0 +1,351 @@ +use crate::{ + context::{self, ContextId, Status}, + ptrace +}; + +use alloc::collections::{BTreeMap, BTreeSet}; +use core::{ + cmp, + mem, + slice, + sync::atomic::{AtomicUsize, Ordering} +}; +use spin::{Mutex, RwLock}; +use syscall::{ + data::{IntRegisters, FloatRegisters}, + error::*, + flag::*, + scheme::Scheme +}; + +#[derive(Clone, Copy)] +enum RegsKind { + Float, + Int +} +#[derive(Clone, Copy)] +enum Operation { + Memory, + Regs(RegsKind), + Trace +} + +#[derive(Clone, Copy)] +struct Handle { + flags: usize, + pid: ContextId, + operation: Operation +} + +pub struct ProcScheme { + next_id: AtomicUsize, + handles: RwLock<BTreeMap<usize, Handle>>, + traced: Mutex<BTreeSet<ContextId>> +} + +impl ProcScheme { + pub fn new() -> Self { + Self { + next_id: AtomicUsize::new(0), + handles: RwLock::new(BTreeMap::new()), + traced: Mutex::new(BTreeSet::new()) + } + } +} + +impl Scheme for ProcScheme { + fn open(&self, path: &[u8], flags: usize, uid: u32, gid: u32) -> Result<usize> { + let path = core::str::from_utf8(path).map_err(|_| Error::new(EINVAL))?; + let mut parts = path.splitn(2, '/'); + let pid = parts.next() + .and_then(|s| s.parse().ok()) + .map(ContextId::from) + .ok_or(Error::new(EINVAL))?; + let operation = match parts.next() { + Some("mem") => Operation::Memory, + Some("regs/float") => Operation::Regs(RegsKind::Float), + Some("regs/int") => Operation::Regs(RegsKind::Int), + Some("trace") => Operation::Trace, + _ => return Err(Error::new(EINVAL)) + }; + + let contexts = context::contexts(); + let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; + + { + // TODO: Put better security here? + + let context = context.read(); + if uid != 0 && gid != 0 + && uid != context.euid && gid != context.egid { + return Err(Error::new(EPERM)); + } + } + + if let Operation::Trace = operation { + let mut traced = self.traced.lock(); + + if traced.contains(&pid) { + return Err(Error::new(EBUSY)); + } + traced.insert(pid); + + let mut context = context.write(); + context.ptrace_stop = true; + } + + let id = self.next_id.fetch_add(1, Ordering::SeqCst); + self.handles.write().insert(id, Handle { + flags, + pid, + operation + }); + Ok(id) + } + + /// Using dup for `proc:` simply opens another operation on the same PID + /// ```rust,ignore + /// let trace = syscall::open("proc:1234/trace")?; + /// + /// // let regs = syscall::open("proc:1234/regs/int")?; + /// let regs = syscall::dup(trace, "regs/int")?; + /// ``` + fn dup(&self, old_id: usize, buf: &[u8]) -> Result<usize> { + let handle = { + let handles = self.handles.read(); + *handles.get(&old_id).ok_or(Error::new(EBADF))? + }; + let mut path = format!("{}/", handle.pid.into()).into_bytes(); + path.extend_from_slice(buf); + + let (uid, gid) = { + let contexts = context::contexts(); + let context = contexts.current().ok_or(Error::new(ESRCH))?; + let context = context.read(); + (context.euid, context.egid) + }; + + self.open(&path, handle.flags, uid, gid) + } + + fn read(&self, id: usize, buf: &mut [u8]) -> Result<usize> { + // Can't hold locks during the context switch later when + // waiting for a process to stop running. + let handle = { + let handles = self.handles.read(); + *handles.get(&id).ok_or(Error::new(EBADF))? + }; + + match handle.operation { + Operation::Memory => { + // let contexts = context::contexts(); + // let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; + // let context = context.read(); + + // for grant in &*context.grants.lock() { + // println!("Grant: {} -> {}", grant.start.get(), grant.size); + // } + // unimplemented!(); + return Err(Error::new(EBADF)); + }, + Operation::Regs(kind) => { + union Output { + _float: FloatRegisters, + int: IntRegisters + } + let mut first = true; + let (output, size) = loop { + if !first { + // We've tried this before, so lets wait before retrying + unsafe { context::switch(); } + } + first = false; + + let contexts = context::contexts(); + let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; + let context = context.read(); + + break match kind { + RegsKind::Float => { + // TODO!! + // (Output { float: FloatRegisters::default() }, mem::size_of::<FloatRegisters>()) + return Err(Error::new(EBADF)); + }, + RegsKind::Int => match unsafe { ptrace::regs_for(&context) } { + None => { + // Another CPU is running this process, wait until it's stopped. + continue; + }, + Some(stack) => { + let mut regs = IntRegisters::default(); + + stack.save(&mut regs); + + (Output { int: regs }, mem::size_of::<IntRegisters>()) + } + } + }; + }; + + let bytes = unsafe { + slice::from_raw_parts(&output as *const _ as *const u8, mem::size_of::<Output>()) + }; + let len = cmp::min(buf.len(), size); + buf[..len].copy_from_slice(&bytes[..len]); + + Ok(len) + }, + Operation::Trace => Err(Error::new(EBADF)) + } + } + + fn write(&self, id: usize, buf: &[u8]) -> Result<usize> { + // Can't hold locks during the context switch later when + // waiting for a process to stop running. + let handle = { + let handles = self.handles.read(); + *handles.get(&id).ok_or(Error::new(EBADF))? + }; + + let mut first = true; + match handle.operation { + Operation::Memory => { + // unimplemented!() + return Err(Error::new(EBADF)); + }, + Operation::Regs(kind) => loop { + if !first { + // We've tried this before, so lets wait before retrying + unsafe { context::switch(); } + } + first = false; + + let contexts = context::contexts(); + let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; + let mut context = context.write(); + + break match kind { + RegsKind::Float => { + // TODO!! + unimplemented!(); + }, + RegsKind::Int => match unsafe { ptrace::regs_for_mut(&mut context) } { + None => { + // Another CPU is running this process, wait until it's stopped. + continue; + }, + Some(stack) => { + if buf.len() < mem::size_of::<IntRegisters>() { + return Ok(0); + } + let regs = unsafe { + *(buf as *const _ as *const IntRegisters) + }; + + stack.load(®s); + + Ok(mem::size_of::<IntRegisters>()) + } + } + }; + }, + Operation::Trace => { + if buf.len() < 1 { + return Ok(0); + } + let op = buf[0]; + let sysemu = op & PTRACE_SYSEMU == PTRACE_SYSEMU; + + let mut blocking = handle.flags & O_NONBLOCK != O_NONBLOCK; + let mut wait_breakpoint = false; + let mut singlestep = false; + + match op & PTRACE_OPERATIONMASK { + PTRACE_CONT => { ptrace::cont(handle.pid); }, + PTRACE_SYSCALL | PTRACE_SINGLESTEP => { // <- not a bitwise OR + singlestep = op & PTRACE_OPERATIONMASK == PTRACE_SINGLESTEP; + ptrace::set_breakpoint(handle.pid, sysemu, singlestep); + wait_breakpoint = true; + }, + PTRACE_WAIT => { + wait_breakpoint = true; + blocking = true; + }, + _ => return Err(Error::new(EINVAL)) + } + + let mut first = true; + loop { + if !first { + // We've tried this before, so lets wait before retrying + unsafe { context::switch(); } + } + first = false; + + let contexts = context::contexts(); + let context = contexts.get(handle.pid).ok_or(Error::new(ESRCH))?; + let mut context = context.write(); + if let Status::Exited(_) = context.status { + return Err(Error::new(ESRCH)); + } + + if singlestep { + match unsafe { ptrace::regs_for_mut(&mut context) } { + None => continue, + Some(stack) => stack.set_singlestep(true) + } + } + + context.ptrace_stop = false; + break; + } + + if wait_breakpoint && blocking { + ptrace::wait_breakpoint(handle.pid)?; + } + + Ok(1) + } + } + } + + fn fcntl(&self, id: usize, cmd: usize, arg: usize) -> Result<usize> { + let mut handles = self.handles.write(); + let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + match cmd { + F_SETFL => { handle.flags = arg; Ok(0) }, + F_GETFL => return Ok(handle.flags), + _ => return Err(Error::new(EINVAL)) + } + } + + fn fpath(&self, id: usize, buf: &mut [u8]) -> Result<usize> { + let handles = self.handles.read(); + let handle = handles.get(&id).ok_or(Error::new(EBADF))?; + + let path = format!("proc:{}/{}", handle.pid.into(), match handle.operation { + Operation::Memory => "mem", + Operation::Regs(RegsKind::Float) => "regs/float", + Operation::Regs(RegsKind::Int) => "regs/int", + Operation::Trace => "trace" + }); + + let len = cmp::min(path.len(), buf.len()); + buf[..len].copy_from_slice(&path.as_bytes()[..len]); + + Ok(len) + } + + fn close(&self, id: usize) -> Result<usize> { + let handle = self.handles.write().remove(&id).ok_or(Error::new(EBADF))?; + ptrace::cont(handle.pid); + + let contexts = context::contexts(); + if let Some(context) = contexts.get(handle.pid) { + let mut context = context.write(); + context.ptrace_stop = false; + } + Ok(0) + } +} diff --git a/src/syscall/debug.rs b/src/syscall/debug.rs index 14992986..853380f1 100644 --- a/src/syscall/debug.rs +++ b/src/syscall/debug.rs @@ -1,5 +1,4 @@ -use core::mem; -use core::ops::Range; +use core::{ascii, mem}; use alloc::string::String; use alloc::vec::Vec; @@ -8,47 +7,13 @@ use super::flag::*; use super::number::*; use super::validate::*; -// Copied from std -pub struct EscapeDefault { - range: Range<usize>, - data: [u8; 4], -} - -pub fn escape_default(c: u8) -> EscapeDefault { - let (data, len) = match c { - b'\t' => ([b'\\', b't', 0, 0], 2), - b'\r' => ([b'\\', b'r', 0, 0], 2), - b'\n' => ([b'\\', b'n', 0, 0], 2), - b'\\' => ([b'\\', b'\\', 0, 0], 2), - b'\'' => ([b'\\', b'\'', 0, 0], 2), - b'"' => ([b'\\', b'"', 0, 0], 2), - b'\x20' ... b'\x7e' => ([c, 0, 0, 0], 1), - _ => ([b'\\', b'x', hexify(c >> 4), hexify(c & 0xf)], 4), - }; - - return EscapeDefault { range: (0.. len), data: data }; - - fn hexify(b: u8) -> u8 { - match b { - 0 ... 9 => b'0' + b, - _ => b'a' + b - 10, - } - } -} - -impl Iterator for EscapeDefault { - type Item = u8; - fn next(&mut self) -> Option<u8> { self.range.next().map(|i| self.data[i]) } - fn size_hint(&self) -> (usize, Option<usize>) { self.range.size_hint() } -} - struct ByteStr<'a>(&'a[u8]); impl<'a> ::core::fmt::Debug for ByteStr<'a> { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { write!(f, "\"")?; for i in self.0 { - for ch in escape_default(*i) { + for ch in ascii::escape_default(*i) { write!(f, "{}", ch as char)?; } } diff --git a/src/syscall/driver.rs b/src/syscall/driver.rs index 5672706f..360d3a4d 100644 --- a/src/syscall/driver.rs +++ b/src/syscall/driver.rs @@ -25,7 +25,8 @@ pub fn iopl(level: usize, stack: &mut SyscallStack) -> Result<usize> { return Err(Error::new(EINVAL)); } - stack.rflags = (stack.rflags & !(3 << 12)) | ((level & 3) << 12); + let iret = &mut stack.interrupt_stack.iret; + iret.rflags = (iret.rflags & !(3 << 12)) | ((level & 3) << 12); Ok(0) } diff --git a/src/syscall/process.rs b/src/syscall/process.rs index d1a7298c..7d07c419 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -6,21 +6,21 @@ use core::{intrinsics, mem}; use core::ops::DerefMut; use spin::Mutex; -use crate::memory::allocate_frames; -use crate::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, PAGE_SIZE}; -use crate::paging::entry::EntryFlags; -use crate::paging::mapper::MapperFlushAll; -use crate::paging::temporary_page::TemporaryPage; -use crate::start::usermode; -use crate::interrupt; -use crate::context; -use crate::context::{ContextId, WaitpidKey}; use crate::context::file::FileDescriptor; +use crate::context::{ContextId, WaitpidKey}; +use crate::context; #[cfg(not(feature="doc"))] use crate::elf::{self, program_header}; +use crate::interrupt; use crate::ipi::{ipi, IpiKind, IpiTarget}; +use crate::memory::allocate_frames; +use crate::paging::entry::EntryFlags; +use crate::paging::mapper::MapperFlushAll; +use crate::paging::temporary_page::TemporaryPage; +use crate::paging::{ActivePageTable, InactivePageTable, Page, VirtualAddress, PAGE_SIZE}; +use crate::ptrace; use crate::scheme::FileHandle; -use crate::syscall; +use crate::start::usermode; use crate::syscall::data::{SigAction, Stat}; use crate::syscall::error::*; use crate::syscall::flag::{CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND, CLONE_STACK, @@ -28,6 +28,7 @@ use crate::syscall::flag::{CLONE_VFORK, CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_S SIG_DFL, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, SIGCONT, SIGTERM, WCONTINUED, WNOHANG, WUNTRACED, wifcontinued, wifstopped}; use crate::syscall::validate::{validate_slice, validate_slice_mut}; +use crate::syscall; pub fn brk(address: usize) -> Result<usize> { let contexts = context::contexts(); @@ -128,10 +129,25 @@ pub fn clone(flags: usize, stack_base: usize) -> Result<ContextId> { } if let Some(ref stack) = context.kstack { + // Get the relative offset to the return address of this function + // (base pointer - start of stack) - one offset = stack_base - stack.as_ptr() as usize - mem::size_of::<usize>(); // Add clone ret let mut new_stack = stack.clone(); unsafe { + if let Some(regs) = ptrace::rebase_regs_ptr_mut(context.regs, Some(&mut new_stack)) { + // We'll need to tell the clone that it should + // return 0, but that's it. We don't actually + // clone the registers, because it will then + // become None and be exempt from all kinds of + // ptracing until the current syscall has + // completed. + (*regs).scratch.rax = 0; + } + + // Change the return address of the child + // (previously syscall) to the arch-specific + // clone_ret callback let func_ptr = new_stack.as_mut_ptr().offset(offset as isize); *(func_ptr as *mut usize) = interrupt::syscall::clone_ret as usize; } @@ -1052,6 +1068,8 @@ pub fn exit(status: usize) -> ! { context.id }; + ptrace::close(pid); + // Files must be closed while context is valid so that messages can be passed for (_fd, file_option) in close_files.drain(..).enumerate() { if let Some(file) = file_option { diff --git a/syscall b/syscall index 5cdc240d..f8eda5ce 160000 --- a/syscall +++ b/syscall @@ -1 +1 @@ -Subproject commit 5cdc240d1338b53a1d8b9f652b0cc628317f3109 +Subproject commit f8eda5ce1bd6fe7f276302493ec54a75a7335fd0 -- GitLab