Skip to content

Latest commit

 

History

History
160 lines (127 loc) · 6.71 KB

File metadata and controls

160 lines (127 loc) · 6.71 KB

Project Instructions for AI Agents

This file provides instructions and context for AI coding agents working on this project.

Build & Test

From CONTRIBUTING.md:

cargo build                              # Build all binaries and examples
cargo test                               # Run all tests
cargo fmt --all -- --check               # Check formatting
cargo clippy --all-targets --all-features  # Lint

The integration tests spawn example programs from examples/ as target processes. The tests locate these example binaries via find_exec() in tests/common/mod.rs, which expects them to already be built in the Cargo output directory. You must build before running tests:

cargo build              # Build binaries and examples first
cargo test               # Then run the tests

Running cargo test alone may fail if the example binaries have not been built yet.

You can also run individual tools directly:

./target/debug/ptree 1
./target/debug/pfiles $$

Architecture Overview

ptools is a collection of Linux command-line utilities for inspecting process state, inspired by the Solaris/illumos tools of the same name. Each tool is a separate binary in src/bin/. All tools work on both live processes (via /proc) and core dumps (via systemd-coredump journal entries), sharing the same parsing logic through a common library.

Directory layout

src/
  lib.rs              Library root; re-exports the public API
  bin/                One binary per tool (pargs.rs, pfiles.rs, ptree.rs, ...)
  proc/               Process handle layer (parsing and structured access)
    mod.rs            ProcHandle, resolve_operand(), Error
    auxv.rs           Auxiliary vector parsing
    cred.rs           Credential parsing and UID/GID resolution
    fd.rs             File descriptor structures and classification
    net.rs            Socket metadata and peer process resolution
    numa.rs           NUMA node and CPU affinity
    pidfd.rs          pidfd utilities
    signal.rs         Signal set parsing
  source/             Data source abstraction
    mod.rs            ProcSource trait
    live.rs           Live process backend (reads /proc)
    coredump.rs       Coredump backend (reads systemd journal + ELF notes)
    dw.rs             DWARF-based unwinding via elfutils libdw/libdwfl
  stack/              Stack unwinding layer
    mod.rs            TraceOptions, Process, Thread, Frame types
  display.rs          Shared formatting helpers
examples/             Example programs used as test targets
tests/                Integration tests
  common/mod.rs       Test utilities (run_ptool, ReadySignal, etc.)
build.rs              Man page generation (roff)

Conventions & Patterns

Rust source file ordering

Within each .rs file, items should appear in this order:

  1. Module declarations (mod foo;)
  2. Imports (use statements) — std first, then external crates, then crate/super/self, separated by blank lines
  3. Constants and statics
  4. Type definitions (struct, enum, type) — simple/derived-like trait impls (From, Default, Drop, FromStr, Ord, PartialOrd, PartialEq, Eq, Borrow, etc.) should directly follow their type definition rather than being separated into the trait impls section
  5. Trait definitions
  6. Trait impls (impl Trait for Type) — for substantial impls with complex parsing or business logic (e.g., FromBufRead)
  7. Inherent impls (impl Type)
  8. Free functions (public before private)
  9. Tests (#[cfg(test)] mod tests)

Within an impl block: constructors (new, with_*, from_*) first, then public methods, then private helpers. Public items come before private items.

Layered architecture

The codebase is organized into four layers with strict dependency rules:

  1. Source layer (src/source/): Abstracts where process data comes from. The ProcSource trait provides a uniform interface; LiveProcess reads /proc/[pid]/... while CoredumpSource reads from journal entries and ELF notes. Each source owns a lazily-initialized dwfl session and exposes trace_thread(tid, options) for stack unwinding -- no dwfl types or raw ELF pointers leak outside this layer. Only the process handle layer consumes it.

  2. Process handle layer (src/proc/): Parses raw data from the source layer into typed Rust structures. The central type is ProcHandle, an opaque handle through which all process queries flow. Types here should not implement Display (except Error). Never format output in this layer -- it provides structured data only. All output formatting belongs in the presentation/display layer.

  3. Stack layer (src/stack/): Orchestrates thread enumeration, ptrace attachment, and snapshot/rolling tracing strategies. Delegates actual frame walking to the source layer via ProcHandle::trace_thread(). This layer must not implement Display (except Error).

  4. Presentation/display layer (src/display.rs and src/bin/*.rs): Formats structured data from the proc-handle layer for terminal output. Shared formatting lives in display.rs; tool-specific formatting lives in each binary. This layer must never consume the source layer directly.

Error handling

Tools should recover from errors and continue producing useful output. Debugging tools are expected to run on systems in unusual states, so do not panic on procfs inconsistencies. Assert only on purely internal invariants.

Command-line parsing

All tools use lexopt for argument parsing. No heavy framework (clap, structopt) is used.

Test framework and example programs

Integration tests live in tests/ and use a common helper module (tests/common/mod.rs). The typical pattern:

  1. An example program in examples/ sets up the process state to be inspected (opens files, sets signal handlers, creates threads, etc.).
  2. The example reads PTOOLS_TEST_READY_FILE from its environment, creates that file to signal readiness, then loops until killed.
  3. The test calls common::run_ptool(), which spawns the example, waits for the ready signal, runs the tool against the example's PID, kills the example, and returns the tool's output.
  4. The test asserts on the captured stdout/stderr.

To add a new test:

  • If you need a new target process state, add an example in examples/ that follows the ready-signal convention (read PTOOLS_TEST_READY_FILE, create the file when ready, loop forever).
  • Add a test file in tests/ (or add a #[test] function to an existing one). Use common::run_ptool() to drive the tool.
  • Remember to run cargo build before cargo test so that the example binaries are available.
  • New functionality is expected to have corresponding tests.