baryl 0.0.2

Public SDK for Baryl, a full-system emulation and introspection engine
//! Reading a CPU fault: which one it was, and what its error code meant.
//!
//! An `#[arch(cpu_exception)]` handler is passed a `CpuExceptionContext`,
//! which is a vector and an error code and nothing else. The accessors here
//! decode both without you keeping Intel's tables to hand.

use super::{
    CpuExceptionContext, X64_EXCP_AC, X64_EXCP_BP, X64_EXCP_BR, X64_EXCP_DB, X64_EXCP_DE,
    X64_EXCP_DF, X64_EXCP_GP, X64_EXCP_MC, X64_EXCP_MF, X64_EXCP_NM, X64_EXCP_NMI, X64_EXCP_NP,
    X64_EXCP_OF, X64_EXCP_PF, X64_EXCP_SS, X64_EXCP_TS, X64_EXCP_UD, X64_EXCP_XF,
    X64_PF_ERR_INSN_FETCH, X64_PF_ERR_PRESENT, X64_PF_ERR_RESERVED, X64_PF_ERR_USER,
    X64_PF_ERR_WRITE,
};

/// What the guest was trying to do when a page fault stopped it.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum AccessType {
    /// A load.
    Read,
    /// A store.
    Write,
    /// An instruction fetch.
    Execute,
}

impl AccessType {
    /// `'R'`, `'W'` or `'X'` — a single character for a crash signature or a
    /// log line.
    pub fn mnemonic(self) -> char {
        match self {
            Self::Read => 'R',
            Self::Write => 'W',
            Self::Execute => 'X',
        }
    }
}

/// Which fault this was, and what its error code carried.
///
/// # Examples
///
/// ```ignore
/// #[arch(cpu_exception)]
/// fn on_fault(&mut self, _t: &mut Control, ctx: &CpuExceptionContext) {
///     let Some(access) = ctx.pf_access_type() else {
///         baryl::logging::info!("{} at pc", ctx.name());
///         return;
///     };
///     // A #PF on a page that was present is a protection violation, not a
///     // page the kernel is about to fill in.
///     if ctx.pf_was_protection_violation() == Some(true) && ctx.pf_was_user() == Some(true) {
///         baryl::logging::warn!("ring 3 {} violation", access.mnemonic());
///     }
/// }
/// ```
impl CpuExceptionContext {
    /// Vector 14 — the guest touched an address its page tables would not let
    /// it. The one fault with a decodable error code, which the `pf_*` methods
    /// read.
    pub fn is_page_fault(&self) -> bool {
        self.vector == X64_EXCP_PF
    }

    /// Vector 13 — a non-canonical address, a segment violation, a privileged
    /// instruction from ring 3.
    pub fn is_general_protection(&self) -> bool {
        self.vector == X64_EXCP_GP
    }

    /// Vector 6 — the guest tried to execute something that is not an
    /// instruction.
    pub fn is_invalid_opcode(&self) -> bool {
        self.vector == X64_EXCP_UD
    }

    /// Vector 8 — a fault raised while handling a fault.
    pub fn is_double_fault(&self) -> bool {
        self.vector == X64_EXCP_DF
    }

    /// Vector 3 — an `int3`, whether the guest's own or one someone planted.
    pub fn is_breakpoint(&self) -> bool {
        self.vector == X64_EXCP_BP
    }

    /// The Intel mnemonic: `"#PF"`, `"#GP"`, `"#UD"` and so on.
    ///
    /// `"?"` for a vector this build has no name for — the reserved ones, and
    /// anything from 32 up, which are external interrupts rather than faults.
    /// Print `self.vector` alongside when you see it.
    pub fn name(&self) -> &'static str {
        match self.vector {
            X64_EXCP_DE => "#DE",
            X64_EXCP_DB => "#DB",
            X64_EXCP_NMI => "NMI",
            X64_EXCP_BP => "#BP",
            X64_EXCP_OF => "#OF",
            X64_EXCP_BR => "#BR",
            X64_EXCP_UD => "#UD",
            X64_EXCP_NM => "#NM",
            X64_EXCP_DF => "#DF",
            X64_EXCP_TS => "#TS",
            X64_EXCP_NP => "#NP",
            X64_EXCP_SS => "#SS",
            X64_EXCP_GP => "#GP",
            X64_EXCP_PF => "#PF",
            X64_EXCP_MF => "#MF",
            X64_EXCP_AC => "#AC",
            X64_EXCP_MC => "#MC",
            X64_EXCP_XF => "#XF",
            _ => "?",
        }
    }

    /// Whether `self.error` means anything.
    ///
    /// True for the seven vectors that push an error code — #DF, #TS, #NP, #SS,
    /// #GP, #PF and #AC. On every other vector `error` is zero and carries
    /// nothing, so do not print it.
    pub fn has_error_code(&self) -> bool {
        matches!(
            self.vector,
            X64_EXCP_DF
                | X64_EXCP_TS
                | X64_EXCP_NP
                | X64_EXCP_SS
                | X64_EXCP_GP
                | X64_EXCP_PF
                | X64_EXCP_AC
        )
    }

    /// What the guest was doing when a page fault stopped it — read, write or
    /// instruction fetch.
    ///
    /// `None` on any vector other than #PF: the error code's bits mean
    /// something different per vector, so there is no honest answer for the
    /// rest. This is also the cheap way to branch on "is this a page fault".
    pub fn pf_access_type(&self) -> Option<AccessType> {
        if !self.is_page_fault() {
            return None;
        }
        if self.error & X64_PF_ERR_INSN_FETCH != 0 {
            Some(AccessType::Execute)
        } else if self.error & X64_PF_ERR_WRITE != 0 {
            Some(AccessType::Write)
        } else {
            Some(AccessType::Read)
        }
    }

    /// `Some(true)` when the page fault was raised in ring 3 — user code, not
    /// the kernel. `None` on any other vector.
    pub fn pf_was_user(&self) -> Option<bool> {
        self.is_page_fault()
            .then_some(self.error & X64_PF_ERR_USER != 0)
    }

    /// Whether the page was there and the access was not allowed, as against
    /// the page not being there at all.
    ///
    /// `Some(true)` is a protection violation and the interesting case.
    /// `Some(false)` is a page the kernel is about to fill in — demand paging,
    /// lazy allocation, copy-on-write — and is most of what a running guest
    /// faults on. `None` on any other vector.
    pub fn pf_was_protection_violation(&self) -> Option<bool> {
        self.is_page_fault()
            .then_some(self.error & X64_PF_ERR_PRESENT != 0)
    }

    /// `Some(true)` when the walk hit a page-table entry with a reserved bit
    /// set — a page table the kernel would never have written that way.
    /// `None` on any other vector.
    pub fn pf_reserved_bit_set(&self) -> Option<bool> {
        self.is_page_fault()
            .then_some(self.error & X64_PF_ERR_RESERVED != 0)
    }
}