1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#![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",
    ))
}