diff --git a/Cargo.toml b/Cargo.toml index 659fa5e..45cb883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ exclude = ["/.github/**"] [lib] name = "probe" crate-type = ["rlib"] + +[lints.clippy] +new_without_default = "allow" diff --git a/src/lib.rs b/src/lib.rs index c5c9e6f..e749b22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,16 +14,14 @@ //! //! ```rust //! use probe::probe; -//! fn main() { -//! probe!(foo, begin); -//! let mut total = 0; -//! for i in 0..100 { -//! total += i; -//! probe!(foo, loop, i, total); -//! } -//! assert_eq!(total, 4950); -//! probe!(foo, end); +//! probe!(foo, begin); +//! let mut total = 0; +//! for i in 0..100 { +//! total += i; +//! probe!(foo, loop, i, total); //! } +//! assert_eq!(total, 4950); +//! probe!(foo, end); //! ``` //! //! ## Using probes with SystemTap @@ -146,6 +144,34 @@ macro_rules! probe( /// ``` #[macro_export] macro_rules! probe_lazy( + (extern $semaphore:path, $provider:ident, $name:ident $(, $arg:expr)* $(,)?) + => ({ + // Type-check the location to have 'static lifetime, so it is safe to + // put it in the stapsdt note. The program cannot really do + // anything with it---the only available method is enabled(); therefore, + // even though not placing it in the .probes section could upset the tracing + // tool, it cannot be used to trigger undefined behavior. + let semaphore: &'static $crate::Semaphore = &$semaphore; + let enabled = semaphore.enabled(); + if enabled { + $crate::platform_probe_lazy!($semaphore, $provider, $name, $($arg,)*); + } + enabled + }); + ($provider:ident, $name:ident $(, $arg:expr)* $(,)?) - => ($crate::platform_probe_lazy!($provider, $name, $($arg,)*)); + => ({ + $crate::platform_declare_semaphore!(SEMAPHORE); + $crate::probe_lazy!(extern SEMAPHORE, $provider, $name, $($arg,)*) + }); ); + +/// A location that represents whether a tracepoint was enabled. +/// +/// [`probe_lazy!`] uses a [`Semaphore`] to guard evaluation of +/// arguments. A platform-specific mechanism ensures that [`Semaphore::enabled`] +/// returns `true` when a debugger or tracing tool is attached to the probe. +/// +/// Note that, if a platform implementation can't determine that, it might +/// always return `true`. +pub use platform::Semaphore; diff --git a/src/platform/default.rs b/src/platform/default.rs index eefa716..9274ab3 100644 --- a/src/platform/default.rs +++ b/src/platform/default.rs @@ -1,3 +1,18 @@ +pub struct Semaphore; + +impl Semaphore { + /// Return a `Semaphore` that starts as disabled. + pub const fn new() -> Self { + Self + } + + /// Return whether a debugger or tracing tool is attached to a probe + /// that uses this semaphore. + pub fn enabled(&self) -> bool { + false + } +} + #[doc(hidden)] #[macro_export] macro_rules! platform_probe( @@ -7,14 +22,20 @@ macro_rules! platform_probe( }) ); +#[doc(hidden)] +#[macro_export] +macro_rules! platform_declare_semaphore( + ($semaphore:ident) => { + static $semaphore: $crate::Semaphore = $crate::Semaphore::new(); + } +); + #[doc(hidden)] #[macro_export] macro_rules! platform_probe_lazy( - ($provider:ident, $name:ident, $($arg:expr,)*) => ({ + ($semaphore:path, $provider:ident, $name:ident, $($arg:expr,)*) => ({ + // The caller wraps this with what is effectively "if false" // Expand the arguments so they don't cause unused warnings. - if false { - let _ = ($($arg,)*); - } - false + let _ = ($($arg,)*); }) ); diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 501d2aa..8e096d8 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -1,5 +1,9 @@ -#[cfg(any(target_os = "linux", target_os = "android"))] +#[cfg(any(target_os = "linux", target_os = "android", docsrs))] mod systemtap; +#[cfg(any(target_os = "linux", target_os = "android", docsrs))] +pub use systemtap::*; -#[cfg(not(any(target_os = "linux", target_os = "android")))] +#[cfg(not(any(target_os = "linux", target_os = "android", docsrs)))] mod default; +#[cfg(not(any(target_os = "linux", target_os = "android", docsrs)))] +pub use default::*; diff --git a/src/platform/systemtap.rs b/src/platform/systemtap.rs index c3ccaf8..8de1ce4 100644 --- a/src/platform/systemtap.rs +++ b/src/platform/systemtap.rs @@ -17,6 +17,9 @@ //! * //! * +use core::cell::UnsafeCell; +use core::ptr; + // // DEVELOPER NOTES // @@ -55,6 +58,27 @@ // when there's nobody attached to see the probe. // +#[repr(transparent)] +pub struct Semaphore(UnsafeCell); + +// SAFETY: the UnsafeCell is only ever read as far as Rust is +// concerned; data races require a read and a write. +unsafe impl Sync for Semaphore {} + +impl Semaphore { + /// Return a `Semaphore` that starts as disabled. + pub const fn new() -> Self { + Self(UnsafeCell::new(0)) + } + + /// Return whether a debugger or tracing tool is attached to a probe + /// that uses this semaphore. + #[inline(always)] + pub fn enabled(&self) -> bool { + (unsafe { ptr::read_volatile(self.0.get() as *const _) }) != 0u16 + } +} + #[doc(hidden)] #[macro_export] macro_rules! platform_probe( @@ -65,15 +89,18 @@ macro_rules! platform_probe( #[doc(hidden)] #[macro_export] -macro_rules! platform_probe_lazy( - ($provider:ident, $name:ident, $($arg:expr,)*) => ({ +macro_rules! platform_declare_semaphore( + ($semaphore:ident) => { #[link_section = ".probes"] - static mut SEMAPHORE: u16 = 0; - let enabled = unsafe { ::core::ptr::read_volatile(&SEMAPHORE) } != 0; - if enabled { - $crate::sdt!([sym "{}" SEMAPHORE], $provider, $name, $($arg,)*); - } - enabled + static $semaphore: $crate::Semaphore = $crate::Semaphore::new(); + } +); + +#[doc(hidden)] +#[macro_export] +macro_rules! platform_probe_lazy( + ($semaphore:path, $provider:ident, $name:ident, $($arg:expr,)*) => ({ + $crate::sdt!([sym "{}" $semaphore], $provider, $name, $($arg,)*); }) ); @@ -82,7 +109,7 @@ macro_rules! platform_probe_lazy( #[doc(hidden)] #[macro_export] macro_rules! sdt( - ([sym $symstr:literal $($sym:ident)?], + ([sym $symstr:literal $($sym:path)?], $provider:ident, $name:ident, $($arg:expr,)* ) => ( #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] @@ -94,7 +121,7 @@ macro_rules! sdt( $provider, $name, $($arg,)*); ); - ([sym $symstr:literal $($sym:ident)?, opt $($opt:ident)?], + ([sym $symstr:literal $($sym:path)?, opt $($opt:ident)?], $provider:ident, $name:ident, $($arg1:expr, $($arg:expr,)*)? ) => ( #[cfg(target_pointer_width = "32")] @@ -106,7 +133,7 @@ macro_rules! sdt( $provider, $name, $("-8@{}", $arg1, $(" -8@{}", $arg,)*)?); ); - ([sym $symstr:literal $($sym:ident)?, opt $($opt:ident)?, size $size:literal], + ([sym $symstr:literal $($sym:path)?, opt $($opt:ident)?, size $size:literal], $provider:ident, $name:ident, $($argstr:literal, $arg:expr,)* ) => (unsafe { ::core::arch::asm!(concat!(r#"