1#![doc(html_root_url = "https://sfackler.github.io/rstack/doc")]
52#![warn(missing_docs)]
53
54use antidote::Mutex;
55use lazy_static::lazy_static;
56use libc::{c_ulong, getppid, prctl, PR_SET_PTRACER};
57use serde::{Deserialize, Serialize};
58use std::error;
59use std::fmt;
60use std::io::{self, BufReader, Read, Write};
61use std::path::{Path, PathBuf};
62use std::process::{Child, Command, Stdio};
63use std::result;
64
65lazy_static! {
66 static ref TRACE_LOCK: Mutex<()> = Mutex::new(());
67}
68
69pub type Result<T> = result::Result<T, Error>;
71
72#[derive(Debug)]
74pub struct Error(Box<dyn error::Error + Sync + Send>);
75
76impl fmt::Display for Error {
77 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
78 fmt::Display::fmt(&self.0, fmt)
79 }
80}
81
82impl error::Error for Error {
83 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
84 error::Error::source(&*self.0)
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct Trace {
91 threads: Vec<Thread>,
92}
93
94impl Trace {
95 pub fn threads(&self) -> &[Thread] {
97 &self.threads
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct Thread {
104 id: u32,
105 name: String,
106 frames: Vec<Frame>,
107}
108
109impl Thread {
110 pub fn id(&self) -> u32 {
112 self.id
113 }
114
115 pub fn name(&self) -> &str {
117 &self.name
118 }
119
120 pub fn frames(&self) -> &[Frame] {
122 &self.frames
123 }
124}
125
126#[derive(Debug, Clone)]
128pub struct Frame {
129 ip: usize,
130 symbols: Vec<Symbol>,
131}
132
133impl Frame {
134 pub fn ip(&self) -> usize {
136 self.ip
137 }
138
139 pub fn symbols(&self) -> &[Symbol] {
143 &self.symbols
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct Symbol {
150 name: Option<String>,
151 file: Option<PathBuf>,
152 line: Option<u32>,
153}
154
155impl Symbol {
156 pub fn name(&self) -> Option<&str> {
158 self.name.as_ref().map(|n| &**n)
159 }
160
161 pub fn file(&self) -> Option<&Path> {
163 self.file.as_ref().map(|f| &**f)
164 }
165
166 pub fn line(&self) -> Option<u32> {
168 self.line
169 }
170}
171
172#[derive(Debug, Clone)]
174pub struct TraceOptions {
175 snapshot: bool,
176}
177
178impl Default for TraceOptions {
179 fn default() -> TraceOptions {
180 TraceOptions { snapshot: false }
181 }
182}
183
184impl TraceOptions {
185 pub fn new() -> TraceOptions {
187 TraceOptions::default()
188 }
189
190 pub fn snapshot(&mut self, snapshot: bool) -> &mut TraceOptions {
197 self.snapshot = snapshot;
198 self
199 }
200
201 pub fn trace(&self, child: &mut Command) -> Result<Trace> {
211 let raw = self.trace_raw(child)?;
212 let threads = symbolicate(raw);
213 Ok(Trace { threads })
214 }
215
216 fn trace_raw(&self, child: &mut Command) -> Result<Vec<RawThread>> {
217 let _guard = TRACE_LOCK.lock();
218
219 let child = child
220 .stdin(Stdio::piped())
221 .stdout(Stdio::piped())
222 .stderr(Stdio::inherit())
223 .spawn()
224 .map_err(|e| Error(e.into()))?;
225 let mut child = ChildGuard(child);
226
227 let mut stdin = child.0.stdin.take().unwrap();
228 let mut stdout = BufReader::new(child.0.stdout.take().unwrap());
229
230 let _guard = PtracerGuard::new(child.0.id()).map_err(|e| Error(e.into()))?;
231
232 let options = RawOptions {
233 snapshot: self.snapshot,
234 };
235 bincode::serialize_into(&mut stdin, &options).map_err(|e| Error(e.into()))?;
236
237 let raw: result::Result<Vec<RawThread>, String> =
238 bincode::deserialize_from(&mut stdout).map_err(|e| Error(e.into()))?;
239 let raw = raw.map_err(|e| Error(e.into()))?;
240
241 Ok(raw)
242 }
243}
244
245pub fn trace(child: &mut Command) -> Result<Trace> {
247 TraceOptions::new().trace(child)
248}
249
250struct ChildGuard(Child);
251
252impl Drop for ChildGuard {
253 fn drop(&mut self) {
254 let _ = self.0.kill();
255 let _ = self.0.wait();
256 }
257}
258
259struct PtracerGuard(bool);
260
261impl Drop for PtracerGuard {
262 fn drop(&mut self) {
263 if self.0 {
264 let _ = set_ptracer(0);
265 }
266 }
267}
268
269impl PtracerGuard {
270 fn new(pid: u32) -> io::Result<PtracerGuard> {
271 match set_ptracer(pid) {
272 Ok(()) => Ok(PtracerGuard(true)),
273 Err(ref e) if e.kind() == io::ErrorKind::InvalidInput => Ok(PtracerGuard(false)),
274 Err(e) => Err(e),
275 }
276 }
277}
278
279fn set_ptracer(pid: u32) -> io::Result<()> {
280 unsafe {
281 let r = prctl(PR_SET_PTRACER, pid as c_ulong, 0, 0, 0);
282 if r != 0 {
283 Err(io::Error::last_os_error().into())
284 } else {
285 Ok(())
286 }
287 }
288}
289
290fn symbolicate(raw: Vec<RawThread>) -> Vec<Thread> {
291 raw.into_iter().map(symbolicate_thread).collect()
292}
293
294fn symbolicate_thread(raw: RawThread) -> Thread {
295 let mut thread = Thread {
296 id: raw.id,
297 name: raw.name,
298 frames: vec![],
299 };
300
301 for raw_frame in raw.frames {
302 let mut frame = Frame {
303 ip: raw_frame.ip as usize,
304 symbols: vec![],
305 };
306
307 let current_ip = if raw_frame.is_signal || raw_frame.ip == 0 {
308 raw_frame.ip
309 } else {
310 raw_frame.ip - 1
311 };
312 backtrace::resolve(current_ip as *mut _, |symbol| {
313 frame.symbols.push(Symbol {
314 name: symbol.name().map(|s| s.to_string()),
315 file: symbol.filename().map(|p| p.to_owned()),
316 line: symbol.lineno(),
317 });
318 });
319
320 thread.frames.push(frame);
321 }
322
323 thread
324}
325
326#[derive(Serialize, Deserialize)]
327struct RawOptions {
328 snapshot: bool,
329}
330
331#[derive(Serialize, Deserialize)]
332struct RawThread {
333 id: u32,
334 name: String,
335 frames: Vec<RawFrame>,
336}
337
338#[derive(Serialize, Deserialize)]
339struct RawFrame {
340 ip: u64,
341 is_signal: bool,
342}
343
344pub fn child() -> Result<()> {
348 let mut stdin = io::stdin();
349 let mut stdout = io::stdout();
350
351 let options = bincode::deserialize_from(&mut stdin).map_err(|e| Error(e.into()))?;
352
353 let trace = child_trace(&options);
354 bincode::serialize_into(&mut stdout, &trace).map_err(|e| Error(e.into()))?;
355 stdout.flush().map_err(|e| Error(e.into()))?;
356
357 let mut buf = [0];
359 let _ = stdin.read_exact(&mut buf);
360 Ok(())
361}
362
363fn child_trace(options: &RawOptions) -> result::Result<Vec<RawThread>, String> {
364 let parent = unsafe { getppid() } as u32;
365
366 match rstack::TraceOptions::new()
367 .thread_names(true)
368 .snapshot(options.snapshot)
369 .trace(parent)
370 {
371 Ok(process) => Ok(process
372 .threads()
373 .iter()
374 .map(|thread| RawThread {
375 id: thread.id(),
376 name: thread.name().unwrap_or("<unknown>").to_string(),
377 frames: thread
378 .frames()
379 .iter()
380 .map(|f| RawFrame {
381 ip: f.ip(),
382 is_signal: f.is_signal(),
383 })
384 .collect(),
385 })
386 .collect()),
387 Err(e) => Err(e.to_string()),
388 }
389}