use std::ffi::c_void;
mod bindings;
macro_rules! raw_call {
($f:ident) => { raw_call!($f,) };
($f:ident, $($args:tt)*) => {{
unsafe{ bindings::$f($($args)*) }
}};
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum RunMode {
Native,
Valgrind,
ValgrindInValgrind(usize),
}
#[inline]
pub fn run_mode() -> RunMode {
match unsafe { bindings::running_on_valgrind() } {
0 => RunMode::Native,
1 => RunMode::Valgrind,
x => RunMode::ValgrindInValgrind(x),
}
}
#[doc(hidden)]
pub fn __print(msg: String) {
let cstr = std::ffi::CString::new(msg).unwrap();
raw_call!(vg_print, cstr.as_ptr());
}
#[macro_export]
macro_rules! print{
($($arg:tt)+) => { $crate::__print(format!("{}",format_args!($($arg)+)));}
}
#[macro_export]
macro_rules! println{
($($arg:tt)+) => { $crate::__print(format!("{}\n",format_args!($($arg)+)));}
}
#[doc(hidden)]
#[inline(always)]
pub fn __print_stacktrace(msg: String) {
let cstr = std::ffi::CString::new(msg).unwrap();
raw_call!(vg_print_backtrace, cstr.as_ptr());
}
#[macro_export]
macro_rules! print_stacktrace{
($($arg:tt)+) => { $crate::__print_stacktrace(format!("{}\n",format_args!($($arg)+)));}
}
#[inline]
pub fn monitor_command(cmd: impl AsRef<str>) -> std::io::Result<()> {
let cmd = std::ffi::CString::new(cmd.as_ref()).unwrap();
if raw_call!(vg_monitor_command, cmd.as_ptr()) {
Err(std::io::ErrorKind::NotFound.into())
} else {
Ok(())
}
}
#[inline]
pub fn disable_error_reporting() {
raw_call!(vg_disable_error_reporting);
}
#[inline]
pub fn enable_error_reporting() {
raw_call!(vg_enable_error_reporting);
}
#[inline]
pub fn count_errors() -> usize {
raw_call!(vg_count_errors)
}
#[inline]
pub fn change_cli_option(opt: impl AsRef<str>) {
let cstr = std::ffi::CString::new(opt.as_ref()).unwrap();
raw_call!(vg_clo_change, cstr.as_ptr());
}
pub mod valgrind {
use std::os::unix::prelude::RawFd;
use super::*;
pub type ThreadId = usize;
#[inline]
pub fn discard_translations(addr: *mut c_void, len: usize) {
raw_call!(vg_discard_translations, addr, len);
}
#[inline]
pub fn load_pdb_debuginfo(fd: RawFd, ptr: *mut c_void, total_size: usize, delta: usize) {
raw_call!(vg_load_pdb_debuginfo, fd, ptr, total_size, delta);
}
#[inline]
pub fn map_ip_to_srcloc(addr: *mut c_void, buf64: *mut c_void) -> usize {
raw_call!(vg_map_ip_to_srcloc, addr, buf64)
}
extern "C" fn _closure_adapter<F>(tid: ThreadId, f: *mut c_void)
where
F: FnMut(ThreadId),
{
if let Err(err) = std::panic::catch_unwind(|| unsafe {
debug_assert!(!f.is_null(), "closure pointer is null");
debug_assert_eq!(
f as usize & (std::mem::align_of::<F>() - 1),
0,
"unexpected closure pointer"
);
(*f.cast::<F>())(tid)
}) {
let panic_info = err
.downcast::<String>()
.map(|v| *v)
.or_else(|e| e.downcast::<&str>().map(|v| v.to_string()))
.unwrap_or_else(|_| "unknown panic source".to_string());
eprintln!("closure code panicked with: {panic_info:?}");
std::process::abort();
}
}
#[inline]
pub fn non_simd_call<F>(f: F)
where
F: FnMut(ThreadId),
{
let boxed = Box::into_raw(Box::new(f));
raw_call!(vg_non_simd_call1, _closure_adapter::<F>, boxed.cast());
let _ = unsafe { Box::from_raw(boxed) };
}
}
pub mod callgrind {
use super::*;
#[inline]
pub fn dump_stats<'a>(reason: impl Into<Option<&'a str>>) {
match reason.into() {
None => raw_call!(cl_dump_stats),
Some(reason) => {
let cstr = std::ffi::CString::new(reason).unwrap();
raw_call!(cl_dump_stats_at, cstr.as_ptr())
}
};
}
#[inline]
pub fn zero_stats() {
raw_call!(cl_zero_stats);
}
#[inline]
pub fn toggle_collect() {
raw_call!(cl_toggle_collect);
}
#[inline]
pub fn start_instrumentation() {
raw_call!(cl_start_instrumentation);
}
#[inline]
pub fn stop_instrumentation() {
raw_call!(cl_stop_instrumentation);
}
}
pub mod cachegrind {
use super::*;
#[inline]
pub fn start_instrumentation() {
raw_call!(cg_start_instrumentation);
}
#[inline]
pub fn stop_instrumentation() {
raw_call!(cg_stop_instrumentation);
}
}
pub mod memcheck {
use super::*;
pub use bindings::LeakCount;
pub type BlockDescHandle = u32;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Error {
InvalidHandle,
NotAddressable(usize),
NoValgrind,
UnalignedArrays,
}
impl std::error::Error for Error {}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
impl std::fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidHandle => f.write_str("Invalid memory block description handle"),
Error::NotAddressable(addr) => {
write!(f, "Memory starting from 0x{addr:X} is not addressable")
}
Error::NoValgrind => f.write_str("Not running under Valgrind"),
Error::UnalignedArrays => {
f.write_str("[previously indicated unaligned arrays; these are now allowed]")
}
}
}
}
pub type Result<T = ()> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum LeakCheck {
Full,
New,
Quick,
Added,
Changed,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum MemState {
NoAccess,
Undefined,
Defined,
DefinedIfAddressable,
}
#[inline]
pub fn mark_mem(addr: *mut c_void, len: usize, mark: MemState) -> Result {
let ret = match mark {
MemState::NoAccess => raw_call!(mc_make_mem_noaccess, addr, len),
MemState::Undefined => raw_call!(mc_make_mem_undefined, addr, len),
MemState::Defined => raw_call!(mc_make_mem_defined, addr, len),
MemState::DefinedIfAddressable => {
raw_call!(mc_make_mem_defined_if_addressable, addr, len)
}
};
if ret == 0 {
Ok(())
} else {
Err(Error::NoValgrind)
}
}
#[inline]
pub fn new_block_description(
addr: *mut c_void,
len: usize,
desc: impl AsRef<str>,
) -> BlockDescHandle {
let cstr = std::ffi::CString::new(desc.as_ref()).unwrap();
raw_call!(mc_create_block, addr, len, cstr.as_ptr())
}
#[inline]
pub fn discard(handle: BlockDescHandle) -> Result {
if raw_call!(mc_discard, handle) == 0 {
Ok(())
} else {
Err(Error::InvalidHandle)
}
}
#[inline]
pub fn is_addressable(addr: *mut c_void, len: usize) -> Result {
match raw_call!(mc_check_mem_is_addressable, addr, len) {
0 => Ok(()),
addr => Err(Error::NotAddressable(addr)),
}
}
#[inline]
pub fn is_defined(addr: *mut c_void, len: usize) -> Result {
match raw_call!(mc_check_mem_is_defined, addr, len) {
0 => Ok(()),
addr => Err(Error::NotAddressable(addr)),
}
}
#[inline]
pub fn leak_check(mode: LeakCheck) {
match mode {
LeakCheck::Full => raw_call!(mc_do_leak_check),
LeakCheck::New => raw_call!(mc_do_new_leak_check),
LeakCheck::Quick => raw_call!(mc_do_quick_leak_check),
LeakCheck::Added => raw_call!(mc_do_added_leak_check),
LeakCheck::Changed => raw_call!(mc_do_changed_leak_check),
};
}
#[inline]
pub fn leaks_count() -> LeakCount {
raw_call!(mc_count_leaks)
}
#[inline]
pub fn block_leaks_count() -> LeakCount {
raw_call!(mc_count_leak_blocks)
}
#[inline]
pub fn vbits(addr: *mut c_void, bits: *const u8, nbytes: usize) -> Result {
match raw_call!(mc_get_vbits, addr, bits, nbytes) {
0 => Err(Error::NoValgrind),
1 => Ok(()),
2 => Err(Error::UnalignedArrays),
3 => Err(Error::NotAddressable(0)),
x => unreachable!("Unexpected return code {}", x),
}
}
#[inline]
pub fn set_vbits(addr: *mut c_void, bits: *const u8, nbytes: usize) -> Result {
match raw_call!(mc_set_vbits, addr, bits, nbytes) {
0 => Err(Error::NoValgrind),
1 => Ok(()),
2 => Err(Error::UnalignedArrays),
3 => Err(Error::NotAddressable(0)),
x => unreachable!("Unexpected return code {}", x),
}
}
#[inline]
pub fn disable_error_reporting(addr: *mut c_void, len: usize) {
raw_call!(mc_disable_addr_error_reporting_in_range, addr, len);
}
#[inline]
pub fn enable_error_reporting(addr: *mut c_void, len: usize) {
raw_call!(mc_enable_addr_error_reporting_in_range, addr, len);
}
pub mod alloc {
use super::super::*;
#[inline]
pub fn malloc(addr: *mut c_void, size: usize, rz: usize, is_zeroed: bool) {
raw_call!(vg_malloclike_block, addr, size, rz, is_zeroed);
}
#[inline]
pub fn free(addr: *mut c_void, rz: usize) {
raw_call!(vg_freelike_block, addr, rz);
}
#[inline]
pub fn resize_inplace(addr: *mut c_void, old_size: usize, new_size: usize, rz: usize) {
raw_call!(vg_resizeinplace_block, addr, old_size, new_size, rz);
}
}
pub mod mempool {
use super::super::*;
pub const AUTO_FREE: u32 = 1;
pub const METAPOOL: u32 = 2;
#[inline]
pub fn create(
pool: *mut c_void,
rz: usize,
is_zeroed: bool,
flags: impl Into<Option<u32>>,
) {
match flags.into() {
None => raw_call!(vg_create_mempool, pool, rz, is_zeroed),
Some(flags) => raw_call!(vg_create_mempool_ext, pool, rz, is_zeroed, flags),
};
}
#[inline]
pub fn destroy(pool: *mut c_void) {
raw_call!(vg_destroy_mempool, pool);
}
#[inline]
pub fn alloc(pool: *mut c_void, addr: *mut c_void, size: usize) {
raw_call!(vg_mempool_alloc, pool, addr, size);
}
#[inline]
pub fn free(pool: *mut c_void, addr: *mut c_void) {
raw_call!(vg_mempool_free, pool, addr);
}
#[inline]
pub fn trim(pool: *mut c_void, addr: *mut c_void, size: usize) {
raw_call!(vg_mempool_trim, pool, addr, size);
}
#[inline]
pub fn move_to(pool_a: *mut c_void, pool_b: *mut c_void) {
raw_call!(vg_move_mempool, pool_a, pool_b);
}
#[inline]
pub fn change(pool: *mut c_void, addr_a: *mut c_void, addr_b: *mut c_void, size: usize) {
raw_call!(vg_mempool_change, pool, addr_a, addr_b, size);
}
#[inline]
pub fn is_exists(pool: *mut c_void) -> bool {
raw_call!(vg_mempool_exists, pool)
}
}
pub mod stack {
use super::super::*;
pub type StackId = usize;
#[inline]
pub fn register(lowest: *mut c_void, highest: *mut c_void) -> StackId {
raw_call!(vg_stack_register, lowest, highest)
}
#[inline]
pub fn deregister(id: StackId) {
raw_call!(vg_stack_deregister, id);
}
#[inline]
pub fn change(id: StackId, new_lowest: *mut c_void, new_highest: *mut c_void) {
raw_call!(vg_stack_change, id, new_lowest, new_highest);
}
}
}
pub mod helgrind {
use super::*;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum Annotation {
HappensBefore,
HappensAfter,
New(usize),
RwLockCreate,
RwLockDestroy,
RwLockAcquired(bool),
RwLockReleased,
}
#[inline]
pub fn clean_memory(addr: *mut c_void, len: usize) {
raw_call!(hg_clean_memory, addr, len);
}
#[inline]
pub fn annotate_memory(addr: *mut c_void, rel: Annotation) {
match rel {
Annotation::RwLockCreate => raw_call!(hg_rwlock_create, addr),
Annotation::RwLockDestroy => raw_call!(hg_rwlock_destroy, addr),
Annotation::RwLockAcquired(is_wl) => raw_call!(hg_rwlock_acquired, addr, is_wl),
Annotation::RwLockReleased => raw_call!(hg_rwlock_released, addr),
Annotation::HappensAfter => raw_call!(hg_annotate_happens_after, addr),
Annotation::HappensBefore => raw_call!(hg_annotate_happens_before, addr),
Annotation::New(size) => raw_call!(hg_annotate_new_memory, addr, size),
};
}
}
pub mod dhat {
use super::*;
#[inline]
pub fn histogram_memory(addr: *mut c_void) {
raw_call!(dh_histogram_memory, addr);
}
}
#[cfg(test)]
mod tests {
use crate::{self as cg, valgrind::ThreadId};
#[test]
fn test_run_mode_under_valgrind() {
assert_eq!(cg::RunMode::Valgrind, cg::run_mode());
}
#[test]
fn print_macros_wont_fail() {
let m = "crabgrind";
cg::print!("{m}");
cg::println!("het, {m}");
cg::print_stacktrace!("{m}");
}
#[test]
fn ok_monitor_command() {
cg::monitor_command("v.info all_errors").unwrap();
}
#[test]
fn wrong_monitor_command() {
assert!(cg::monitor_command("hey valgringo").is_err());
}
#[test]
fn count_errors() {
unsafe {
let uninit = std::mem::MaybeUninit::<u8>::uninit();
if uninit.assume_init() > 0 {
unreachable!();
}
}
assert_eq!(cg::count_errors(), 1);
}
#[test]
fn disable_error_reporting() {
cg::disable_error_reporting();
unsafe {
let uninit = std::mem::MaybeUninit::<u8>::uninit();
if uninit.assume_init() > 0 {
unreachable!();
}
}
assert_eq!(cg::count_errors(), 0);
}
#[test]
fn non_simd_call() {
let mut tid = ThreadId::MAX;
cg::valgrind::non_simd_call(|id| {
tid = id;
});
assert_ne!(tid, ThreadId::MAX);
}
#[test]
fn change_cli_option() {
cg::change_cli_option("--leak-check=no");
std::mem::forget(String::from("leaked"));
}
}