[go: up one dir, main page]

count_instructions 0.2.0

Counts the instructions executed within a single function
Documentation
#![no_std]

//! Counts the instructions executed within a single function.
//!
//! When developing constant-time code, it can be helpful to validate that several executions of a
//! given function have the same number of instructions, and that the same instructions were used.
//!
//! The approach used by this crate is to single-step the function using the operating system debug
//! API, optionally recording the address of each instruction. This is currently implemented only
//! for Linux; for other operating systems, it will fail with an error.
//!
//! Using the debug API to single-step the function has several drawbacks:
//!
//! - It can be very slow, especially when not compiled in release mode;
//! - It cannot be used while another debugger is attached to the process;
//! - Its use might be restricted by several system hardening mechanisms.
//!
//! On the other hand, using the debug API has the advantage of tracing the real code executed by
//! the CPU, as generated by the compiler, instead of symbolic execution of the source code, or
//! emulation on another architecture.
//!
//! # Examples
//!
//! ```
//! # #[cfg(target_os = "linux")]
//! # {
//! use count_instructions::count_instructions;
//!
//! # #[inline(never)]
//! fn add(left: usize, right: usize) -> usize {
//!     left + right
//! }
//!
//! let mut count = 0;
//! let result = count_instructions(|| add(2, 2), |instruction| count += 1)?;
//! assert_eq!(result, 4);
//! assert_ne!(count, 0);
//! # }
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! For a stronger test, you can store and later compare the instruction addresses:
//!
//! ```
//! # #[cfg(target_os = "linux")]
//! # {
//! use count_instructions::count_instructions;
//!
//! # #[inline(never)]
//! fn add(left: usize, right: usize) -> usize {
//!     left + right
//! }
//!
//! let mut addresses = Vec::new();
//! let result = count_instructions(
//!     || add(2, 2),
//!     |instruction| addresses.push(instruction.address())
//! )?;
//! assert_eq!(result, 4);
//! assert!(!addresses.is_empty());
//! # }
//! # Ok::<(), std::io::Error>(())
//! ```
//!
//! Note that, due to monomorphization and inlining, a separate monomorphic function must be used
//! whenever the instruction addresses from more than one execution will be compared later:
//!
//! ```
//! # #[cfg(target_os = "linux")]
//! # {
//! use count_instructions::count_instructions;
//! use count_instructions::Address;
//!
//! # #[inline(never)]
//! fn add(left: usize, right: usize) -> usize {
//!     left + right
//! }
//!
//! #[inline(never)]
//! fn count(left: usize, right: usize, capacity: usize) -> std::io::Result<Vec<Address>> {
//!     let mut addresses = Vec::with_capacity(capacity);
//!     let result = count_instructions(
//!         || add(left, right),
//!         |instruction| addresses.push(instruction.address())
//!     )?;
//!     assert_eq!(result, 4);
//!     Ok(addresses)
//! }
//!
//! let expected = count(2, 2, 0)?;
//! let addresses = count(3, 1, expected.len())?;
//! assert_eq!(addresses, expected);
//! # }
//! # Ok::<(), std::io::Error>(())
//! ```

extern crate std;

#[cfg(target_os = "linux")]
mod linux;

/// Represents the address of a machine instruction.
pub type Address = usize;

/// Information about an instruction executed by the function being traced.
#[derive(Debug)]
pub struct Instruction {
    address: Address,
}

impl Instruction {
    #[inline]
    #[cfg_attr(not(target_os = "linux"), allow(dead_code))]
    fn new(address: Address) -> Self {
        Self { address }
    }

    /// The address of the executed instruction.
    #[inline]
    #[must_use]
    pub fn address(&self) -> Address {
        self.address
    }
}

/// Runs the function being tested, calling a counter function for each instruction executed.
///
/// The `counter` will be called once for each instruction executed while running `f`. Note that
/// `counter` might be called a few more times, immediately before and after running `f`, due to a
/// few extra instructions necessary to start the trace, copy the result, and stop the trace.
///
/// See the top-level crate documentation for examples.
#[inline]
pub fn count_instructions<F, T, C>(f: F, counter: C) -> std::io::Result<T>
where
    F: FnOnce() -> T,
    C: FnMut(&Instruction) + Send,
{
    count_instructions_impl(f, counter)
}

#[cfg(target_os = "linux")]
use linux::count_instructions as count_instructions_impl;

#[cfg(not(target_os = "linux"))]
fn count_instructions_impl<F, T, C>(_f: F, _counter: C) -> std::io::Result<T>
where
    F: FnOnce() -> T,
    C: FnMut(&Instruction) + Send,
{
    Err(std::io::Error::new(
        std::io::ErrorKind::Unsupported,
        "instruction tracing not implemented for this platform",
    ))
}