Crate crabgrind

source ·
Expand description

Valgrind Client Request interface for Rust

crabgrind wraps various Valgrind macros in C functions, compiles and links against the resulting binary, and exposes an unsafe interface to allow Rust programs running under Valgrind to interact with the tools and environment.

This library is indeed a wrapper, the only thing it adds are the type conversions and some structure, all the real things are happening inside Valgrind.

Valgrind 3 API coverage

Quickstart

crabgrind doesn’t links against Valgrind, but reads it’s header files, so they must be accessible to build the project.

If headers resides at the /usr/include/valgrind, cc, the build tool crabgrind uses, will find them.

If you have installed Vallgrind manually, you can set DEP_VALGRIND environment variable to the appropriate path, its value, if specified, will be directly passed to cc::Build::include.

env DEP_VALGRIND=/usr/include cargo build

Add the following to your Cargo.toml file:

[dependencies]
crabgrind = "~0.1"

Next, use some of the Valgrind’s API

use crabgrind as cg;

fn main() {
    if matches!(cg::run_mode(), cg::RunMode::Native) {
        println!("run me under Valgrind");
    } else {
        cg::println!("Hey, Valgrind!");
    }
}

And run your application under Valgrind, either with handy cargo-valgrind

cargo valgrind run

or manually

cargo build

valgrind ./target/debug/appname

Examples

Valgrind provides VALGRIND_PRINTF_BACKTRACE macro to print the message with the stack-trace attached, crabgrind::print_stacktrace is it’s crabbed wrapper.

use crabgrind as cg;

#[inline(never)]
fn print_trace(){
    let mode = cg::run_mode();
    cg::print_stacktrace!("current mode: {mode:?}");
}

print_trace();
Exclude expensive initialization code from the measurements

One way to do this would be to turn off stats collection at stratup with the --collect-atstart=no callgrind command-line attribute, and enable/disable it from the code with callgrind::toggle_collect

use crabgrind as cg;

// ... some expensive initialization

cg::callgrind::toggle_collect();
// code of interest
cg::callgrind::toggle_collect();

// ... some deinitialization
Run a closure on the real CPU while running under Valgrind

We can run on the real CPU instead of the virtual one using valgrind::non_simd_call, refer to valgrind.h for details on limitations and various ways to crash.

use crabgrind as cg;

let mut state = 0;
cg::valgrind::non_simd_call(|tid| {
    // uncomment following line to see "the 'impossible' happened"
    // println!("tid: {tid}");
    state = tid;
});

println!("tid: {state}");
Save current memory usage snapshot to a file

We’ll use Massif tool and the monitor command interface to run the corresponding Massif command.

use crabgrind as cg;

let heap = String::from("alloca");

if cg::monitor_command("snapshot mem.snapshot").is_ok(){
    println!("snapshot is saved to \"mem.snapshot\"");
}
Dump Callgrind counters on a function basis
use crabgrind as cg;

fn factorial1(num: u128) -> u128 {
    match num {
        0 => 1,
        1 => 1,
        _ => factorial1(num - 1) * num,
    }
}

fn factorial2(num: u128) -> u128 {
    (1..=num).product()
}

cg::callgrind::zero_stats();

let a = factorial1(20);
cg::callgrind::dump_stats("factorial1");

let b = factorial2(20);
cg::callgrind::dump_stats("factorial2");

assert_eq!(a,b);
cg::callgrind::dump_stats(None);

Overhead

from Valgrind docs

The code added to your binary has negligible performance impact: on x86, amd64, ppc32, ppc64 and ARM, the overhead is 6 simple integer instructions and is probably undetectable except in tight loops.

… the code does nothing when not run on Valgrind, so you are not forced to run your program under Valgrind just because you use the macros in this file.

however,

  • wrapping each macros in a function implies function call overhead regardless of the run mode
  • functions that returns std::result::Result involve branching
  • functions that takes strings as a parameters internally converts them to std::ffi::CString

If you wish to compile out all (crab)Valgrind from the binary, you can wrap crabgrind calls with the feature-gate.

Tests

Tests must be run under Valgrind, as of now cargo-valgrind fits nicely, it allows to compile and run tests under Valgrind in one command

cargo valgrind test

Modules

Macros

  • Prints to the Valgrind’s log.
  • Prints to the Valgrind’s log, with the current stacktrace attached.
  • Prints to the Valgrind’s log, with a newline.

Enums

Functions