Skip to main content

sys_rs/
trace.rs

1use nix::{
2    sys::{ptrace, wait::WaitStatus},
3    unistd::{execve, fork, ForkResult, Pid},
4};
5use std::ffi::CString;
6
7use crate::diag::Result;
8
9/// A trait for implementing ptrace-based process tracers.
10///
11/// Types implementing `Tracer` provide the logic to inspect and control a traced
12/// process using the `ptrace` API. The primary extension point is the `trace`
13/// method, which is invoked with the PID of the child process to be traced. This
14/// allows custom tracing logic, such as system call inspection, breakpoint
15/// handling, or binary instrumentation.
16pub trait Tracer {
17    /// Trace the execution of the process identified by `pid`.
18    ///
19    /// Implementations should drive the ptrace-based inspection loop for the
20    /// child `pid` and return the process's numeric exit/status code. This is
21    /// the primary extension point used by the library to perform binary
22    /// inspection.
23    ///
24    /// # Arguments
25    ///
26    /// * `pid` - PID of the traced child process.
27    ///
28    /// # Errors
29    ///
30    /// Returns `Err` when any ptrace/wait/IO operation fails while tracing.
31    ///
32    /// # Returns
33    ///
34    /// Returns `Ok(exit_code)` where `exit_code` is the traced process's
35    /// numeric exit status (or a signal-derived value). On failure an `Err`
36    /// value is returned.
37    fn trace(&self, pid: Pid) -> Result<i32>;
38}
39
40fn tracee(args: &[CString], env: &[CString]) -> Result<i32> {
41    ptrace::traceme()?;
42    execve(&args[0], args, env)?;
43
44    Ok(0)
45}
46
47/// Fork and execute the target program, running `tracer` against the
48/// resulting child process.
49///
50/// This helper forks the current process. The child will exec the provided
51/// `args`/`env` and the parent will invoke the supplied `tracer` with the
52/// child's PID.
53///
54/// # Arguments
55///
56/// * `tracer` - The tracer implementation to use for binary inspection.
57/// * `args` - Command-line arguments for the binary to be inspected (the
58///   first element is the program path).
59/// * `env` - Environment variables for the child process.
60///
61/// # Errors
62///
63/// Returns `Err` if the fork fails or if the tracer returns an error while
64/// inspecting the child process.
65///
66/// # Returns
67///
68/// Returns `Ok(status)` where `status` is the numeric exit/status code
69/// produced by the tracer (typically `0` for success). On failure an `Err`
70/// is returned.
71pub fn run<T: Tracer>(tracer: &T, args: &[CString], env: &[CString]) -> Result<i32> {
72    match unsafe { fork() }? {
73        ForkResult::Parent { child: pid, .. } => tracer.trace(pid),
74        ForkResult::Child => tracee(args, env),
75    }
76}
77
78#[must_use]
79/// Inspect a `WaitStatus` and, if it denotes termination, print a
80/// human-readable summary and return the numeric exit or signal code.
81///
82/// # Arguments
83///
84/// * `status` - A `WaitStatus` returned from `wait`/`waitpid`.
85///
86/// # Returns
87///
88/// Returns `Some(code)` when the status represents a process exit or
89/// termination due to a signal. Returns `None` for non-terminating
90/// statuses such as `Stopped`.
91pub fn terminated(status: WaitStatus) -> Option<i32> {
92    match status {
93        WaitStatus::Signaled(_, signal, coredump) => {
94            let coredump_str = if coredump { " (core dumped)" } else { "" };
95            eprintln!("+++ killed by {signal:?}{coredump_str} +++");
96            Some(signal as i32)
97        }
98        WaitStatus::Exited(_, code) => {
99            eprintln!("+++ exited with {code} +++");
100            Some(code)
101        }
102        _ => None,
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_terminated_signaled() {
112        let status = WaitStatus::Signaled(
113            Pid::from_raw(1),
114            nix::sys::signal::Signal::SIGKILL,
115            false,
116        );
117        assert!(terminated(status).is_some());
118    }
119
120    #[test]
121    fn test_terminated_other() {
122        let status =
123            WaitStatus::Stopped(Pid::from_raw(1), nix::sys::signal::Signal::SIGSTOP);
124        assert!(terminated(status).is_none());
125    }
126}