Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
- changed-files:
- any-glob-to-any-file: src/bin/psig.rs

"Component: pstack":
- changed-files:
- any-glob-to-any-file: src/bin/pstack.rs

"Component: pstop":
- changed-files:
- any-glob-to-any-file: src/bin/pstop.rs
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ jobs:
target/debug/pargs --object target/debug/pauxv --object target/debug/pcred \
--object target/debug/penv \
--object target/debug/pfiles --object target/debug/plgrp --object target/debug/prun \
--object target/debug/psig --object target/debug/pstop --object target/debug/ptree \
--object target/debug/psig --object target/debug/pstack --object target/debug/pstop \
--object target/debug/ptree \
--object target/debug/pwait

build-i686:
Expand Down Expand Up @@ -143,5 +144,6 @@ jobs:
"${target_dir}/pargs" --object "${target_dir}/pauxv" --object "${target_dir}/pcred" \
--object "${target_dir}/penv" \
--object "${target_dir}/pfiles" --object "${target_dir}/plgrp" --object "${target_dir}/prun" \
--object "${target_dir}/psig" --object "${target_dir}/pstop" --object "${target_dir}/ptree" \
--object "${target_dir}/psig" --object "${target_dir}/pstack" --object "${target_dir}/pstop" \
--object "${target_dir}/ptree" \
--object "${target_dir}/pwait"
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ $ llvm-cov report --ignore-filename-regex='/(\.cargo/registry|rustc)/' \
target/debug/pargs --object target/debug/pauxv --object target/debug/pcred \
--object target/debug/penv --object target/debug/pfiles \
--object target/debug/plgrp --object target/debug/prun \
--object target/debug/psig --object target/debug/pstop \
--object target/debug/psig --object target/debug/pstack \
--object target/debug/pstop \
--object target/debug/ptree --object target/debug/pwait
$ llvm-cov export --format=lcov \
--instr-profile=target/coverage/ptools.profdata \
target/debug/pargs --object target/debug/pauxv --object target/debug/pcred \
--object target/debug/penv --object target/debug/pfiles \
--object target/debug/plgrp --object target/debug/prun \
--object target/debug/psig --object target/debug/pstop \
--object target/debug/psig --object target/debug/pstack \
--object target/debug/pstop \
--object target/debug/ptree --object target/debug/pwait > target/coverage/lcov.info
```

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ assets = [
["target/release/plgrp", "usr/bin/", "755"],
["target/release/prun", "usr/bin/", "755"],
["target/release/psig", "usr/bin/", "755"],
["target/release/pstack", "usr/bin/", "755"],
["target/release/pstop", "usr/bin/", "755"],
["target/release/ptree", "usr/bin/", "755"],
["target/release/pwait", "usr/bin/", "755"],
Expand All @@ -62,6 +63,7 @@ assets = [
["target/man/plgrp.1", "usr/share/man/man1/plgrp.1", "644"],
["target/man/prun.1", "usr/share/man/man1/prun.1", "644"],
["target/man/psig.1", "usr/share/man/man1/psig.1", "644"],
["target/man/pstack.1", "usr/share/man/man1/pstack.1", "644"],
["target/man/pstop.1", "usr/share/man/man1/pstop.1", "644"],
["target/man/ptree.1", "usr/share/man/man1/ptree.1", "644"],
["target/man/pwait.1", "usr/share/man/man1/pwait.1", "644"],
Expand All @@ -79,6 +81,7 @@ assets = [
{ source = "target/release/plgrp", dest = "/usr/bin/plgrp", mode = "755" },
{ source = "target/release/prun", dest = "/usr/bin/prun", mode = "755" },
{ source = "target/release/psig", dest = "/usr/bin/psig", mode = "755" },
{ source = "target/release/pstack", dest = "/usr/bin/pstack", mode = "755" },
{ source = "target/release/pstop", dest = "/usr/bin/pstop", mode = "755" },
{ source = "target/release/ptree", dest = "/usr/bin/ptree", mode = "755" },
{ source = "target/release/pwait", dest = "/usr/bin/pwait", mode = "755" },
Expand All @@ -90,6 +93,7 @@ assets = [
{ source = "target/man/plgrp.1", dest = "/usr/share/man/man1/plgrp.1", mode = "644" },
{ source = "target/man/prun.1", dest = "/usr/share/man/man1/prun.1", mode = "644" },
{ source = "target/man/psig.1", dest = "/usr/share/man/man1/psig.1", mode = "644" },
{ source = "target/man/pstack.1", dest = "/usr/share/man/man1/pstack.1", mode = "644" },
{ source = "target/man/pstop.1", dest = "/usr/share/man/man1/pstop.1", mode = "644" },
{ source = "target/man/ptree.1", dest = "/usr/share/man/man1/ptree.1", mode = "644" },
{ source = "target/man/pwait.1", dest = "/usr/share/man/man1/pwait.1", mode = "644" },
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ project. Tools provided by [procps-ng](https://gitlab.com/procps-ng/procps),
are not reimplemented here, as these packages are widely available on Linux
distributions and already provide equivalent functionality. There are a number
of commands available on Solaris/illumos which have not
been implemented here yet, perhaps most notably `pstack(1)`.
been implemented here yet.

| Command | Description | Status |
| ------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------- |
Expand All @@ -218,7 +218,7 @@ been implemented here yet, perhaps most notably `pstack(1)`.
| [`prun(1)`](https://illumos.org/man/1/prun) | Set stopped processes running with `SIGCONT` | ✅ Implemented |
| [`psecflags(1)`](https://illumos.org/man/1/psecflags) | Print or modify process security flags | 🔲 Not yet implemented |
| [`psig(1)`](https://illumos.org/man/1/psig) | Print process signal actions | ✅ Implemented |
| [`pstack(1)`](https://illumos.org/man/1/pstack) | Print process call stack | 🔲 Not yet implemented |
| [`pstack(1)`](https://illumos.org/man/1/pstack) | Print process call stack | ✅ Implemented |
| [`pstop(1)`](https://illumos.org/man/1/pstop) | Stop processes with `SIGSTOP` | ✅ Implemented |
| [`ptime(1)`](https://illumos.org/man/1/ptime) | Time a process using microstate accounting | 🔲 Not yet implemented |
| [`ptree(1)`](https://illumos.org/man/1/ptree) | Print process trees | ✅ Implemented |
Expand Down
34 changes: 34 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,5 +554,39 @@ $ plgrp -a 0-2 101398
out_dir,
);

render_man_page(
&ManPage {
name: "pstack",
about: "print stack traces of a running process",
description: "Print the stack backtraces of all threads in each running process. \
It attaches to the target using the ptrace(2) debugging interface. \
The first line of output displays the PID and binary name as reported by \
the kernel. For each thread, the thread ID and name is displayed followed \
by its backtrace. Each frame shows an address and a symbol with offset.",
synopsis: "[pid[/tid]]...",
options: &[],
operands: &[(
"pid[/tid]",
"Process ID, optionally followed by a slash and a thread ID \
to display a single thread.",
)],
examples: &[],
exit_status: DEFAULT_EXIT_STATUS,
files: DEFAULT_FILES,
notes: "pstack(1) only works on processes executing ELF binaries. \
A process cannot be traced if another debugger is already attached to it. \
The ptrace(2) interface used to obtain live process information may cause \
some syscalls in the target to return EINTR on detach.",
see_also: "ptrace(2), proc(5)",
warnings: "pstack stops the entire target process while inspecting it, even if \
invoked against an individual thread. The process can do nothing while \
stopped. Stopping a heavily loaded process in a production environment, \
even briefly, can cause severe bottlenecks or hangs, making the process \
unavailable to users. Some applications, such as database servers, may \
terminate abnormally. Use caution when tracing production processes.",
},
out_dir,
);

println!("cargo:rerun-if-changed=build.rs");
}
128 changes: 100 additions & 28 deletions src/bin/pstack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,22 @@
// limitations under the License.
//

use std::env;
use std::process;
use std::process::exit;

fn main() {
let args = env::args().collect::<Vec<_>>();
if args.len() != 2 {
eprintln!("Usage: {} <pid>", args[0]);
process::exit(1);
}

let pid = match args[1].parse() {
Ok(pid) => pid,
Err(e) => {
eprintln!("error parsing PID: {}", e);
process::exit(1);
}
};
use ptools::ProcHandle;

let process = match ptools::stack::trace(pid) {
Ok(threads) => threads,
Err(e) => {
eprintln!("error tracing threads: {}", e);
process::exit(1);
}
};
fn print_stack(handle: &ProcHandle, tid_filter: Option<u64>) -> Result<(), ptools::stack::Error> {
let process = ptools::stack::trace(handle.pid() as u32)?;

ptools::print_proc_summary_from(handle);
println!();
for thread in process.threads() {
println!(
"thread {} - {}",
thread.id(),
thread.name().unwrap_or("<unknown>")
);
if let Some(tid) = tid_filter {
if thread.id() as u64 != tid {
continue;
}
}
println!("{}: {}", thread.id(), thread.name().unwrap_or("<unknown>"));
for frame in thread.frames() {
match frame.symbol() {
Some(symbol) => println!(
Expand All @@ -59,4 +43,92 @@ fn main() {
}
println!();
}

Ok(())
}

struct Args {
operands: Vec<String>,
}

fn print_usage() {
eprintln!("Usage: pstack [pid[/thread]]...");
eprintln!("Print stack traces of running processes.");
eprintln!();
eprintln!("Options:");
eprintln!(" -h, --help Print help");
eprintln!(" -V, --version Print version");
}

fn parse_args() -> Args {
use lexopt::prelude::*;

let mut args = Args {
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('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 PID 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 !first {
println!();
}
first = false;
let (handle, tid) = match ptools::resolve_operand_with_tid(operand) {
Ok(r) => r,
Err(e) => {
eprintln!("pstack: {e}");
error = true;
continue;
}
};
for w in handle.drain_warnings() {
eprintln!("{w}");
}
if let Err(e) = print_stack(&handle, tid) {
eprintln!("pstack: {}: {e}", handle.pid());
error = true;
}
for w in handle.drain_warnings() {
eprintln!("{w}");
}
}
if error {
exit(1);
}
}
Loading