baryl 0.0.3

Public SDK for Baryl, a full-system emulation and introspection engine
//! Which blocks of guest code have run.
//!
//! Reached as `ctl.subs.coverage`. Most components read coverage rather than
//! write it: subscribe with `#[cov(novel_block)]` and you are called once, the
//! first time the guest executes each distinct block. [`CoverageRef`] is the
//! other direction — telling the subsystem about a block it did not see.
//!
//! A block is identified by a [`GuestAddr`], which is a program counter *and*
//! the page-table root it ran under, so the same address in two processes is
//! two blocks.
//!
//! [`cov_csv`] names the columns of the coverage CSV a run writes.
//!
//! [`CoverageRef::record_block`] reads the subsystem's table without checking
//! it is there, so write `requires(coverage)` on your `#[component]` — though
//! subscribing to `#[cov(...)]` already does that for you.

use core::ffi::c_void;

use crate::abi::SbxPtr;

// Bindgen output cannot satisfy the workspace lints; the allow stops here.
// Two files, because codegen runs before cargo has resolved a feature.
#[cfg(feature = "x86_64")]
mod generated {
    #![allow(non_camel_case_types, non_upper_case_globals, dead_code)]
    include!("generated-x86_64.rs");
}
#[cfg(not(feature = "x86_64"))]
mod generated {
    #![allow(non_camel_case_types, non_upper_case_globals, dead_code)]
    include!("generated.rs");
}
pub use crate::arch::GuestAddr;
pub use generated::*;

/// The novel-block handler as the ABI calls it: your state, the `Control`, and
/// the block's key. `#[cov(novel_block)]` writes one of these for you.
pub type NovelBlockCb = unsafe extern "C" fn(*mut c_void, *mut c_void, *const GuestAddr);

/// Column layout of the coverage CSV a run writes, for reading one back.
///
/// # Examples
///
/// ```ignore
/// for line in text.lines().skip(1) {
///     let cols: Vec<&str> = line.split(',').collect();
///     assert_eq!(cols.len(), cov_csv::COL_COUNT);
///     println!("{} +{}", cols[cov_csv::COL_MODULE], cols[cov_csv::COL_OFFSET]);
/// }
/// ```
pub mod cov_csv {
    /// The header line, verbatim.
    pub const HEADER: &str = "process,module,offset,raw_pc,cr3,icount";
    /// The guest process the block ran in.
    pub const COL_PROCESS: usize = 0;
    /// The image the block belongs to.
    pub const COL_MODULE: usize = 1;
    /// Offset into that image.
    pub const COL_OFFSET: usize = 2;
    /// The guest virtual address, unresolved.
    pub const COL_RAW_PC: usize = 3;
    /// The page-table root it ran under.
    pub const COL_CR3: usize = 4;
    /// Instructions retired when it was first seen.
    pub const COL_ICOUNT: usize = 5;
    /// How many columns a row has.
    pub const COL_COUNT: usize = 6;
}

impl CoverageVtable {
    /// A table with no calls in it. Start from this when filling one in, so a
    /// call you did not implement reads as absent rather than as garbage.
    pub const ABSENT: CoverageVtable = CoverageVtable { record_block: None };
}

impl CoverageRef {
    /// Offer a block to the coverage set; `Some(true)` if this is the first
    /// time it has been seen.
    ///
    /// The subsystem keeps the record itself, so this answers novelty and
    /// nothing else — there is no reading back what it stored. `hash` is the
    /// key's placement hash, which `GuestAddr::block_hash` computes.
    ///
    /// `None` means the run's coverage implementation offers no `record_block`,
    /// not that the block was already known.
    pub fn record_block(&self, key: SbxPtr<GuestAddr>, hash: u64) -> Option<bool> {
        // SAFETY: `vtable` is the coverage `.so`'s static table, valid for the
        // process; `key` is one whole key of the standard this build names.
        let record = unsafe { (*self.vtable).record_block }?;
        Some(unsafe { record(*self, key.as_raw_ptr(), hash) } != 0)
    }
}