1use std::collections::{HashMap, HashSet};
2use std::ffi::CString;
3use std::io;
4
5use nix::sys::ptrace;
6use nix::sys::wait::WaitStatus;
7use nix::unistd::{ForkResult, Pid, execvp, fork};
8
9use crate::arch;
10use crate::event::SyscallEvent;
11use crate::platform;
12
13pub struct TracerOptions {
15 pub follow_forks: bool,
17}
18
19impl Default for TracerOptions {
20 fn default() -> Self {
21 Self {
22 follow_forks: false,
23 }
24 }
25}
26
27pub struct Tracer {
28 root_pid: Pid,
30 traced: HashSet<i32>,
32 in_syscall: HashMap<i32, bool>,
34 pending_entry: HashMap<i32, SyscallEvent>,
36 attached: bool,
38 options: TracerOptions,
39}
40
41impl Tracer {
42 pub fn spawn(command: &[String], options: TracerOptions) -> io::Result<Self> {
44 if command.is_empty() {
45 return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty command"));
46 }
47
48 let c_args: Vec<CString> = command
49 .iter()
50 .map(|s| CString::new(s.as_str()).unwrap())
51 .collect();
52
53 match unsafe { fork() }.map_err(|e| io::Error::from_raw_os_error(e as i32))? {
54 ForkResult::Child => {
55 platform::traceme()?;
56 execvp(&c_args[0], &c_args)
57 .map_err(|e| io::Error::from_raw_os_error(e as i32))?;
58 unreachable!()
59 }
60 ForkResult::Parent { child } => {
61 platform::wait(child)?;
63 platform::set_options(child, Self::ptrace_options(&options))?;
64 platform::syscall_continue(child, None)?;
65
66 let mut traced = HashSet::new();
67 traced.insert(child.as_raw());
68
69 Ok(Tracer {
70 root_pid: child,
71 traced,
72 in_syscall: HashMap::new(),
73 pending_entry: HashMap::new(),
74 attached: false,
75 options,
76 })
77 }
78 }
79 }
80
81 pub fn attach(pid: i32, options: TracerOptions) -> io::Result<Self> {
83 let target = Pid::from_raw(pid);
84 platform::attach(target)?;
85 platform::wait(target)?;
87 platform::set_options(target, Self::ptrace_options(&options))?;
88 platform::syscall_continue(target, None)?;
89
90 let mut traced = HashSet::new();
91 traced.insert(pid);
92
93 Ok(Tracer {
94 root_pid: target,
95 traced,
96 in_syscall: HashMap::new(),
97 pending_entry: HashMap::new(),
98 attached: true,
99 options,
100 })
101 }
102
103 fn ptrace_options(opts: &TracerOptions) -> ptrace::Options {
104 let mut flags = ptrace::Options::PTRACE_O_TRACESYSGOOD
105 | ptrace::Options::PTRACE_O_TRACEEXEC;
106 if opts.follow_forks {
107 flags |= ptrace::Options::PTRACE_O_TRACEFORK
108 | ptrace::Options::PTRACE_O_TRACEVFORK
109 | ptrace::Options::PTRACE_O_TRACECLONE;
110 }
111 flags
112 }
113
114 pub fn run<F: FnMut(&SyscallEvent)>(&mut self, mut on_event: F) -> io::Result<i32> {
118 let mut root_exit_code: Option<i32> = None;
119
120 loop {
121 if self.traced.is_empty() {
122 return Ok(root_exit_code.unwrap_or(0));
123 }
124
125 let status = if self.options.follow_forks {
127 platform::wait_any()
128 } else {
129 platform::wait(self.root_pid)
130 };
131
132 let status = match status {
133 Ok(s) => s,
134 Err(e) if e.raw_os_error() == Some(10) => {
135 return Ok(root_exit_code.unwrap_or(0));
136 }
137 Err(e) => return Err(e),
138 };
139
140 match status {
141 WaitStatus::PtraceSyscall(pid) => {
142 let regs = platform::get_registers(pid)?;
143 let raw_pid = pid.as_raw();
144
145 if !self.in_syscall.get(&raw_pid).copied().unwrap_or(false) {
146 self.in_syscall.insert(raw_pid, true);
148 let (number, args) = arch::extract_syscall_entry(®s);
149 let name = arch::syscall_name(number);
150 let decoded_args = arch::decode_entry_args(pid, number, &args);
151 let event = SyscallEvent {
152 pid: raw_pid,
153 number,
154 name,
155 args,
156 ret: None,
157 decoded_args,
158 };
159 self.pending_entry.insert(raw_pid, event);
160 } else {
161 self.in_syscall.insert(raw_pid, false);
163 if let Some(mut event) = self.pending_entry.remove(&raw_pid) {
164 let ret = arch::extract_return_value(®s);
165 event.ret = Some(ret);
166 arch::decode_exit_args(
167 pid,
168 event.number,
169 &event.args,
170 ret,
171 &mut event.decoded_args,
172 );
173 on_event(&event);
174 }
175 }
176 platform::syscall_continue(pid, None)?;
177 }
178 WaitStatus::PtraceEvent(pid, _sig, event) => {
179 if event == nix::libc::PTRACE_EVENT_FORK as i32
180 || event == nix::libc::PTRACE_EVENT_VFORK as i32
181 || event == nix::libc::PTRACE_EVENT_CLONE as i32
182 {
183 if let Ok(new_pid) = platform::get_event(pid) {
185 let new_pid_raw = new_pid as i32;
186 self.traced.insert(new_pid_raw);
187 let new_pid_nix = Pid::from_raw(new_pid_raw);
188 let _ = platform::wait(new_pid_nix);
190 let _ = platform::set_options(
191 new_pid_nix,
192 Self::ptrace_options(&self.options),
193 );
194 let _ = platform::syscall_continue(new_pid_nix, None);
195 }
196 } else if event == nix::libc::PTRACE_EVENT_EXEC as i32 {
197 }
201 platform::syscall_continue(pid, None)?;
202 }
203 WaitStatus::Exited(pid, code) => {
204 let raw_pid = pid.as_raw();
205 self.traced.remove(&raw_pid);
206 self.in_syscall.remove(&raw_pid);
207 self.pending_entry.remove(&raw_pid);
208 if pid == self.root_pid {
209 root_exit_code = Some(code);
210 }
211 if !self.options.follow_forks || self.traced.is_empty() {
212 return Ok(root_exit_code.unwrap_or(code));
213 }
214 }
215 WaitStatus::Signaled(pid, sig, _core_dumped) => {
216 let raw_pid = pid.as_raw();
217 self.traced.remove(&raw_pid);
218 self.in_syscall.remove(&raw_pid);
219 self.pending_entry.remove(&raw_pid);
220 if pid == self.root_pid {
221 root_exit_code = Some(128 + sig as i32);
222 }
223 if !self.options.follow_forks || self.traced.is_empty() {
224 return Ok(root_exit_code.unwrap_or(128 + sig as i32));
225 }
226 }
227 WaitStatus::Stopped(pid, sig) => {
228 platform::syscall_continue(pid, Some(sig))?;
230 }
231 _ => {
232 }
234 }
235 }
236 }
237}
238
239impl Drop for Tracer {
240 fn drop(&mut self) {
241 if self.attached {
243 for &pid in &self.traced {
244 let _ = platform::detach(Pid::from_raw(pid), None);
245 }
246 }
247 }
248}