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}