diff --git a/src/arch/x86_64/interrupt/exception.rs b/src/arch/x86_64/interrupt/exception.rs index e76f393..bd3f4a4 100644 --- a/src/arch/x86_64/interrupt/exception.rs +++ b/src/arch/x86_64/interrupt/exception.rs @@ -20,9 +20,9 @@ interrupt_stack!(debug, stack, { let guard = ptrace::set_process_regs(stack); - // Disable singlestep before their is a breakpoint, since the - // breakpoint handler might end up setting it again but unless it - // does we want the default to be false. + // Disable singlestep before there is a breakpoint, since the breakpoint + // handler might end up setting it again but unless it does we want the + // default to be false. let had_singlestep = stack.iret.rflags & (1 << 8) == 1 << 8; stack.set_singlestep(false); @@ -48,13 +48,12 @@ interrupt_stack!(non_maskable, stack, { }); interrupt_stack!(breakpoint, stack, { - println!("Breakpoint trap"); - let guard = ptrace::set_process_regs(stack); if ptrace::breakpoint_callback(PTRACE_STOP_BREAKPOINT, None).is_none() { drop(guard); + println!("Breakpoint trap"); stack.dump(); ksignal(SIGTRAP); } diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs index f084f4c..9ddd3c7 100644 --- a/src/arch/x86_64/interrupt/syscall.rs +++ b/src/arch/x86_64/interrupt/syscall.rs @@ -22,10 +22,10 @@ macro_rules! with_interrupt_stack { unsafe fn $wrapped(stack: *mut InterruptStack) { let _guard = ptrace::set_process_regs(stack); - let thumbs_up = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None) + let allowed = ptrace::breakpoint_callback(PTRACE_STOP_PRE_SYSCALL, None) .and_then(|_| ptrace::next_breakpoint().map(|f| !f.contains(PTRACE_FLAG_IGNORE))); - if thumbs_up.unwrap_or(true) { + if allowed.unwrap_or(true) { // If syscall not ignored let $stack = &mut *stack; $stack.scratch.rax = $code; diff --git a/src/ptrace.rs b/src/ptrace.rs index 625cc6a..b55b3f6 100644 --- a/src/ptrace.rs +++ b/src/ptrace.rs @@ -48,15 +48,13 @@ use spin::{Mutex, Once, RwLock, RwLockReadGuard, RwLockWriteGuard}; // |____/ \___||___/___/_|\___/|_| |_|___/ #[derive(Debug)] -struct Session { +pub struct SessionData { breakpoint: Option, events: VecDeque, file_id: usize, - tracee: Arc, - tracer: Arc, } -impl Session { - fn send_event(&mut self, event: PtraceEvent) { +impl SessionData { + fn add_event(&mut self, event: PtraceEvent) { self.events.push_back(event); // Notify nonblocking tracers @@ -64,13 +62,57 @@ impl Session { // If the list of events was previously empty, alert now proc_trigger_event(self.file_id, EVENT_READ); } + } - // Alert blocking tracers - self.tracer.notify(); + /// Override the breakpoint for the specified tracee. Pass `None` to clear + /// breakpoint. + pub fn set_breakpoint(&mut self, flags: Option) { + self.breakpoint = flags.map(|flags| Breakpoint { + reached: false, + flags + }); + } + + /// Used for getting the flags in fevent + pub fn session_fevent_flags(&self) -> EventFlags { + let mut flags = EventFlags::empty(); + + if !self.events.is_empty() { + flags |= EVENT_READ; + } + + flags + } + + /// Poll events, return the amount read. This drains events from the queue. + pub fn recv_events(&mut self, out: &mut [PtraceEvent]) -> usize { + let len = cmp::min(out.len(), self.events.len()); + for (dst, src) in out.iter_mut().zip(self.events.drain(..len)) { + *dst = src; + } + len } } -type SessionMap = BTreeMap; +#[derive(Debug)] +pub struct Session { + pub data: Mutex, + pub tracee: WaitCondition, + pub tracer: WaitCondition, +} +impl Session { + pub fn with_session(pid: ContextId, callback: F) -> Result + where + F: FnOnce(&Session) -> Result, + { + let sessions = sessions(); + let session = sessions.get(&pid).ok_or(Error::new(ENODEV))?; + + callback(session) + } +} + +type SessionMap = BTreeMap>; static SESSIONS: Once> = Once::new(); @@ -84,51 +126,45 @@ fn sessions_mut() -> RwLockWriteGuard<'static, SessionMap> { SESSIONS.call_once(init_sessions).write() } -/// Try to create a new session, but fail if one already exists for -/// this process +/// Try to create a new session, but fail if one already exists for this +/// process pub fn try_new_session(pid: ContextId, file_id: usize) -> bool { let mut sessions = sessions_mut(); match sessions.entry(pid) { Entry::Occupied(_) => false, Entry::Vacant(vacant) => { - vacant.insert(Session { - breakpoint: None, - events: VecDeque::new(), - file_id, - tracee: Arc::new(WaitCondition::new()), - tracer: Arc::new(WaitCondition::new()), - }); + vacant.insert(Arc::new(Session { + data: Mutex::new(SessionData { + breakpoint: None, + events: VecDeque::new(), + file_id, + }), + tracee: WaitCondition::new(), + tracer: WaitCondition::new(), + })); true } } } -/// Returns true if a session is attached to this process -pub fn is_traced(pid: ContextId) -> bool { - sessions().contains_key(&pid) -} - -/// Used for getting the flags in fevent -pub fn session_fevent_flags(pid: ContextId) -> Option { - let sessions = sessions(); - let session = sessions.get(&pid)?; - let mut flags = EventFlags::empty(); - if !session.events.is_empty() { - flags |= EVENT_READ; - } - Some(flags) -} - /// Remove the session from the list of open sessions and notify any /// waiting processes pub fn close_session(pid: ContextId) { if let Some(session) = sessions_mut().remove(&pid) { session.tracer.notify(); session.tracee.notify(); + + let data = session.data.lock(); + proc_trigger_event(data.file_id, EVENT_READ); } } +/// Returns true if a session is attached to this process +pub fn is_traced(pid: ContextId) -> bool { + sessions().contains_key(&pid) +} + /// Trigger a notification to the event: scheme fn proc_trigger_event(file_id: usize, flags: EventFlags) { event::trigger(proc::PROC_SCHEME_ID.load(Ordering::SeqCst), file_id, flags); @@ -138,35 +174,30 @@ fn proc_trigger_event(file_id: usize, flags: EventFlags) { /// the tracer to wake up and poll for events. Returns Some(()) if an /// event was sent. pub fn send_event(event: PtraceEvent) -> Option<()> { - let contexts = context::contexts(); - let context = contexts.current()?; - let context = context.read(); + let id = { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); + context.id + }; - let mut sessions = sessions_mut(); - let session = sessions.get_mut(&context.id)?; - let breakpoint = session.breakpoint.as_ref()?; + let sessions = sessions(); + let session = sessions.get(&id)?; + let mut data = session.data.lock(); + let breakpoint = data.breakpoint.as_ref()?; if event.cause & breakpoint.flags != event.cause { return None; } - session.send_event(event); + // Add event to queue + data.add_event(event); + // Notify tracer + session.tracer.notify(); Some(()) } -/// Poll events, return the amount read -pub fn recv_events(pid: ContextId, out: &mut [PtraceEvent]) -> Option { - let mut sessions = sessions_mut(); - let session = sessions.get_mut(&pid)?; - - let len = cmp::min(out.len(), session.events.len()); - for (dst, src) in out.iter_mut().zip(session.events.drain(..len)) { - *dst = src; - } - Some(len) -} - // ____ _ _ _ // | __ ) _ __ ___ __ _| | ___ __ ___ (_)_ __ | |_ ___ // | _ \| '__/ _ \/ _` | |/ / '_ \ / _ \| | '_ \| __/ __| @@ -174,60 +205,44 @@ pub fn recv_events(pid: ContextId, out: &mut [PtraceEvent]) -> Option { // |____/|_| \___|\__,_|_|\_\ .__/ \___/|_|_| |_|\__|___/ // |_| -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] struct Breakpoint { reached: bool, flags: PtraceFlags } -/// Continue the process with the specified ID -pub fn cont(pid: ContextId) { - let mut sessions = sessions_mut(); - let session = match sessions.get_mut(&pid) { - Some(session) => session, - None => return - }; - - // Remove the breakpoint to make sure any yet unreached but - // obsolete breakpoints don't stop the program. - session.breakpoint = None; - - session.tracee.notify(); -} - -/// Create a new breakpoint for the specified tracee, optionally with -/// a sysemu flag. Panics if the session is invalid. -pub fn set_breakpoint(pid: ContextId, flags: PtraceFlags) { - let mut sessions = sessions_mut(); - let session = sessions.get_mut(&pid).expect("proc (set_breakpoint): invalid session"); - session.breakpoint = Some(Breakpoint { - reached: false, - flags - }); -} - -/// Wait for the tracee to stop. If an event occurs, it returns a copy -/// of that. It will still be available for read using recv_event. +/// Wait for the tracee to stop, or return immediately if there's an unread +/// event. /// -/// Note: Don't call while holding any locks or allocated data, this -/// will switch contexts and may in fact just never terminate. +/// Note: Don't call while holding any locks or allocated data, this will +/// switch contexts and may in fact just never terminate. pub fn wait(pid: ContextId) -> Result<()> { - let tracer: Arc = { - let sessions = sessions(); - match sessions.get(&pid) { - Some(session) if session.breakpoint.as_ref().map(|b| !b.reached).unwrap_or(true) => { - if !session.events.is_empty() { - return Ok(()); - } - Arc::clone(&session.tracer) - }, - _ => return Ok(()) - } - }; + loop { + let session = { + let sessions = sessions(); - //TODO: proper wait_condition mutex - let m = Mutex::new(()); - while !tracer.wait(m.lock(), "ptrace::wait") {} + match sessions.get(&pid) { + Some(session) => Arc::clone(&session), + _ => return Ok(()) + } + }; + + // Lock the data, to make sure we're reading the final value before going + // to sleep. + let data = session.data.lock(); + + // Wake up if a breakpoint is already reached or there's an unread event + if data.breakpoint.as_ref().map(|b| b.reached).unwrap_or(false) || !data.events.is_empty() { + break; + } + + // Go to sleep, and drop the lock on our data, which will allow other the + // tracer to wake us up. + if session.tracer.wait(data, "ptrace::wait") { + // We successfully waited, wake up! + break; + } + } let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; @@ -239,48 +254,53 @@ pub fn wait(pid: ContextId) -> Result<()> { Ok(()) } -/// Notify the tracer and await green flag to continue. +/// Notify the tracer and await green flag to continue. If the breakpoint was +/// set and reached, return the flags which the user waited for. Otherwise, +/// None. /// /// Note: Don't call while holding any locks or allocated data, this /// will switch contexts and may in fact just never terminate. pub fn breakpoint_callback(match_flags: PtraceFlags, event: Option) -> Option { - // Can't hold any locks when executing wait() - let (tracee, flags) = { - let contexts = context::contexts(); - let context = contexts.current()?; - let context = context.read(); + loop { + let session = { + let contexts = context::contexts(); + let context = contexts.current()?; + let context = context.read(); - let mut sessions = sessions_mut(); - let session = sessions.get_mut(&context.id)?; - let breakpoint = session.breakpoint.as_mut()?; + let sessions = sessions(); + let session = sessions.get(&context.id)?; + Arc::clone(&session) + }; + + let mut data = session.data.lock(); + let breakpoint = data.breakpoint?; // only go to sleep if there's a breakpoint + + // Only stop if the tracer have asked for this breakpoint if breakpoint.flags & match_flags != match_flags { return None; } - // In case no tracer is waiting, make sure the next one gets - // the memo - breakpoint.reached = true; + // In case no tracer is waiting, make sure the next one gets the memo + data.breakpoint.as_mut() + .expect("already checked that breakpoint isn't None") + .reached = true; - let flags = breakpoint.flags; - session.send_event(event.unwrap_or(ptrace_event!(match_flags))); + // Add event to queue + data.add_event(event.unwrap_or(ptrace_event!(match_flags))); - ( - Arc::clone(&session.tracee), - flags - ) - }; + // Wake up sleeping tracer + session.tracer.notify(); - //TODO: proper wait_condition mutex - let m = Mutex::new(()); - while !tracee.wait(m.lock(), "ptrace::breakpoint_callback") {} - - Some(flags) + if session.tracee.wait(data, "ptrace::breakpoint_callback") { + // We successfully waited, wake up! + break Some(breakpoint.flags); + } + } } -/// Obtain the next breakpoint flags for the current process. This is -/// used for detecting whether or not the tracer decided to use sysemu -/// mode. +/// Obtain the next breakpoint flags for the current process. This is used for +/// detecting whether or not the tracer decided to use sysemu mode. pub fn next_breakpoint() -> Option { let contexts = context::contexts(); let context = contexts.current()?; @@ -288,25 +308,12 @@ pub fn next_breakpoint() -> Option { let sessions = sessions(); let session = sessions.get(&context.id)?; - let breakpoint = session.breakpoint.as_ref()?; + let data = session.data.lock(); + let breakpoint = data.breakpoint?; Some(breakpoint.flags) } -/// Call when a context is closed to alert any tracers -pub fn close_tracee(pid: ContextId) -> Option<()> { - let mut sessions = sessions_mut(); - let session = sessions.get_mut(&pid)?; - - // Cause tracers to wake up. Any action will cause ESRCH which can - // be used to detect exit. - session.breakpoint = None; - session.tracer.notify(); - proc_trigger_event(session.file_id, EVENT_READ); - - Some(()) -} - // ____ _ _ // | _ \ ___ __ _(_)___| |_ ___ _ __ ___ // | |_) / _ \/ _` | / __| __/ _ \ '__/ __| @@ -426,7 +433,7 @@ pub unsafe fn regs_for_mut(context: &mut Context) -> Option<&mut InterruptStack> // |___/ pub fn with_context_memory(context: &mut Context, offset: VirtualAddress, len: usize, f: F) -> Result<()> - where F: FnOnce(*mut u8) -> Result<()> +where F: FnOnce(*mut u8) -> Result<()> { // As far as I understand, mapping any regions following // USER_TMP_MISC_OFFSET is safe because no other memory location diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs index 31672bc..37f640d 100644 --- a/src/scheme/proc.rs +++ b/src/scheme/proc.rs @@ -26,7 +26,8 @@ use core::{ use spin::RwLock; fn with_context(pid: ContextId, callback: F) -> Result - where F: FnOnce(&Context) -> Result +where + F: FnOnce(&Context) -> Result, { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; @@ -37,7 +38,8 @@ fn with_context(pid: ContextId, callback: F) -> Result callback(&context) } fn with_context_mut(pid: ContextId, callback: F) -> Result - where F: FnOnce(&mut Context) -> Result +where + F: FnOnce(&mut Context) -> Result, { let contexts = context::contexts(); let context = contexts.get(pid).ok_or(Error::new(ESRCH))?; @@ -47,43 +49,36 @@ fn with_context_mut(pid: ContextId, callback: F) -> Result } callback(&mut context) } -fn try_stop_context(pid: ContextId, restart_after: bool, mut callback: F) -> Result - where F: FnMut(&mut Context) -> Result +fn try_stop_context(pid: ContextId, mut callback: F) -> Result +where + F: FnMut(&mut Context) -> Result, { - let mut first = true; - let mut was_stopped = false; // will never be read - - 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(pid).ok_or(Error::new(ESRCH))?; - let mut context = context.write(); - if let Status::Exited(_) = context.status { - return Err(Error::new(ESRCH)); - } - - // Stop the process until we've done our thing - if first { - was_stopped = context.ptrace_stop; - } + // Stop process + let (was_stopped, mut running) = with_context_mut(pid, |context| { + let was_stopped = context.ptrace_stop; context.ptrace_stop = true; - if context.running { - // Process still running, wait until it has stopped - continue; - } + Ok((was_stopped, context.running)) + })?; - let ret = callback(&mut context); + // Wait until stopped + while running { + unsafe { context::switch(); } - context.ptrace_stop = restart_after && was_stopped; - - break ret; + running = with_context(pid, |context| { + Ok(context.running) + })?; } + + with_context_mut(pid, |context| { + assert!(!context.running, "process can't have been restarted, we stopped it!"); + + let ret = callback(context); + + context.ptrace_stop = was_stopped; + + ret + }) } #[derive(Clone, Copy, PartialEq, Eq)] @@ -154,6 +149,7 @@ impl Handle { fn continue_ignored_children(&mut self) -> Option<()> { let data = self.data.trace_data()?; let contexts = context::contexts(); + for pid in data.clones.drain(..) { if ptrace::is_traced(pid) { continue; @@ -222,8 +218,8 @@ impl Scheme for ProcScheme { return Err(Error::new(EPERM)); } - // Is it a subprocess of us? In the future, a capability - // could bypass this check. + // Is it a subprocess of us? In the future, a capability could + // bypass this check. match contexts.anchestors(target.ppid).find(|&(id, _context)| id == current.id) { Some((id, context)) => { // Paranoid sanity check, as ptrace security holes @@ -240,8 +236,8 @@ impl Scheme for ProcScheme { if let Operation::Trace { .. } = operation { if !ptrace::try_new_session(pid, id) { - // There is no good way to handle id being occupied - // for nothing here, is there? + // There is no good way to handle id being occupied for nothing + // here, is there? return Err(Error::new(EBUSY)); } @@ -347,7 +343,7 @@ impl Scheme for ProcScheme { let fx = context.arch.get_fx_regs().unwrap_or_default(); Ok((Output { float: fx }, mem::size_of::())) })?, - RegsKind::Int => try_stop_context(info.pid, true, |context| match unsafe { ptrace::regs_for(&context) } { + RegsKind::Int => try_stop_context(info.pid, |context| match unsafe { ptrace::regs_for(&context) } { None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) @@ -375,7 +371,9 @@ impl Scheme for ProcScheme { buf.len() / mem::size_of::() ) }; - let read = ptrace::recv_events(info.pid, slice).unwrap_or(0); + let read = ptrace::Session::with_session(info.pid, |session| { + Ok(session.data.lock().recv_events(slice)) + })?; // Won't context switch, don't worry about the locks let mut handles = self.handles.write(); @@ -448,7 +446,7 @@ impl Scheme for ProcScheme { *(buf as *const _ as *const IntRegisters) }; - try_stop_context(info.pid, true, |context| match unsafe { ptrace::regs_for_mut(context) } { + try_stop_context(info.pid, |context| match unsafe { ptrace::regs_for_mut(context) } { None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) @@ -472,21 +470,21 @@ impl Scheme for ProcScheme { let op = u64::from_ne_bytes(bytes); let op = PtraceFlags::from_bits(op).ok_or(Error::new(EINVAL))?; - if !op.contains(PTRACE_FLAG_WAIT) || op.intersects(PTRACE_STOP_MASK) { - ptrace::cont(info.pid); - } - if op.intersects(PTRACE_STOP_MASK) { - ptrace::set_breakpoint(info.pid, op); - } + let should_continue = !op.contains(PTRACE_FLAG_WAIT) || op.intersects(PTRACE_STOP_MASK); + + // Set next breakpoint + ptrace::Session::with_session(info.pid, |session| { + if op.intersects(PTRACE_STOP_MASK) { + session.data.lock().set_breakpoint(Some(op)); + } else if should_continue { + session.data.lock().set_breakpoint(None); + } + Ok(()) + })?; if op.contains(PTRACE_STOP_SINGLESTEP) { - // try_stop_context with `false` will - // automatically disable ptrace_stop - try_stop_context(info.pid, false, |context| { + try_stop_context(info.pid, |context| { match unsafe { ptrace::regs_for_mut(context) } { - // If another CPU is running this process, - // await for it to be stopped and in such - // a way the registers can be read! None => { println!("{}:{}: Couldn't read registers from stopped process", file!(), line!()); Err(Error::new(ENOTRECOVERABLE)) @@ -497,14 +495,24 @@ impl Scheme for ProcScheme { } } })?; - } else { - // disable ptrace stop + } + + // Continue execution, if requested + if should_continue { + // disable the ptrace_stop flag, which is used in some cases with_context_mut(info.pid, |context| { context.ptrace_stop = false; Ok(()) })?; + + // and notify the tracee's WaitCondition, which is used in other cases + ptrace::Session::with_session(info.pid, |session| { + session.tracee.notify(); + Ok(()) + })?; } + // And await the tracee, if requested if op.contains(PTRACE_FLAG_WAIT) || info.flags & O_NONBLOCK != O_NONBLOCK { ptrace::wait(info.pid)?; } @@ -529,7 +537,9 @@ impl Scheme for ProcScheme { let handles = self.handles.read(); let handle = handles.get(&id).ok_or(Error::new(EBADF))?; - Ok(ptrace::session_fevent_flags(handle.info.pid).expect("proc (fevent): invalid session")) + ptrace::Session::with_session(handle.info.pid, |session| { + Ok(session.data.lock().session_fevent_flags()) + }) } fn fpath(&self, id: usize, buf: &mut [u8]) -> Result { @@ -558,12 +568,12 @@ impl Scheme for ProcScheme { if handle.info.flags & O_EXCL == O_EXCL { syscall::kill(handle.info.pid, SIGKILL)?; - } else { - let contexts = context::contexts(); - if let Some(context) = contexts.get(handle.info.pid) { - let mut context = context.write(); - context.ptrace_stop = false; - } + } + + let contexts = context::contexts(); + if let Some(context) = contexts.get(handle.info.pid) { + let mut context = context.write(); + context.ptrace_stop = false; } } Ok(0) diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 1920976..cf1737b 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -1151,8 +1151,8 @@ pub fn exit(status: usize) -> ! { } } - // Alert any tracers waiting for process (important: AFTER sending waitpid event) - ptrace::close_tracee(pid); + // Alert any tracers waiting of this process + ptrace::close_session(pid); if pid == ContextId::from(1) { println!("Main kernel thread exited with status {:X}", status);