rstack_self/
lib.rs

1//! Retrieve stack traces of all threads of the process.
2//!
3//! This is implemented using the [rstack] crate, which itself uses ptrace to unwind the threads of the process.
4//! Because processes cannot ptrace themselves, we're forced to use spawn a child process which does that work. Multiple
5//! unwinding implementations are supported via Cargo features:
6//!
7//! * `unwind`: Uses [libunwind].
8//! * `dw`: Uses libdw, part of the [elfutils] project.
9//!
10//! By default, the libunwind backend is used. You can switch to libdw via Cargo:
11//!
12//! ```toml
13//! [dependencies]
14//! rstack-self = { version = "0.1", features = ["dw"], default-features = false }
15//! ```
16//!
17//! [rstack]: https://sfackler.github.io/rstack/doc/rstack
18//! [libunwind]: http://www.nongnu.org/libunwind/
19//! [elfutils]: https://sourceware.org/elfutils/
20//!
21//! # Example
22//!
23//! ```
24//! extern crate rstack_self;
25//!
26//! use std::env;
27//! use std::process::Command;
28//! use std::thread;
29//!
30//! fn main() {
31//!     if env::args_os().count() > 1 {
32//!         let _ = rstack_self::child();
33//!         return;
34//!     }
35//!
36//!     // spawn a second thread just for fun
37//!     thread::spawn(background_thread);
38//!
39//!     let exe = env::current_exe().unwrap();
40//!     let trace = rstack_self::trace(Command::new(exe).arg("child")).unwrap();
41//!
42//!     println!("{:#?}", trace);
43//! }
44//!
45//! fn background_thread() {
46//!     loop {
47//!         thread::park();
48//!     }
49//! }
50//! ```
51#![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
69/// The result type returned by methods in this crate.
70pub type Result<T> = result::Result<T, Error>;
71
72/// The error type returned by methods in this crate.
73#[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/// A trace of the threads in a process.
89#[derive(Debug, Clone)]
90pub struct Trace {
91    threads: Vec<Thread>,
92}
93
94impl Trace {
95    /// Returns the threads in the trace.
96    pub fn threads(&self) -> &[Thread] {
97        &self.threads
98    }
99}
100
101/// Information about a thread.
102#[derive(Debug, Clone)]
103pub struct Thread {
104    id: u32,
105    name: String,
106    frames: Vec<Frame>,
107}
108
109impl Thread {
110    /// Returns the thread's ID.
111    pub fn id(&self) -> u32 {
112        self.id
113    }
114
115    /// Returns the thread's name.
116    pub fn name(&self) -> &str {
117        &self.name
118    }
119
120    /// Returns the stack frames of the thread.
121    pub fn frames(&self) -> &[Frame] {
122        &self.frames
123    }
124}
125
126/// Information about a stack frame.
127#[derive(Debug, Clone)]
128pub struct Frame {
129    ip: usize,
130    symbols: Vec<Symbol>,
131}
132
133impl Frame {
134    /// Returns the instruction pointer of the frame.
135    pub fn ip(&self) -> usize {
136        self.ip
137    }
138
139    /// Returns the symbols resolved to this frame.
140    ///
141    /// Multiple symbols can be returned due to inlining.
142    pub fn symbols(&self) -> &[Symbol] {
143        &self.symbols
144    }
145}
146
147/// Information about a symbol.
148#[derive(Debug, Clone)]
149pub struct Symbol {
150    name: Option<String>,
151    file: Option<PathBuf>,
152    line: Option<u32>,
153}
154
155impl Symbol {
156    /// Returns the name of the symbol, if known.
157    pub fn name(&self) -> Option<&str> {
158        self.name.as_ref().map(|n| &**n)
159    }
160
161    /// Returns the file in which this symbol is defined, if known.
162    pub fn file(&self) -> Option<&Path> {
163        self.file.as_ref().map(|f| &**f)
164    }
165
166    /// Returns the line at which the address which resolved to this symbol corresponds, if known.
167    pub fn line(&self) -> Option<u32> {
168        self.line
169    }
170}
171
172/// Options controlling tracing.
173#[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    /// Returns a new `TraceOptions` with default settings.
186    pub fn new() -> TraceOptions {
187        TraceOptions::default()
188    }
189
190    /// If set, the threads of the process will be traced in a consistent snapshot.
191    ///
192    /// A snapshot-mode trace ensures a consistent view of all threads, but requires that all
193    /// threads be paused for the entire duration of the trace.
194    ///
195    /// Defaults to `false`.
196    pub fn snapshot(&mut self, snapshot: bool) -> &mut TraceOptions {
197        self.snapshot = snapshot;
198        self
199    }
200
201    /// Returns stack traces of all of the threads in the calling process.
202    ///
203    /// The provided `Command` should be configured to spawn a process which will call the [`child`]
204    /// function. It must not use standard input or standard output, but standard error will be
205    /// inherited and can be used. The spawned process must "directly" call `child` rather than
206    /// spawning another process to call it. That is, the parent of the process that calls `child` is
207    /// the one that will be traced.
208    ///
209    /// [`child`]: fn.child.html
210    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
245/// A convenience wrapper over `TraceOptions` which uses default options.
246pub 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
344/// The function called by the process spawned by a call to [`trace`].
345///
346/// [`trace`]: fn.trace.html
347pub 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    // wait around for the parent to kill us, or die
358    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}