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 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
//! This crate provides code coverage and profile-guided optimization (PGO) support
//! for `no_std` and embedded programs.
//!
//! This is done through a modified version of the LLVM profiling runtime (normally
//! part of compiler-rt) from which all dependencies on libc have been removed.
//!
//! All types of instrumentation using the LLVM profiling runtime are supported:
//! - Rust code coverage with `-Cinstrument-coverage`.
//! - Rust profile-guided optimization with `-Cprofile-generate`.
//! - Clang code coverage with `-fprofile-instr-generate -fcoverage-mapping`.
//! - Clang profile-guided optimization with `-fprofile-instr-generate`.
//! - Clang LLVM IR profile-guided optimization with `-fprofile-generate`.
//!
//! Note that to profile both C and Rust code at the same time you must use Clang
//! with the same LLVM version as the LLVM used by rustc. You can pass these flags
//! to C code compiled with the `cc` crates using [environment variables].
//!
//! [environment variables]: https://github.com/rust-lang/cc-rs#external-configuration-via-environment-variables
//!
//! ## Usage
//!
//! Note: This crate requires a recent nightly compiler.
//!
//! 1. Ensure that the following environment variables are set up:
//!
//! ```sh
//! export RUSTFLAGS="-Cinstrument-coverage -Zno-profiler-runtime"
//! ```
//!
//! Note that these flags also apply to build-dependencies and proc
//! macros by default. This can be worked around by explicitly
//! specifying a target when invoking cargo:
//!
//! ```sh
//! # Applies RUSTFLAGS to everything
//! cargo build
//!
//! # Doesn't apply RUSTFLAGS to build dependencies and proc macros
//! cargo build --target x86_64-unknown-linux-gnu
//! ```
//!
//! 2. Add the `minicov` crate as a dependency to your program:
//!
//! ```toml
//! [dependencies]
//! minicov = "0.3"
//! ```
//!
//! 3. Before your program exits, call `minicov::capture_coverage` with a sink (such
//! as `Vec<u8>`) and then dump its contents to a file with the `.profraw` extension:
//!
//! ```ignore
//! fn main() {
//! // ...
//!
//! let mut coverage = vec![];
//! unsafe {
//! // Note that this function is not thread-safe! Use a lock if needed.
//! minicov::capture_coverage(&mut coverage).unwrap();
//! }
//! std::fs::write("output.profraw", coverage).unwrap();
//! }
//! ```
//!
//! If your program is running on a different system than your build system then
//! you will need to transfer this file back to your build system.
//!
//! Sinks must implement the `CoverageWriter` trait. If the default `alloc` feature
//! is enabled then an implementation is provided for `Vec<u8>`.
//!
//! 4. Use a tool such as [grcov] or llvm-cov to generate a human-readable coverage
//! report:
//!
//! ```sh
//! grcov output.profraw -b ./target/debug/my_program -s . -t html -o cov_report
//! ```
//!
//! [grcov]: https://github.com/mozilla/grcov
//!
//! ## Profile-guided optimization
//!
//! The steps for profile-guided optimzation are similar. The only difference is the
//! flags passed in `RUSTFLAGS`:
//!
//! ```sh
//! # First run to generate profiling information.
//! export RUSTFLAGS="-Cprofile-generate -Zno-profiler-runtime"
//! cargo run --target x86_64-unknown-linux-gnu --release
//!
//! # Post-process the profiling information.
//! # The rust-profdata tool comes from cargo-binutils.
//! rust-profdata merge -o output.profdata output.profraw
//!
//! # Optimized build using PGO. minicov is not needed in this step.
//! export RUSTFLAGS="-Cprofile-use=output.profdata"
//! cargo build --target x86_64-unknown-linux-gnu --release
//! ```
#![no_std]
#![warn(missing_docs)]
#![warn(rust_2018_idioms)]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use core::alloc::Layout;
use core::{fmt, slice};
#[allow(non_snake_case)]
#[repr(C)]
struct ProfDataIOVec {
Data: *mut u8,
ElmSize: usize,
NumElm: usize,
UseZeroPadding: i32,
}
#[allow(non_snake_case)]
#[repr(C)]
struct ProfDataWriter {
Write:
unsafe extern "C" fn(This: *mut ProfDataWriter, *mut ProfDataIOVec, NumIOVecs: u32) -> u32,
WriterCtx: *mut u8,
}
// Opaque type for our purposes.
enum VPDataReaderType {}
extern "C" {
fn __llvm_profile_reset_counters();
fn __llvm_profile_merge_from_buffer(profile: *const u8, size: u64) -> i32;
fn __llvm_profile_check_compatibility(profile: *const u8, size: u64) -> i32;
fn __llvm_profile_get_version() -> u64;
fn lprofWriteData(
Writer: *mut ProfDataWriter,
VPDataReader: *mut VPDataReaderType,
SkipNameDataWrite: i32,
) -> i32;
fn lprofGetVPDataReader() -> *mut VPDataReaderType;
}
const INSTR_PROF_RAW_VERSION: u64 = 8;
const VARIANT_MASKS_ALL: u64 = 0xff00000000000000;
// On some target rustc will insert an artificial dependency on the
// __llvm_profile_runtime symbol to ensure the static initializer from LLVM's
// profiling runtime is pulled in by the linker. We don't need any runtime
// initialization so we just provide the symbol here.
#[no_mangle]
static __llvm_profile_runtime: u8 = 0;
// Memory allocation functions used by value profiling. If the "alloc" feature
// is disabled then value profiling will also be disabled.
#[cfg(feature = "alloc")]
#[no_mangle]
unsafe fn minicov_alloc_zeroed(size: usize, align: usize) -> *mut u8 {
alloc::alloc::alloc_zeroed(Layout::from_size_align(size, align).unwrap())
}
#[cfg(feature = "alloc")]
#[no_mangle]
unsafe extern "C" fn minicov_dealloc(ptr: *mut u8, size: usize, align: usize) {
alloc::alloc::dealloc(ptr, Layout::from_size_align(size, align).unwrap())
}
#[cfg(not(feature = "alloc"))]
#[no_mangle]
unsafe fn minicov_alloc_zeroed(_size: usize, _align: usize) -> *mut u8 {
core::ptr::null_mut()
}
#[cfg(not(feature = "alloc"))]
#[no_mangle]
unsafe extern "C" fn minicov_dealloc(_ptr: *mut u8, _size: usize, _align: usize) {}
/// Sink into which coverage data can be written.
///
/// A default implementation for `Vec<u8>` is provided,
pub trait CoverageWriter {
/// Writes the given bytes to the sink.
///
/// This method should return an error if all bytes could not be written to
/// the sink.
fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError>;
}
#[cfg(feature = "alloc")]
impl CoverageWriter for Vec<u8> {
fn write(&mut self, data: &[u8]) -> Result<(), CoverageWriteError> {
self.extend_from_slice(data);
Ok(())
}
}
/// Callback function passed to `lprofWriteData`.
unsafe extern "C" fn write_callback<Writer: CoverageWriter>(
this: *mut ProfDataWriter,
iovecs: *mut ProfDataIOVec,
num_iovecs: u32,
) -> u32 {
let writer = &mut *((*this).WriterCtx as *mut Writer);
for iov in slice::from_raw_parts(iovecs, num_iovecs as usize) {
let len = iov.ElmSize * iov.NumElm;
if iov.Data.is_null() {
// Pad with zero bytes.
let zero = [0; 16];
let mut remaining = len;
while remaining != 0 {
let data = &zero[..usize::min(zero.len(), remaining)];
if writer.write(data).is_err() {
return 1;
}
remaining -= data.len();
}
} else {
let data = slice::from_raw_parts(iov.Data, len);
if writer.write(data).is_err() {
return 1;
}
}
}
0
}
/// Checks that the instrumented binary uses the same profiling data format as
/// the LLVM profiling runtime.
fn check_version() {
let version = unsafe { __llvm_profile_get_version() & !VARIANT_MASKS_ALL };
assert_eq!(
version, INSTR_PROF_RAW_VERSION,
"Runtime and instrumentation version mismatch"
);
}
/// Captures the coverage data for the current program and writes it into the
/// given sink.
///
/// The data should be saved to a file with the `.profraw` extension, which can
/// then be processed using the `llvm-profdata` and `llvm-cov` tools.
///
/// You should call `reset_coverage` afterwards if you intend to continue
/// running the program so that future coverage can be merged with the returned
/// captured coverage.
///
/// # Safety
///
/// This function is not thread-safe and should not be concurrently called from
/// multiple threads.
pub unsafe fn capture_coverage<Writer: CoverageWriter>(
writer: &mut Writer,
) -> Result<(), CoverageWriteError> {
check_version();
let mut prof_writer = ProfDataWriter {
Write: write_callback::<Writer>,
WriterCtx: writer as *mut Writer as *mut u8,
};
let res = lprofWriteData(&mut prof_writer, lprofGetVPDataReader(), 0);
if res == 0 {
Ok(())
} else {
Err(CoverageWriteError)
}
}
/// Error type returned when trying to merge incompatible coverage data.
///
/// This typically happens if the coverage data comes from a different binary.
#[derive(Copy, Clone, Debug)]
pub struct IncompatibleCoverageData;
impl fmt::Display for IncompatibleCoverageData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("incompatible coverage data")
}
}
/// Error while trying to write coverage data.
///
/// This only happens if the `CoverageWriter` implementation returns an error.
#[derive(Copy, Clone, Debug)]
pub struct CoverageWriteError;
impl fmt::Display for CoverageWriteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("error while writing coverage data")
}
}
/// Merges previously dumped coverage data into the coverage counters.
///
/// This should be called prior to dumping if coverage data from a previous run
/// already exists and should be merged with instead of overwritten.
///
/// # Safety
///
/// This function is not thread-safe and should not be concurrently called from
/// multiple threads.
pub unsafe fn merge_coverage(data: &[u8]) -> Result<(), IncompatibleCoverageData> {
check_version();
if __llvm_profile_check_compatibility(data.as_ptr(), data.len() as u64) == 0
&& __llvm_profile_merge_from_buffer(data.as_ptr(), data.len() as u64) == 0
{
Ok(())
} else {
Err(IncompatibleCoverageData)
}
}
/// Resets all coverage counters in the program to zero.
///
/// This function should be called after a process forks to avoid recording
/// coverage data for the parent process twice.
///
/// You should also call this after calling `capture_coverage` if you intend to
/// continue running with the intention of merging with the captured coverage
/// later.
pub fn reset_coverage() {
check_version();
unsafe {
__llvm_profile_reset_counters();
}
}