Merge branch 'moar-ptrace' into 'master'

Misc ptrace cleanup

See merge request redox-os/kernel!128
This commit is contained in:
Jeremy Soller
2020-06-16 12:09:26 +00:00
5 changed files with 227 additions and 211 deletions

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -48,15 +48,13 @@ use spin::{Mutex, Once, RwLock, RwLockReadGuard, RwLockWriteGuard};
// |____/ \___||___/___/_|\___/|_| |_|___/
#[derive(Debug)]
struct Session {
pub struct SessionData {
breakpoint: Option<Breakpoint>,
events: VecDeque<PtraceEvent>,
file_id: usize,
tracee: Arc<WaitCondition>,
tracer: Arc<WaitCondition>,
}
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<PtraceFlags>) {
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<ContextId, Session>;
#[derive(Debug)]
pub struct Session {
pub data: Mutex<SessionData>,
pub tracee: WaitCondition,
pub tracer: WaitCondition,
}
impl Session {
pub fn with_session<F, T>(pid: ContextId, callback: F) -> Result<T>
where
F: FnOnce(&Session) -> Result<T>,
{
let sessions = sessions();
let session = sessions.get(&pid).ok_or(Error::new(ENODEV))?;
callback(session)
}
}
type SessionMap = BTreeMap<ContextId, Arc<Session>>;
static SESSIONS: Once<RwLock<SessionMap>> = 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<EventFlags> {
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<usize> {
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<usize> {
// |____/|_| \___|\__,_|_|\_\ .__/ \___/|_|_| |_|\__|___/
// |_|
#[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<WaitCondition> = {
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<PtraceEvent>) -> Option<PtraceFlags> {
// 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<PtraceFlags> {
let contexts = context::contexts();
let context = contexts.current()?;
@@ -288,25 +308,12 @@ pub fn next_breakpoint() -> Option<PtraceFlags> {
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<F>(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

View File

@@ -26,7 +26,8 @@ use core::{
use spin::RwLock;
fn with_context<F, T>(pid: ContextId, callback: F) -> Result<T>
where F: FnOnce(&Context) -> Result<T>
where
F: FnOnce(&Context) -> Result<T>,
{
let contexts = context::contexts();
let context = contexts.get(pid).ok_or(Error::new(ESRCH))?;
@@ -37,7 +38,8 @@ fn with_context<F, T>(pid: ContextId, callback: F) -> Result<T>
callback(&context)
}
fn with_context_mut<F, T>(pid: ContextId, callback: F) -> Result<T>
where F: FnOnce(&mut Context) -> Result<T>
where
F: FnOnce(&mut Context) -> Result<T>,
{
let contexts = context::contexts();
let context = contexts.get(pid).ok_or(Error::new(ESRCH))?;
@@ -47,43 +49,36 @@ fn with_context_mut<F, T>(pid: ContextId, callback: F) -> Result<T>
}
callback(&mut context)
}
fn try_stop_context<F, T>(pid: ContextId, restart_after: bool, mut callback: F) -> Result<T>
where F: FnMut(&mut Context) -> Result<T>
fn try_stop_context<F, T>(pid: ContextId, mut callback: F) -> Result<T>
where
F: FnMut(&mut Context) -> Result<T>,
{
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::<FloatRegisters>()))
})?,
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::<PtraceEvent>()
)
};
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<usize> {
@@ -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)

View File

@@ -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);