procutils-pmap 0.2.0

Report memory map of a process
Documentation
//! Snapshot tests for `pmap` against a programmatically-built `/proc`.

use procutils_testutil::{Builder, ProcRoot, ProcessFixture};

const BIN: &str = env!("CARGO_BIN_EXE_pmap");

/// `/proc/[pid]/maps` content for a small fictional process. Six
/// mappings: text/rodata/data segments of the binary, an anonymous
/// heap, the stack, and the vdso. Field layout per `proc_pid_maps(5)`.
// procfs's maps parser uses splitn(6, ' '), so every line needs all
// six fields. Real /proc/[pid]/maps pads the path column for anonymous
// mappings; we do the same with a trailing space.
const MAPS: &str = "\
0000aaaa00000000-0000aaaa00100000 r-xp 00000000 fd:00 12345 /usr/bin/example
0000aaaa00100000-0000aaaa00110000 r--p 00100000 fd:00 12345 /usr/bin/example
0000aaaa00110000-0000aaaa00120000 rw-p 00110000 fd:00 12345 /usr/bin/example
0000aaaa00120000-0000aaaa00220000 rw-p 00000000 00:00 0 \n\
0000ffff00000000-0000ffff00021000 rw-p 00000000 00:00 0 [stack]
0000ffff80000000-0000ffff80002000 r-xp 00000000 00:00 0 [vdso]
";

/// `/proc/[pid]/smaps` for the same six mappings, with the per-mapping
/// fields procfs's parser requires. Padded with zeros where the value
/// doesn't matter for the columns pmap prints.
const SMAPS: &str = "\
0000aaaa00000000-0000aaaa00100000 r-xp 00000000 fd:00 12345 /usr/bin/example
Size:               1024 kB
Rss:                 800 kB
Pss:                 800 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:       800 kB
Private_Dirty:         0 kB
Referenced:          800 kB
Anonymous:             0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd ex mr mw me
0000aaaa00100000-0000aaaa00110000 r--p 00100000 fd:00 12345 /usr/bin/example
Size:                 64 kB
Rss:                  60 kB
Pss:                  60 kB
Shared_Clean:         60 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:           60 kB
Anonymous:             0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd mr mw me
0000aaaa00110000-0000aaaa00120000 rw-p 00110000 fd:00 12345 /usr/bin/example
Size:                 64 kB
Rss:                  64 kB
Pss:                  64 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        64 kB
Referenced:           64 kB
Anonymous:            64 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac
0000aaaa00120000-0000aaaa00220000 rw-p 00000000 00:00 0 \n\
Size:               1024 kB
Rss:                 512 kB
Pss:                 512 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:       512 kB
Referenced:          512 kB
Anonymous:           512 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac
0000ffff00000000-0000ffff00021000 rw-p 00000000 00:00 0 [stack]
Size:                132 kB
Rss:                  20 kB
Pss:                  20 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:        20 kB
Referenced:           20 kB
Anonymous:            20 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me gd ac
0000ffff80000000-0000ffff80002000 r-xp 00000000 00:00 0 [vdso]
Size:                  8 kB
Rss:                   8 kB
Pss:                   0 kB
Shared_Clean:          8 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:            8 kB
Anonymous:             0 kB
Swap:                  0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd ex mr mw me de
";

fn fixture() -> ProcRoot {
    let proc = ProcessFixture {
        cmdline: "/usr/bin/example\0--flag\0value".into(),
        maps: Some(MAPS.into()),
        smaps: Some(SMAPS.into()),
        ..ProcessFixture::new(200, "example")
    };
    Builder::new()
        .process(proc)
        .finish()
        .expect("build fixture")
}

#[track_caller]
fn run(root: &ProcRoot, args: &[&str]) -> String {
    if !procutils_testutil::unshare_supported() {
        return "<unshare unavailable>".to_string();
    }
    let mounts = procutils_testutil::proc_only(root.path());
    procutils_testutil::run_ok(BIN, &mounts, args)
}

#[test]
fn default_format() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["200"]));
}

#[test]
fn extended_format() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-x", "200"]));
}

#[test]
fn device_format() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-d", "200"]));
}

#[test]
fn quiet_default() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-q", "200"]));
}

#[test]
fn show_full_path() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-p", "200"]));
}

#[test]
fn range_single_address() {
    // Address inside the data segment (third mapping, 0xaaaa00110000+).
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-A", "0xaaaa00115000", "200"]));
}

#[test]
fn range_pair() {
    // Range spanning the heap and stack but excluding the binary segments.
    let root = fixture();
    insta::assert_snapshot!(run(
        &root,
        &["-A", "0xaaaa00120000,0xffff00021000", "200"],
    ));
}

#[test]
fn range_no_match() {
    let root = fixture();
    insta::assert_snapshot!(run(&root, &["-A", "0x1", "200"]));
}

#[test]
fn range_invalid_address() {
    let out = std::process::Command::new(BIN)
        .args(["-A", "not-hex", "1"])
        .output()
        .expect("spawn pmap");
    assert_eq!(out.status.code(), Some(2));
    let stderr = String::from_utf8_lossy(&out.stderr);
    assert!(stderr.contains("invalid address"), "stderr: {stderr}");
}