use std::cell::Cell;
use std::io::Write;
use std::path::Path;
use std::process::exit;
use ptools::proc::ProcHandle;
use ptools::stack::SourceLocation;
use ptools::stack::Thread;
fn format_source(src: &SourceLocation) -> String {
let basename = Path::new(src.file())
.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_else(|| src.file().into());
if src.line() > 0 {
format!("{basename}:{}", src.line())
} else {
basename.into_owned()
}
}
fn print_thread(
thread: &Thread,
tid_filter: Option<u64>,
max_frames: usize,
show_header: bool,
first: &Cell<bool>,
) {
if let Some(tid) = tid_filter {
if thread.id() as u64 != tid {
return;
}
}
if !first.get() {
println!();
}
first.set(false);
if show_header {
println!("{}:\t{}", thread.id(), thread.name().unwrap_or("<unknown>"));
}
for frame in thread.frames() {
print!("{:#018x}", frame.ip());
if let Some(symbol) = frame.symbol() {
print!(" {}", symbol.name());
if frame.is_inline() {
print!(" [inlined]");
} else {
print!("+{:#x}", symbol.offset());
}
} else {
print!(" - ???");
}
if let Some(module) = frame.module() {
print!(" in {module}");
}
if let Some(src) = frame.source() {
print!(" ({})", format_source(src));
}
println!();
}
if max_frames > 0 && thread.frames().len() >= max_frames {
eprintln!("pstack: maximum number of frames exceeded (use -n 0 for unlimited)");
}
std::io::stdout().flush().ok();
}
fn print_stack(
handle: &ProcHandle,
tid_filter: Option<u64>,
is_core: bool,
args: &Args,
) -> std::io::Result<()> {
let mut opts = ptools::stack::TraceOptions::new();
let opts = opts
.thread_names(true)
.symbols(true)
.demangle(true)
.module(args.module)
.source(args.verbose)
.inlines(args.verbose)
.max_frames(args.max_frames);
let first_thread = Cell::new(true);
if is_core {
opts.trace_core_each(
handle,
|pid| {
print!("{pid}:\t");
println!("{}", ptools::display::cmd_summary_from(handle));
println!();
first_thread.set(true);
},
|thread| print_thread(&thread, tid_filter, args.max_frames, true, &first_thread),
)?;
} else if let Some(tid) = tid_filter {
opts.tid(tid as u32);
print!("{tid}:\t");
println!("{}", ptools::display::cmd_summary_from(handle));
opts.trace_each(handle, |thread| {
print_thread(&thread, Some(tid), args.max_frames, false, &first_thread);
})?;
} else {
let multithread = handle.thread_count().is_ok_and(|n| n > 1);
ptools::display::print_proc_summary_from(handle);
if multithread {
println!();
}
opts.trace_each(handle, |thread| {
print_thread(&thread, None, args.max_frames, multithread, &first_thread);
})?;
}
Ok(())
}
const DEFAULT_MAX_FRAMES: usize = 64;
struct Args {
verbose: bool,
module: bool,
max_frames: usize,
operands: Vec<String>,
}
fn print_usage() {
eprintln!("Usage: pstack [-mv] [-n count] [pid[/thread] | core]...");
eprintln!("Print stack traces of running processes or core dumps.");
eprintln!();
eprintln!("Options:");
eprintln!(" -m, --module Show module file paths");
eprintln!(" -n N Print at most N frames per thread (0 for unlimited)");
eprintln!(" -v, --verbose Show source locations and inline frames");
eprintln!(" -h, --help Print help");
eprintln!(" -V, --version Print version");
}
fn parse_args() -> Args {
use lexopt::prelude::*;
let mut args = Args {
module: false,
verbose: false,
max_frames: DEFAULT_MAX_FRAMES,
operands: Vec::new(),
};
let mut parser = lexopt::Parser::from_env();
while let Some(arg) = parser.next().unwrap_or_else(|e| {
eprintln!("pstack: {e}");
exit(2);
}) {
match arg {
Short('m') | Long("module") => {
args.module = true;
}
Short('n') => {
let val: String = parser
.value()
.unwrap_or_else(|e| {
eprintln!("pstack: {e}");
exit(2);
})
.to_string_lossy()
.into_owned();
args.max_frames = val.parse().unwrap_or_else(|e| {
eprintln!("pstack: -n: {e}");
exit(2);
});
}
Short('v') | Long("verbose") => {
args.verbose = true;
}
Short('h') | Long("help") => {
print_usage();
exit(0);
}
Short('V') | Long("version") => {
println!("pstack {}", env!("CARGO_PKG_VERSION"));
exit(0);
}
Value(val) => {
args.operands.push(val.to_string_lossy().into_owned());
}
_ => {
eprintln!("pstack: unexpected argument: {arg:?}");
exit(2);
}
}
}
if args.operands.is_empty() {
eprintln!("pstack: at least one operand required");
exit(2);
}
args
}
fn main() {
ptools::reset_sigpipe();
let args = parse_args();
let mut error = false;
let mut first = true;
for operand in &args.operands {
if ptools::proc::is_non_pid_proc_path(operand) {
continue;
}
if !first {
println!();
}
first = false;
match ptools::proc::resolve_operand_with_tid(operand) {
Ok((handle, tid)) => {
if let Err(e) = print_stack(&handle, tid, handle.is_core(), &args) {
eprintln!("pstack: {}: {e}", handle.pid());
error = true;
}
}
Err(e) => {
eprintln!("pstack: {e}");
error = true;
}
}
}
if error {
exit(1);
}
}