#![doc(html_root_url = "https://sfackler.github.io/rstack/doc")]
#![warn(missing_docs)]
use antidote::Mutex;
use lazy_static::lazy_static;
use libc::{c_ulong, getppid, prctl, PR_SET_PTRACER};
use serde::{Deserialize, Serialize};
use std::error;
use std::fmt;
use std::io::{self, BufReader, Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::result;
lazy_static! {
static ref TRACE_LOCK: Mutex<()> = Mutex::new(());
}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub struct Error(Box<dyn error::Error + Sync + Send>);
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, fmt)
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
error::Error::source(&*self.0)
}
}
#[derive(Debug, Clone)]
pub struct Trace {
threads: Vec<Thread>,
}
impl Trace {
pub fn threads(&self) -> &[Thread] {
&self.threads
}
}
#[derive(Debug, Clone)]
pub struct Thread {
id: u32,
name: String,
frames: Vec<Frame>,
}
impl Thread {
pub fn id(&self) -> u32 {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn frames(&self) -> &[Frame] {
&self.frames
}
}
#[derive(Debug, Clone)]
pub struct Frame {
ip: usize,
symbols: Vec<Symbol>,
}
impl Frame {
pub fn ip(&self) -> usize {
self.ip
}
pub fn symbols(&self) -> &[Symbol] {
&self.symbols
}
}
#[derive(Debug, Clone)]
pub struct Symbol {
name: Option<String>,
file: Option<PathBuf>,
line: Option<u32>,
}
impl Symbol {
pub fn name(&self) -> Option<&str> {
self.name.as_ref().map(|n| &**n)
}
pub fn file(&self) -> Option<&Path> {
self.file.as_ref().map(|f| &**f)
}
pub fn line(&self) -> Option<u32> {
self.line
}
}
#[derive(Debug, Clone)]
pub struct TraceOptions {
snapshot: bool,
}
impl Default for TraceOptions {
fn default() -> TraceOptions {
TraceOptions { snapshot: false }
}
}
impl TraceOptions {
pub fn new() -> TraceOptions {
TraceOptions::default()
}
pub fn snapshot(&mut self, snapshot: bool) -> &mut TraceOptions {
self.snapshot = snapshot;
self
}
pub fn trace(&self, child: &mut Command) -> Result<Trace> {
let raw = self.trace_raw(child)?;
let threads = symbolicate(raw);
Ok(Trace { threads })
}
fn trace_raw(&self, child: &mut Command) -> Result<Vec<RawThread>> {
let _guard = TRACE_LOCK.lock();
let child = child
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| Error(e.into()))?;
let mut child = ChildGuard(child);
let mut stdin = child.0.stdin.take().unwrap();
let mut stdout = BufReader::new(child.0.stdout.take().unwrap());
let _guard = PtracerGuard::new(child.0.id()).map_err(|e| Error(e.into()))?;
let options = RawOptions {
snapshot: self.snapshot,
};
bincode::serialize_into(&mut stdin, &options).map_err(|e| Error(e.into()))?;
let raw: result::Result<Vec<RawThread>, String> =
bincode::deserialize_from(&mut stdout).map_err(|e| Error(e.into()))?;
let raw = raw.map_err(|e| Error(e.into()))?;
Ok(raw)
}
}
pub fn trace(child: &mut Command) -> Result<Trace> {
TraceOptions::new().trace(child)
}
struct ChildGuard(Child);
impl Drop for ChildGuard {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
struct PtracerGuard(bool);
impl Drop for PtracerGuard {
fn drop(&mut self) {
if self.0 {
let _ = set_ptracer(0);
}
}
}
impl PtracerGuard {
fn new(pid: u32) -> io::Result<PtracerGuard> {
match set_ptracer(pid) {
Ok(()) => Ok(PtracerGuard(true)),
Err(ref e) if e.kind() == io::ErrorKind::InvalidInput => Ok(PtracerGuard(false)),
Err(e) => Err(e),
}
}
}
fn set_ptracer(pid: u32) -> io::Result<()> {
unsafe {
let r = prctl(PR_SET_PTRACER, pid as c_ulong, 0, 0, 0);
if r != 0 {
Err(io::Error::last_os_error().into())
} else {
Ok(())
}
}
}
fn symbolicate(raw: Vec<RawThread>) -> Vec<Thread> {
raw.into_iter().map(symbolicate_thread).collect()
}
fn symbolicate_thread(raw: RawThread) -> Thread {
let mut thread = Thread {
id: raw.id,
name: raw.name,
frames: vec![],
};
for raw_frame in raw.frames {
let mut frame = Frame {
ip: raw_frame.ip as usize,
symbols: vec![],
};
let current_ip = if raw_frame.is_signal || raw_frame.ip == 0 {
raw_frame.ip
} else {
raw_frame.ip - 1
};
backtrace::resolve(current_ip as *mut _, |symbol| {
frame.symbols.push(Symbol {
name: symbol.name().map(|s| s.to_string()),
file: symbol.filename().map(|p| p.to_owned()),
line: symbol.lineno(),
});
});
thread.frames.push(frame);
}
thread
}
#[derive(Serialize, Deserialize)]
struct RawOptions {
snapshot: bool,
}
#[derive(Serialize, Deserialize)]
struct RawThread {
id: u32,
name: String,
frames: Vec<RawFrame>,
}
#[derive(Serialize, Deserialize)]
struct RawFrame {
ip: u64,
is_signal: bool,
}
pub fn child() -> Result<()> {
let mut stdin = io::stdin();
let mut stdout = io::stdout();
let options = bincode::deserialize_from(&mut stdin).map_err(|e| Error(e.into()))?;
let trace = child_trace(&options);
bincode::serialize_into(&mut stdout, &trace).map_err(|e| Error(e.into()))?;
stdout.flush().map_err(|e| Error(e.into()))?;
let mut buf = [0];
let _ = stdin.read_exact(&mut buf);
Ok(())
}
fn child_trace(options: &RawOptions) -> result::Result<Vec<RawThread>, String> {
let parent = unsafe { getppid() } as u32;
match rstack::TraceOptions::new()
.thread_names(true)
.snapshot(options.snapshot)
.trace(parent)
{
Ok(process) => Ok(process
.threads()
.iter()
.map(|thread| RawThread {
id: thread.id(),
name: thread.name().unwrap_or("<unknown>").to_string(),
frames: thread
.frames()
.iter()
.map(|f| RawFrame {
ip: f.ip(),
is_signal: f.is_signal(),
})
.collect(),
})
.collect()),
Err(e) => Err(e.to_string()),
}
}