minicov/
lib.rs

1//! This crate provides code coverage and profile-guided optimization (PGO) support
2//! for `no_std` and embedded programs.
3//!
4//! This is done through a modified version of the LLVM profiling runtime (normally
5//! part of compiler-rt) from which all dependencies on libc have been removed.
6//!
7//! All types of instrumentation using the LLVM profiling runtime are supported:
8//! - Rust code coverage with `-Cinstrument-coverage`.
9//! - Rust profile-guided optimization with `-Cprofile-generate`.
10//! - Clang code coverage with `-fprofile-instr-generate -fcoverage-mapping`.
11//! - Clang profile-guided optimization with `-fprofile-instr-generate`.
12//! - Clang LLVM IR profile-guided optimization with `-fprofile-generate`.
13//!
14//! Note that to profile both C and Rust code at the same time you must use Clang
15//! with the same LLVM version as the LLVM used by rustc. You can pass these flags
16//! to C code compiled with the `cc` crates using [environment variables].
17//!
18//! [environment variables]: https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables
19//!
20//! ## Usage
21//!
22//! Note: This crate requires a recent nightly compiler.
23//!
24//! 1. Ensure that the following environment variables are set up:
25//!
26//! ```sh
27//! export RUSTFLAGS="-Cinstrument-coverage -Zno-profiler-runtime"
28//! ```
29//!
30//! Note that these flags also apply to build-dependencies and proc
31//! macros by default. This can be worked around by explicitly
32//! specifying a target when invoking cargo:
33//!
34//! ```sh
35//! # Applies RUSTFLAGS to everything
36//! cargo build
37//!
38//! # Doesn't apply RUSTFLAGS to build dependencies and proc macros
39//! cargo build --target x86_64-unknown-linux-gnu
40//! ```
41//!
42//! 2. Add the `minicov` crate as a dependency to your program:
43//!
44//! ```toml
45//! [dependencies]
46//! minicov = "0.3"
47//! ```
48//!
49//! 3. Before your program exits, call `minicov::capture_coverage` with a sink (such
50//!    as `Vec<u8>`) and then dump its contents to a file with the `.profraw` extension:
51//!
52//! ```ignore
53//! fn main() {
54//!     // ...
55//!
56//!     let mut coverage = vec![];
57//!     unsafe {
58//!         // Note that this function is not thread-safe! Use a lock if needed.
59//!         minicov::capture_coverage(&mut coverage).unwrap();
60//!     }
61//!     std::fs::write("output.profraw", coverage).unwrap();
62//! }
63//! ```
64//!
65//! If your program is running on a different system than your build system then
66//! you will need to transfer this file back to your build system.
67//!
68//! Sinks must implement the `CoverageWriter` trait. If the default `alloc` feature
69//! is enabled then an implementation is provided for `Vec<u8>`.
70//!
71//! 4. Use a tool such as [grcov] or llvm-cov to generate a human-readable coverage
72//!    report:
73//!
74//! ```sh
75//! grcov output.profraw -b ./target/debug/my_program -s . -t html -o cov_report
76//! ```
77//!
78//! [grcov]: https://github.com/mozilla/grcov
79//!
80//! ## Profile-guided optimization
81//!
82//! The steps for profile-guided optimzation are similar. The only difference is the
83//! flags passed in `RUSTFLAGS`:
84//!
85//! ```sh
86//! # First run to generate profiling information.
87//! #
88//! # The filename passed to profile-generate doesn't matter, but cc-rs complains
89//! # if it is not provided.
90//! export RUSTFLAGS="-Cprofile-generate=output.profraw -Zno-profiler-runtime"
91//! cargo run --target x86_64-unknown-linux-gnu --release
92//!
93//! # Post-process the profiling information.
94//! # The rust-profdata tool comes from cargo-binutils.
95//! rust-profdata merge -o output.profdata output.profraw
96//!
97//! # Optimized build using PGO. minicov is not needed in this step.
98//! export RUSTFLAGS="-Cprofile-use=output.profdata"
99//! cargo build --target x86_64-unknown-linux-gnu --release
100//! ```
101
102#![no_std]
103#![warn(missing_docs)]
104#![warn(rust_2018_idioms)]
105
106#[cfg(feature = "alloc")]
107extern crate alloc;
108
109#[cfg(feature = "alloc")]
110use alloc::vec::Vec;
111#[cfg(feature = "alloc")]
112use core::alloc::Layout;
113use core::{fmt, slice};
114
115#[allow(non_snake_case)]
116#[repr(C)]
117struct ProfDataIOVec {
118    Data: *mut u8,
119    ElmSize: usize,
120    NumElm: usize,
121    UseZeroPadding: i32,
122}
123
124#[allow(non_snake_case)]
125#[repr(C)]
126struct ProfDataWriter {
127    Write:
128        unsafe extern "C" fn(This: *mut ProfDataWriter, *mut ProfDataIOVec, NumIOVecs: u32) -> u32,
129    WriterCtx: *mut u8,
130}
131
132// Opaque type for our purposes.
133enum VPDataReaderType {}
134
135extern "C" {
136    fn __llvm_profile_begin_counters() -> *const u8;
137    fn __llvm_profile_end_counters() -> *const u8;
138    fn __llvm_profile_reset_counters();
139    fn __llvm_profile_merge_from_buffer(profile: *const u8, size: u64) -> i32;
140    fn __llvm_profile_check_compatibility(profile: *const u8, size: u64) -> i32;
141    fn __llvm_profile_get_version() -> u64;
142    fn lprofWriteData(
143        Writer: *mut ProfDataWriter,
144        VPDataReader: *mut VPDataReaderType,
145        SkipNameDataWrite: i32,
146    ) -> i32;
147    fn lprofGetVPDataReader() -> *mut VPDataReaderType;
148    fn lprofGetLoadModuleSignature() -> u64;
149}
150
151const INSTR_PROF_RAW_VERSION: u64 = 10;
152const VARIANT_MASKS_ALL: u64 = 0xffffffff00000000;
153
154// On some target rustc will insert an artificial dependency on the
155// __llvm_profile_runtime symbol to ensure the static initializer from LLVM's
156// profiling runtime is pulled in by the linker. We don't need any runtime
157// initialization so we just provide the symbol here.
158#[no_mangle]
159static __llvm_profile_runtime: u8 = 0;
160
161// Memory allocation functions used by value profiling. If the "alloc" feature
162// is disabled then value profiling will also be disabled.
163#[cfg(feature = "alloc")]
164#[no_mangle]
165unsafe fn minicov_alloc_zeroed(size: usize, align: usize) -> *mut u8 {
166    alloc::alloc::alloc_zeroed(Layout::from_size_align(size, align).unwrap())
167}
168#[cfg(feature = "alloc")]
169#[no_mangle]
170unsafe extern "C" fn minicov_dealloc(ptr: *mut u8, size: usize, align: usize) {
171    alloc::alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap())
172}
173#[cfg(not(feature = "alloc"))]
174#[no_mangle]
175unsafe fn minicov_alloc_zeroed(_size: usize, _align: usize) -> *mut u8 {
176    core::ptr::null_mut()
177}
178#[cfg(not(feature = "alloc"))]
179#[no_mangle]
180unsafe extern "C" fn minicov_dealloc(_ptr: *mut u8, _size: usize, _align: usize) {}
181
182/// Sink into which coverage data can be written.
183///
184/// A default implementation for `Vec<u8>` is provided,
185pub trait CoverageWriter {
186    /// Writes the given bytes to the sink.
187    ///
188    /// This method should return an error if all bytes could not be written to
189    /// the sink.
190    fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError>;
191}
192
193#[cfg(feature = "alloc")]
194impl CoverageWriter for Vec<u8> {
195    fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError> {
196        self.extend_from_slice(data);
197        Ok(())
198    }
199}
200
201/// Callback function passed to `lprofWriteData`.
202unsafe extern "C" fn write_callback<Writer: CoverageWriter>(
203    this: *mut ProfDataWriter,
204    iovecs: *mut ProfDataIOVec,
205    num_iovecs: u32,
206) -> u32 {
207    let writer = &mut *((*this).WriterCtx as *mut Writer);
208    for iov in slice::from_raw_parts(iovecs, num_iovecs as usize) {
209        let len = iov.ElmSize * iov.NumElm;
210        if iov.Data.is_null() {
211            // Pad with zero bytes.
212            let zero = [0; 16];
213            let mut remaining = len;
214            while remaining != 0 {
215                let data = &zero[..usize::min(zero.len(), remaining)];
216                if writer.write(data).is_err() {
217                    return 1;
218                }
219                remaining -= data.len();
220            }
221        } else {
222            let data = slice::from_raw_parts(iov.Data, len);
223            if writer.write(data).is_err() {
224                return 1;
225            }
226        }
227    }
228    0
229}
230
231/// Checks that the instrumented binary uses the same profiling data format as
232/// the LLVM profiling runtime.
233fn check_version() {
234    let version = unsafe { __llvm_profile_get_version() & !VARIANT_MASKS_ALL };
235    assert_eq!(
236        version, INSTR_PROF_RAW_VERSION,
237        "Runtime and instrumentation version mismatch"
238    );
239}
240
241/// Returns whether the current binary was built with coverage instrumentation
242/// enabled.
243///
244/// If this returns `false` then the functions in this file will still function
245/// correctly but will emit empty coverage data.
246pub fn coverage_enabled() -> bool {
247    unsafe { __llvm_profile_begin_counters() != __llvm_profile_end_counters() }
248}
249
250/// Captures the coverage data for the current program and writes it into the
251/// given sink.
252///
253/// The data should be saved to a file with the `.profraw` extension, which can
254/// then be processed using the `llvm-profdata` and `llvm-cov` tools.
255///
256/// You should call `reset_coverage` afterwards if you intend to continue
257/// running the program so that future coverage can be merged with the returned
258/// captured coverage.
259///
260/// # Safety
261///
262/// This function is not thread-safe and should not be concurrently called from
263/// multiple threads.
264pub unsafe fn capture_coverage<Writer: CoverageWriter>(
265    writer: &mut Writer,
266) -> Result<(), CoverageWriteError> {
267    check_version();
268
269    let mut prof_writer = ProfDataWriter {
270        Write: write_callback::<Writer>,
271        WriterCtx: writer as *mut Writer as *mut u8,
272    };
273    let res = lprofWriteData(&mut prof_writer, lprofGetVPDataReader(), 0);
274    if res == 0 {
275        Ok(())
276    } else {
277        Err(CoverageWriteError)
278    }
279}
280
281/// Error type returned when trying to merge incompatible coverage data.
282///
283/// This typically happens if the coverage data comes from a different binary.
284#[derive(Copy, Clone, Debug)]
285pub struct IncompatibleCoverageData;
286impl fmt::Display for IncompatibleCoverageData {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        f.write_str("incompatible coverage data")
289    }
290}
291
292/// Error while trying to write coverage data.
293///
294/// This only happens if the `CoverageWriter` implementation returns an error.
295#[derive(Copy, Clone, Debug)]
296pub struct CoverageWriteError;
297impl fmt::Display for CoverageWriteError {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        f.write_str("error while writing coverage data")
300    }
301}
302
303/// Merges previously dumped coverage data into the coverage counters.
304///
305/// This should be called prior to dumping if coverage data from a previous run
306/// already exists and should be merged with instead of overwritten.
307///
308/// # Safety
309///
310/// This function is not thread-safe and should not be concurrently called from
311/// multiple threads.
312pub unsafe fn merge_coverage(data: &[u8]) -> Result<(), IncompatibleCoverageData> {
313    check_version();
314
315    if __llvm_profile_check_compatibility(data.as_ptr(), data.len() as u64) == 0
316        && __llvm_profile_merge_from_buffer(data.as_ptr(), data.len() as u64) == 0
317    {
318        Ok(())
319    } else {
320        Err(IncompatibleCoverageData)
321    }
322}
323
324/// Resets all coverage counters in the program to zero.
325///
326/// This function should be called after a process forks to avoid recording
327/// coverage data for the parent process twice.
328///
329/// You should also call this after calling `capture_coverage` if you intend to
330/// continue running with the intention of merging with the captured coverage
331/// later.
332pub fn reset_coverage() {
333    check_version();
334
335    unsafe {
336        __llvm_profile_reset_counters();
337    }
338}
339
340/// Returns the profile header "signature" value associated with the current
341/// executable or shared library.
342///
343/// The signature value can be used to for a profile name that is unique to
344/// this load module so that it does not collide with profiles from other
345/// binaries. It also allows shared libraries to dump merged profile data into
346/// its own profile file.
347pub fn module_signature() -> u64 {
348    unsafe { lprofGetLoadModuleSignature() }
349}