From 3932dd96a71e05fc20c3fd58863f50ccc29fe8e2 Mon Sep 17 00:00:00 2001 From: Matthew Leach Date: Sat, 17 Jan 2026 08:35:33 +0000 Subject: [PATCH] ptrace: trace children on fork When calling `clone()` with PTRACE_O_TRACEFORK set, make the child inherit the current ptrace context. Also start the process with SIGSTOP pending as per the ptrace docs. This enable strace follow-forks functionality `strace -f`. --- src/process/clone.rs | 17 ++++++++++++++--- src/process/ptrace.rs | 35 +++++++++++++++++++---------------- src/sched/uspc_ret.rs | 2 +- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/process/clone.rs b/src/process/clone.rs index 30ae5c8..8209e6f 100644 --- a/src/process/clone.rs +++ b/src/process/clone.rs @@ -1,5 +1,5 @@ use super::owned::OwnedTask; -use super::ptrace::PTrace; +use super::ptrace::{PTrace, TracePoint, ptrace_stop}; use super::{ctx::Context, thread_group::signal::SigSet}; use crate::kernel::cpu_id::CpuId; use crate::memory::uaccess::copy_to_user; @@ -55,6 +55,10 @@ pub async fn sys_clone( ) -> Result { let flags = CloneFlags::from_bits_truncate(flags); + // TODO: differentiate between `TracePoint::Fork`, `TracePoint::Clone` and + // `TracePoint::VFork`. + let should_trace_new_tsk = ptrace_stop(TracePoint::Fork).await; + let new_task = { let current_task = current_task(); @@ -128,7 +132,7 @@ pub async fn sys_clone( Arc::new(SpinLock::new(current_task.root.lock_save_irq().clone())) }; - let ptrace = if flags.contains(CloneFlags::CLONE_PTRACE) { + let ptrace = if flags.contains(CloneFlags::CLONE_PTRACE) || should_trace_new_tsk { current_task.ptrace.lock_save_irq().clone() } else { PTrace::new() @@ -142,7 +146,14 @@ pub async fn sys_clone( ctx: Context::from_user_ctx(user_ctx), priority: current_task.priority, sig_mask: new_sigmask, - pending_signals: SigSet::empty(), + pending_signals: if should_trace_new_tsk { + // When we want to trace a new task through one of + // PTRACE_O_TRACE{FORK,VFORK,CLONE}, stop the child as soon as + // it is created. + SigSet::SIGSTOP + } else { + SigSet::empty() + }, robust_list: None, child_tid_ptr: if !child_tidptr.is_null() { Some(child_tidptr) diff --git a/src/process/ptrace.rs b/src/process/ptrace.rs index 5964233..ad0e26d 100644 --- a/src/process/ptrace.rs +++ b/src/process/ptrace.rs @@ -73,6 +73,7 @@ pub struct PTrace { break_points: TracePoint, state: Option, waker: Option, + tracer: Option>, sysgood: bool, } @@ -82,6 +83,7 @@ impl PTrace { state: None, break_points: TracePoint::empty(), waker: None, + tracer: None, sysgood: false, } } @@ -146,7 +148,7 @@ impl PTrace { } /// Notify parents of a trap event. - pub fn notify_parent_of_trap(&self, process: Arc) { + pub fn notify_tracer_of_trap(&self, me: &Arc) { let Some(trap_signal) = (match self.state { // For non-signal trace events, we use SIGTRAP. Some(PTraceState::TracePointHit { hit_point, .. }) => match hit_point { @@ -161,21 +163,16 @@ impl PTrace { }; // Notify the parent that we have stopped (SIGCHLD). - if let Some(parent) = process - .parent - .lock_save_irq() - .as_ref() - .and_then(|p| p.upgrade()) - { - parent.child_notifiers.child_update( - process.tgid, + if let Some(tracer) = self.tracer.as_ref() { + tracer.child_notifiers.child_update( + me.tgid, ChildState::TraceTrap { signal: trap_signal, mask: self.calc_trace_point_mask(), }, ); - parent + tracer .pending_signals .lock_save_irq() .set_signal(SigId::SIGCHLD); @@ -263,15 +260,15 @@ impl TryFrom for PtraceOperation { } } -pub async fn ptrace_stop(point: TracePoint) { +pub async fn ptrace_stop(point: TracePoint) -> bool { let task_sh = current_task_shared(); { let mut ptrace = task_sh.ptrace.lock_save_irq(); if ptrace.hit_trace_point(point, current_task().ctx.user()) { - ptrace.notify_parent_of_trap(task_sh.process.clone()); + ptrace.notify_tracer_of_trap(&task_sh.process); } else { - return; + return false; } } @@ -279,13 +276,13 @@ pub async fn ptrace_stop(point: TracePoint) { let mut ptrace = task_sh.ptrace.lock_save_irq(); if matches!(ptrace.state, Some(PTraceState::Running)) { - Poll::Ready(()) + Poll::Ready(true) } else { ptrace.set_waker(cx.waker().clone()); Poll::Pending } }) - .await; + .await } pub async fn sys_ptrace(op: i32, pid: u64, addr: UA, data: UA) -> Result { @@ -296,6 +293,12 @@ pub async fn sys_ptrace(op: i32, pid: u64, addr: UA, data: UA) -> Result let mut ptrace = current_task.ptrace.lock_save_irq(); ptrace.state = Some(PTraceState::Running); + ptrace.tracer = current_task + .process + .parent + .lock_save_irq() + .as_ref() + .and_then(|x| x.upgrade()); // Set default breakpoint for TraceMe. ptrace.break_points = TracePoint::Exec; @@ -361,7 +364,7 @@ pub async fn sys_ptrace(op: i32, pid: u64, addr: UA, data: UA) -> Result PTraceOptions::PTRACE_O_TRACEEXIT => { ptrace.break_points.insert(TracePoint::Exit) } - PTraceOptions::PTRACE_O_TRACEFORK => { + PTraceOptions::PTRACE_O_TRACEFORK | PTraceOptions::PTRACE_O_TRACEVFORK => { ptrace.break_points.insert(TracePoint::Fork) } PTraceOptions::PTRACE_O_TRACEEXEC => { diff --git a/src/sched/uspc_ret.rs b/src/sched/uspc_ret.rs index c7a570c..6866d28 100644 --- a/src/sched/uspc_ret.rs +++ b/src/sched/uspc_ret.rs @@ -251,7 +251,7 @@ pub fn dispatch_userspace_task(ctx: *mut UserCtx) { while let Some(signal) = task.take_signal() { let mut ptrace = task.ptrace.lock_save_irq(); if ptrace.trace_signal(signal, task.ctx.user()) { - ptrace.notify_parent_of_trap(task.process.clone()); + ptrace.notify_tracer_of_trap(&task.process); ptrace.set_waker(create_waker(task.descriptor())); *task.state.lock_save_irq() = TaskState::Stopped;