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`.
This commit is contained in:
Matthew Leach
2026-01-17 08:35:33 +00:00
parent c6dd7b07c0
commit 3932dd96a7
3 changed files with 34 additions and 20 deletions

View File

@@ -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<usize> {
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)

View File

@@ -73,6 +73,7 @@ pub struct PTrace {
break_points: TracePoint,
state: Option<PTraceState>,
waker: Option<Waker>,
tracer: Option<Arc<ThreadGroup>>,
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<ThreadGroup>) {
pub fn notify_tracer_of_trap(&self, me: &Arc<ThreadGroup>) {
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<i32> 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<usize> {
@@ -296,6 +293,12 @@ pub async fn sys_ptrace(op: i32, pid: u64, addr: UA, data: UA) -> Result<usize>
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<usize>
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 => {

View File

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