Skip to main content

running_process_platform_internal/
platform_linux_trace.rs

1//! Launch-time Linux `ptrace` process-tree supervision.
2
3use std::collections::HashMap;
4use std::ffi::OsString;
5use std::io;
6use std::os::unix::ffi::OsStringExt;
7use std::os::unix::process::CommandExt;
8use std::process::Child;
9use std::sync::{Arc, Condvar, Mutex};
10use std::time::Duration;
11
12use crate::platform::process::{ExactTraceEvent, ExactTraceEventKind, TraceOriginArtifact};
13
14const STACK_CAPTURE_BYTES: usize = 16 * 1024;
15const MODULE_MAP_CAPTURE_BYTES: usize = 256 * 1024;
16
17// libc follows the host C ABI here: glibc declares ptrace's request as an
18// unsigned enum, while musl declares it as c_int. Keeping the wrapper's
19// parameter identical to libc also keeps every PTRACE_* constant type-correct.
20#[cfg(target_env = "musl")]
21type PtraceRequest = libc::c_int;
22#[cfg(not(target_env = "musl"))]
23type PtraceRequest = libc::c_uint;
24
25/// Arrange for a successful `exec` to stop the child before user code runs.
26/// No pre-exec SIGSTOP is used: that would deadlock `Command::spawn`'s exec
27/// error pipe.
28pub fn configure_exact_trace(command: &mut std::process::Command) -> io::Result<()> {
29    // SAFETY: this closure runs in the single-threaded post-fork child before
30    // exec. PTRACE_TRACEME takes only scalar/null arguments and reports errors
31    // through errno; it does not retain borrowed Rust memory.
32    unsafe {
33        command.pre_exec(|| {
34            let result = libc::ptrace(
35                libc::PTRACE_TRACEME,
36                0,
37                std::ptr::null_mut::<libc::c_void>(),
38                std::ptr::null_mut::<libc::c_void>(),
39            );
40            if result == -1 {
41                return Err(io::Error::last_os_error());
42            }
43            Ok(())
44        });
45    }
46    Ok(())
47}
48
49#[derive(Default)]
50struct RootState {
51    exit_code: Option<i32>,
52    done: bool,
53    tracer_error: Option<String>,
54}
55
56struct Shared {
57    state: Mutex<RootState>,
58    wake: Condvar,
59}
60
61/// Root-process control handle whose wait state is owned by the tracer.
62pub struct TracedChild {
63    pid: u32,
64    shared: Arc<Shared>,
65    stdin: Option<std::process::ChildStdin>,
66    stdout: Option<std::process::ChildStdout>,
67    stderr: Option<std::process::ChildStderr>,
68}
69
70impl TracedChild {
71    pub fn id(&self) -> u32 {
72        self.pid
73    }
74
75    pub fn try_wait_code(&self) -> io::Result<Option<i32>> {
76        let state = self.shared.state.lock().unwrap_or_else(|e| e.into_inner());
77        if let Some(error) = state.tracer_error.as_ref() {
78            return Err(io::Error::other(error.clone()));
79        }
80        Ok(state.exit_code)
81    }
82
83    pub fn kill(&mut self) -> io::Result<()> {
84        // SAFETY: `pid` is the positive PID returned by `Command::spawn`; no
85        // pointer arguments are involved and ESRCH is handled as already gone.
86        if unsafe { libc::kill(self.pid as libc::pid_t, libc::SIGKILL) } == -1 {
87            let error = io::Error::last_os_error();
88            if error.raw_os_error() != Some(libc::ESRCH) {
89                return Err(error);
90            }
91        }
92        Ok(())
93    }
94
95    pub fn take_stdin(&mut self) -> Option<std::process::ChildStdin> {
96        self.stdin.take()
97    }
98
99    pub fn take_stdout(&mut self) -> Option<std::process::ChildStdout> {
100        self.stdout.take()
101    }
102
103    pub fn take_stderr(&mut self) -> Option<std::process::ChildStderr> {
104        self.stderr.take()
105    }
106}
107
108pub fn start_exact_trace(
109    mut command: std::process::Command,
110    emit: Box<dyn Fn(ExactTraceEvent) + Send>,
111    complete: Box<dyn FnOnce() + Send>,
112) -> io::Result<TracedChild> {
113    configure_exact_trace(&mut command)?;
114    let shared = Arc::new(Shared {
115        state: Mutex::new(RootState::default()),
116        wake: Condvar::new(),
117    });
118    let thread_shared = Arc::clone(&shared);
119    let (setup_tx, setup_rx) = std::sync::mpsc::sync_channel(1);
120    let spawned = std::thread::Builder::new()
121        .name("rp-linux-ptrace".to_owned())
122        .spawn(move || {
123            let child = match command.spawn() {
124                Ok(child) => child,
125                Err(error) => {
126                    let _ = setup_tx.send(Err(error));
127                    return;
128                }
129            };
130            trace_loop(child, thread_shared, emit, complete, setup_tx);
131        });
132    if let Err(error) = spawned {
133        return Err(io::Error::other(format!(
134            "spawn ptrace supervisor: {error}"
135        )));
136    }
137    let setup = setup_rx
138        .recv()
139        .map_err(|_| io::Error::other("ptrace supervisor ended during launch setup"))??;
140    Ok(TracedChild {
141        pid: setup.pid,
142        shared,
143        stdin: setup.stdin,
144        stdout: setup.stdout,
145        stderr: setup.stderr,
146    })
147}
148
149struct TraceSetup {
150    pid: u32,
151    stdin: Option<std::process::ChildStdin>,
152    stdout: Option<std::process::ChildStdout>,
153    stderr: Option<std::process::ChildStderr>,
154}
155
156#[derive(Clone)]
157struct Tracee {
158    parent_pid: Option<u32>,
159    parent_start_key: Option<u64>,
160    start_key: Option<u64>,
161    process_leader: bool,
162    executable: Option<std::path::PathBuf>,
163    argv: Option<Vec<OsString>>,
164    origin: Option<TraceOriginArtifact>,
165}
166
167fn trace_loop(
168    mut child: Child,
169    shared: Arc<Shared>,
170    emit: Box<dyn Fn(ExactTraceEvent) + Send>,
171    complete: Box<dyn FnOnce() + Send>,
172    setup: std::sync::mpsc::SyncSender<io::Result<TraceSetup>>,
173) {
174    let root_pid = child.id();
175    let mut initial_status = 0;
176    // SAFETY: `root_pid` was spawned by this same supervisor task after
177    // PTRACE_TRACEME, and `initial_status` is valid writable storage.
178    if unsafe { libc::waitpid(root_pid as libc::pid_t, &mut initial_status, libc::__WALL) } == -1
179        || !libc::WIFSTOPPED(initial_status)
180    {
181        let message = "root did not reach its initial ptrace exec stop";
182        let _ = setup.send(Err(io::Error::other(message)));
183        cleanup_failed_setup(&mut child);
184        return;
185    }
186
187    let options = libc::PTRACE_O_TRACEFORK
188        | libc::PTRACE_O_TRACEVFORK
189        | libc::PTRACE_O_TRACECLONE
190        | libc::PTRACE_O_TRACEEXEC
191        | libc::PTRACE_O_TRACEEXIT;
192    if ptrace_value(libc::PTRACE_SETOPTIONS, root_pid, 0, options as usize).is_err() {
193        let message = "PTRACE_SETOPTIONS was denied";
194        let _ = setup.send(Err(io::Error::new(
195            io::ErrorKind::PermissionDenied,
196            message,
197        )));
198        cleanup_failed_setup(&mut child);
199        return;
200    }
201
202    let mut sequence = 1u64;
203    let mut tracees = HashMap::from([(
204        root_pid,
205        Tracee {
206            parent_pid: None,
207            parent_start_key: None,
208            start_key: process_start_key(root_pid),
209            process_leader: true,
210            executable: read_executable(root_pid),
211            argv: read_argv(root_pid),
212            origin: None,
213        },
214    )]);
215    if ptrace_value(libc::PTRACE_CONT, root_pid, 0, 0).is_err() {
216        let message = "failed to continue root after initial exec stop";
217        let _ = setup.send(Err(io::Error::other(message)));
218        detach_all(tracees.keys().copied());
219        cleanup_failed_setup(&mut child);
220        return;
221    }
222    let setup_result = TraceSetup {
223        pid: root_pid,
224        stdin: child.stdin.take(),
225        stdout: child.stdout.take(),
226        stderr: child.stderr.take(),
227    };
228    if setup.send(Ok(setup_result)).is_err() {
229        cleanup_failed_setup(&mut child);
230        return;
231    }
232
233    while !tracees.is_empty() {
234        let pids: Vec<u32> = tracees.keys().copied().collect();
235        let mut progressed = false;
236        for pid in pids {
237            let mut status = 0;
238            // SAFETY: `status` is valid writable storage and `pid` is a
239            // tracee owned by this supervisor thread. `WNOHANG` keeps the
240            // event pump responsive across the complete tracee set.
241            let waited = unsafe {
242                libc::waitpid(
243                    pid as libc::pid_t,
244                    &mut status,
245                    libc::WNOHANG | libc::__WALL,
246                )
247            };
248            if waited == 0 {
249                continue;
250            }
251            if waited == -1 {
252                let error = io::Error::last_os_error();
253                let tracee = tracees.get(&pid).cloned();
254                if error.raw_os_error() == Some(libc::ECHILD)
255                    && tracee.as_ref().is_some_and(|item| !item.process_leader)
256                {
257                    // A non-leader thread can disappear with its process
258                    // leader and leave a stale TID in the local set. Losing
259                    // wait ownership for a process leader is different: it
260                    // destroys exit coverage (and for the root would leave
261                    // TracedChild waiting forever), so that is fatal below.
262                    tracees.remove(&pid);
263                    continue;
264                }
265                abort_runtime_trace(
266                    &mut child,
267                    &shared,
268                    &emit,
269                    sequence,
270                    pid,
271                    tracee.as_ref(),
272                    &tracees,
273                    format!("ptrace wait ownership lost: {error}"),
274                );
275                complete();
276                return;
277            }
278            progressed = true;
279            if libc::WIFEXITED(status) || libc::WIFSIGNALED(status) {
280                if let Some(tracee) = tracees.remove(&pid) {
281                    if tracee.process_leader {
282                        let exit_code = libc::WIFEXITED(status).then(|| libc::WEXITSTATUS(status));
283                        let signal = libc::WIFSIGNALED(status).then(|| libc::WTERMSIG(status));
284                        if pid == root_pid {
285                            let normalized = exit_code.unwrap_or_else(|| -signal.unwrap_or(0));
286                            finish_root(&shared, normalized);
287                        } else {
288                            emit(event_for(
289                                sequence,
290                                pid,
291                                &tracee,
292                                ExactTraceEventKind::Exit {
293                                    exit_code,
294                                    signal,
295                                    raw_status: i64::from(status),
296                                },
297                            ));
298                            sequence += 1;
299                        }
300                    }
301                }
302                continue;
303            }
304            if !libc::WIFSTOPPED(status) {
305                continue;
306            }
307
308            let signal = libc::WSTOPSIG(status);
309            let ptrace_event = status >> 16;
310            match ptrace_event {
311                libc::PTRACE_EVENT_FORK | libc::PTRACE_EVENT_VFORK | libc::PTRACE_EVENT_CLONE => {
312                    let mut child_pid = 0usize;
313                    match ptrace_value(
314                        libc::PTRACE_GETEVENTMSG,
315                        pid,
316                        0,
317                        (&raw mut child_pid) as usize,
318                    ) {
319                        Ok(_) => {
320                        let child_pid = child_pid as u32;
321                        let process_leader = if matches!(
322                            ptrace_event,
323                            libc::PTRACE_EVENT_FORK | libc::PTRACE_EVENT_VFORK
324                        ) {
325                            true
326                        } else {
327                            match thread_group_id(child_pid) {
328                                Some(thread_group) => thread_group == child_pid,
329                                None => {
330                                    // The auto-attached child is stopped but
331                                    // cannot safely be classified as a thread
332                                    // or process. Include it in fatal cleanup;
333                                    // defaulting to a thread would later hide
334                                    // process-leader ECHILD as a stale TID.
335                                    tracees.insert(
336                                        child_pid,
337                                        Tracee {
338                                            parent_pid: None,
339                                            parent_start_key: None,
340                                            start_key: process_start_key(child_pid),
341                                            process_leader: true,
342                                            executable: None,
343                                            argv: None,
344                                            origin: None,
345                                        },
346                                    );
347                                    abort_runtime_trace(
348                                        &mut child,
349                                        &shared,
350                                        &emit,
351                                        sequence,
352                                        child_pid,
353                                        tracees.get(&child_pid),
354                                        &tracees,
355                                        "cannot classify PTRACE_EVENT_CLONE child".to_owned(),
356                                    );
357                                    complete();
358                                    return;
359                                }
360                            }
361                        };
362                        let parent_pid =
363                            process_leader.then(|| thread_group_id(pid).unwrap_or(pid));
364                        let tracee = Tracee {
365                            parent_pid,
366                            parent_start_key: parent_pid.and_then(process_start_key),
367                            start_key: process_start_key(child_pid),
368                            process_leader,
369                            executable: None,
370                            argv: None,
371                            origin: process_leader.then(|| capture_origin(pid)),
372                        };
373                        let spawn_event = process_leader.then(|| {
374                            event_for(
375                                sequence,
376                                child_pid,
377                                &tracee,
378                                ExactTraceEventKind::Spawn,
379                            )
380                        });
381                        tracees.insert(child_pid, tracee);
382                        if let Err(error) = ptrace_value(libc::PTRACE_CONT, pid, 0, 0) {
383                            abort_runtime_trace(
384                                &mut child,
385                                &shared,
386                                &emit,
387                                sequence,
388                                pid,
389                                tracees.get(&pid),
390                                &tracees,
391                                format!("continue spawning tracee: {error}"),
392                            );
393                            complete();
394                            return;
395                        }
396                        let mut child_status = 0;
397                        // SAFETY: the kernel has just reported this auto-attached
398                        // child and guarantees an initial ptrace stop. This
399                        // supervisor thread is its tracer and owns the wait.
400                        let waited = unsafe {
401                            libc::waitpid(
402                                child_pid as libc::pid_t,
403                                &mut child_status,
404                                libc::__WALL,
405                            )
406                        };
407                        if waited != child_pid as libc::pid_t
408                            || !libc::WIFSTOPPED(child_status)
409                        {
410                            abort_runtime_trace(
411                                &mut child,
412                                &shared,
413                                &emit,
414                                sequence,
415                                child_pid,
416                                tracees.get(&child_pid),
417                                &tracees,
418                                "new tracee did not reach its initial stop".to_owned(),
419                            );
420                            complete();
421                            return;
422                        }
423                        if let Err(error) = ptrace_value(libc::PTRACE_CONT, child_pid, 0, 0) {
424                            abort_runtime_trace(
425                                &mut child,
426                                &shared,
427                                &emit,
428                                sequence,
429                                child_pid,
430                                tracees.get(&child_pid),
431                                &tracees,
432                                format!("continue new tracee: {error}"),
433                            );
434                            complete();
435                            return;
436                        }
437                        // Both stopped tasks are running before delivery can do
438                        // any file I/O or deferred-symbolization queueing.
439                        if let Some(spawn_event) = spawn_event {
440                            emit(spawn_event);
441                            sequence += 1;
442                        }
443                        }
444                        Err(error) => {
445                            abort_runtime_trace(
446                                &mut child,
447                                &shared,
448                                &emit,
449                                sequence,
450                                pid,
451                                tracees.get(&pid),
452                                &tracees,
453                                format!("read fork/clone event child pid: {error}"),
454                            );
455                            complete();
456                            return;
457                        }
458                    }
459                }
460                libc::PTRACE_EVENT_EXEC => {
461                    let exec_event = if let Some(tracee) = tracees.get_mut(&pid) {
462                        tracee.executable = read_executable(pid);
463                        tracee.argv = read_argv(pid);
464                        tracee
465                            .process_leader
466                            .then(|| event_for(sequence, pid, tracee, ExactTraceEventKind::Exec))
467                    } else {
468                        None
469                    };
470                    if let Err(error) = ptrace_value(libc::PTRACE_CONT, pid, 0, 0) {
471                        abort_runtime_trace(
472                            &mut child,
473                            &shared,
474                            &emit,
475                            sequence,
476                            pid,
477                            tracees.get(&pid),
478                            &tracees,
479                            format!("continue after exec event: {error}"),
480                        );
481                        complete();
482                        return;
483                    }
484                    if let Some(exec_event) = exec_event {
485                        emit(exec_event);
486                        sequence += 1;
487                    }
488                }
489                libc::PTRACE_EVENT_EXIT => {
490                    if let Err(error) = ptrace_value(libc::PTRACE_CONT, pid, 0, 0) {
491                        abort_runtime_trace(
492                            &mut child,
493                            &shared,
494                            &emit,
495                            sequence,
496                            pid,
497                            tracees.get(&pid),
498                            &tracees,
499                            format!("continue after exit event: {error}"),
500                        );
501                        complete();
502                        return;
503                    }
504                }
505                _ => {
506                    let forwarded = if signal == libc::SIGTRAP {
507                        0
508                    } else {
509                        signal
510                    };
511                    if let Err(error) =
512                        ptrace_value(libc::PTRACE_CONT, pid, 0, forwarded as usize)
513                    {
514                        abort_runtime_trace(
515                            &mut child,
516                            &shared,
517                            &emit,
518                            sequence,
519                            pid,
520                            tracees.get(&pid),
521                            &tracees,
522                            format!("continue after signal stop: {error}"),
523                        );
524                        complete();
525                        return;
526                    }
527                }
528            }
529        }
530        if !progressed {
531            std::thread::sleep(Duration::from_millis(1));
532        }
533    }
534    drop(child);
535    complete();
536}
537
538fn ptrace_value(
539    request: PtraceRequest,
540    pid: u32,
541    address: usize,
542    data: usize,
543) -> io::Result<libc::c_long> {
544    // SAFETY: callers provide a live tracee owned by this supervisor. Pointer
545    // values are either null, kernel-defined scalar payloads, or addresses of
546    // writable storage whose lifetime covers this synchronous syscall.
547    let value = unsafe {
548        libc::ptrace(
549            request,
550            pid as libc::pid_t,
551            address as *mut libc::c_void,
552            data as *mut libc::c_void,
553        )
554    };
555    if value == -1 {
556        Err(io::Error::last_os_error())
557    } else {
558        Ok(value)
559    }
560}
561
562fn emit_loss(
563    emit: &dyn Fn(ExactTraceEvent),
564    sequence: u64,
565    pid: u32,
566    tracee: Option<&Tracee>,
567    reason: String,
568) {
569    emit(ExactTraceEvent {
570        sequence,
571        pid,
572        parent_pid: tracee.and_then(|tracee| tracee.parent_pid),
573        parent_start_key: tracee.and_then(|tracee| tracee.parent_start_key),
574        start_key: tracee.and_then(|tracee| tracee.start_key),
575        timestamp: std::time::SystemTime::now(),
576        kind: ExactTraceEventKind::Loss { reason },
577        executable: None,
578        argv: None,
579        origin: None,
580    });
581}
582
583#[allow(clippy::too_many_arguments)]
584fn abort_runtime_trace(
585    child: &mut Child,
586    shared: &Shared,
587    emit: &dyn Fn(ExactTraceEvent),
588    sequence: u64,
589    pid: u32,
590    tracee: Option<&Tracee>,
591    tracees: &HashMap<u32, Tracee>,
592    reason: String,
593) {
594    emit_loss(emit, sequence, pid, tracee, reason.clone());
595    for tracee_pid in tracees.keys().copied().filter(|item| *item != child.id()) {
596        // SAFETY: each value is a positive kernel-reported tracee PID. Fatal
597        // supervision failure chooses deterministic termination over leaving
598        // an unserviced ptrace stop that can wedge indefinitely.
599        unsafe {
600            libc::kill(tracee_pid as libc::pid_t, libc::SIGKILL);
601            libc::waitpid(tracee_pid as libc::pid_t, std::ptr::null_mut(), libc::__WALL);
602        }
603    }
604    let _ = child.kill();
605    let _ = child.wait();
606    let mut state = shared.state.lock().unwrap_or_else(|error| error.into_inner());
607    state.tracer_error = Some(reason);
608    state.done = true;
609    shared.wake.notify_all();
610}
611
612fn capture_origin(thread_id: u32) -> TraceOriginArtifact {
613    let origin_pid = thread_group_id(thread_id).unwrap_or(thread_id);
614    let mut registers = vec![0u8; 1024];
615    let mut iov = libc::iovec {
616        iov_base: registers.as_mut_ptr().cast(),
617        iov_len: registers.len(),
618    };
619    if ptrace_value(
620        libc::PTRACE_GETREGSET,
621        thread_id,
622        libc::NT_PRSTATUS as usize,
623        (&raw mut iov) as usize,
624    )
625    .is_ok()
626    {
627        registers.truncate(iov.iov_len);
628    } else {
629        registers.clear();
630    }
631    let (stack_pointer, instruction_pointer) = read_syscall_pointers(thread_id);
632    let mut stack = vec![0u8; STACK_CAPTURE_BYTES];
633    if let Some(stack_pointer) = stack_pointer {
634        let local = libc::iovec {
635            iov_base: stack.as_mut_ptr().cast(),
636            iov_len: stack.len(),
637        };
638        let remote = libc::iovec {
639            iov_base: stack_pointer as *mut libc::c_void,
640            iov_len: stack.len(),
641        };
642        // SAFETY: local points to the owned `stack` allocation for its full
643        // lifetime; remote is a bounded address range in the stopped tracee.
644        // `process_vm_readv` copies at most the declared local length.
645        let read = unsafe {
646            libc::process_vm_readv(thread_id as libc::pid_t, &local, 1, &remote, 1, 0)
647        };
648        if read >= 0 {
649            stack.truncate(read as usize);
650        } else {
651            stack.clear();
652        }
653    } else {
654        stack.clear();
655    }
656    let mut module_map = std::fs::read(format!("/proc/{origin_pid}/maps")).unwrap_or_default();
657    let module_map_truncated = module_map.len() > MODULE_MAP_CAPTURE_BYTES;
658    module_map.truncate(MODULE_MAP_CAPTURE_BYTES);
659    TraceOriginArtifact {
660        origin_pid,
661        thread_id,
662        architecture: std::env::consts::ARCH.to_owned(),
663        register_format: format!("linux-nt-prstatus-{}", std::env::consts::ARCH),
664        executable: read_executable(origin_pid),
665        registers,
666        stack_pointer,
667        instruction_pointer,
668        truncated: stack.len() == STACK_CAPTURE_BYTES,
669        stack,
670        module_map,
671        module_map_truncated,
672    }
673}
674
675fn read_syscall_pointers(pid: u32) -> (Option<u64>, Option<u64>) {
676    let Ok(text) = std::fs::read_to_string(format!("/proc/{pid}/syscall")) else {
677        return (None, None);
678    };
679    let fields: Vec<&str> = text.split_ascii_whitespace().collect();
680    if fields.len() < 2 {
681        return (None, None);
682    }
683    let parse = |value: &str| u64::from_str_radix(value.trim_start_matches("0x"), 16).ok();
684    (parse(fields[fields.len() - 2]), parse(fields[fields.len() - 1]))
685}
686
687fn read_executable(pid: u32) -> Option<std::path::PathBuf> {
688    std::fs::read_link(format!("/proc/{pid}/exe")).ok()
689}
690
691fn read_argv(pid: u32) -> Option<Vec<OsString>> {
692    let bytes = std::fs::read(format!("/proc/{pid}/cmdline")).ok()?;
693    Some(
694        bytes
695            .split(|byte| *byte == 0)
696            .filter(|part| !part.is_empty())
697            .map(|part| OsString::from_vec(part.to_vec()))
698            .collect(),
699    )
700}
701
702fn process_start_key(pid: u32) -> Option<u64> {
703    let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
704    stat.get(stat.rfind(')')? + 1..)?
705        .split_ascii_whitespace()
706        .nth(19)?
707        .parse()
708        .ok()
709}
710
711fn thread_group_id(pid: u32) -> Option<u32> {
712    std::fs::read_to_string(format!("/proc/{pid}/status"))
713        .ok()?
714        .lines()
715        .find_map(|line| line.strip_prefix("Tgid:")?.trim().parse().ok())
716}
717
718fn event_for(
719    sequence: u64,
720    pid: u32,
721    tracee: &Tracee,
722    kind: ExactTraceEventKind,
723) -> ExactTraceEvent {
724    ExactTraceEvent {
725        sequence,
726        pid,
727        parent_pid: tracee.parent_pid,
728        parent_start_key: tracee.parent_start_key,
729        start_key: tracee.start_key,
730        timestamp: std::time::SystemTime::now(),
731        kind,
732        executable: tracee.executable.clone(),
733        argv: tracee.argv.clone(),
734        origin: tracee.origin.clone(),
735    }
736}
737
738fn detach_all(tracees: impl IntoIterator<Item = u32>) {
739    for pid in tracees {
740        let _ = ptrace_value(libc::PTRACE_DETACH, pid, 0, 0);
741    }
742}
743
744fn cleanup_failed_setup(child: &mut Child) {
745    let _ = child.kill();
746    let _ = child.wait();
747}
748
749fn finish_root(shared: &Shared, exit_code: i32) {
750    let mut state = shared.state.lock().unwrap_or_else(|e| e.into_inner());
751    state.exit_code = Some(exit_code);
752    state.done = true;
753    shared.wake.notify_all();
754}
755
756#[cfg(test)]
757#[path = "tests/platform_linux_trace_coverage.rs"]
758mod coverage_tests;