baryl 0.0.2

Public SDK for Baryl, a full-system emulation and introspection engine
//! Reading guest memory a value at a time, under one address space.
//!
//! [`GuestMem`] carries the handle and the page-table root together. Every read
//! on it is safe, and pass one where you would otherwise pass an `ArchRef` and
//! a `cr3` side by side.
//!
//! Every read fails closed: an unmapped page answers `None`, never stale bytes.
//! Walking a live guest means walking structures the guest is changing
//! underneath you, so a torn read has to be survivable — check the `None` and
//! give up on that structure, rather than trusting a partial answer.

use std::{string::String, vec, vec::Vec};

use crate::abi::VirtAddr;

use super::ArchRef;

/// The most bytes [`GuestMem::inline_str`] will read, whatever width it is
/// asked for. A length field the guest got wrong cannot turn into a huge read.
pub const MAX_INLINE_STR: usize = 256;

/// A reader over guest memory, bound to one page-table root.
///
/// `Copy` and two words wide, so pass it by value and keep one in a struct
/// walking a guest data structure. Every method is safe: the `cr3` was checked
/// once, when the reader was made.
///
/// # Examples
///
/// ```ignore
/// let Ok(regs): Result<&X64Regs, _> = t.subs.regs() else { return };
/// let mem = t.subs.arch.mem(*regs.cr(3));
///
/// // Walk a linked list, giving up the moment a link does not resolve.
/// let mut node = head;
/// while node != 0 {
///     let Some(name) = mem.inline_str(node + 0x10, 16) else { break };
///     baryl::logging::info!("{name}");
///     let Some(next) = mem.u64(node) else { break };
///     node = next;
/// }
/// ```
#[derive(Clone, Copy)]
pub struct GuestMem {
    arch: ArchRef,
    cr3: u64,
}

impl ArchRef {
    /// A reader over guest memory under `cr3`.
    pub fn mem(&self, cr3: u64) -> GuestMem {
        GuestMem { arch: *self, cr3: cr3 }
    }
}

impl GuestMem {
    /// The same reader against a different address space — how you follow a
    /// kernel pointer into a process's own mappings.
    pub fn at(&self, cr3: u64) -> GuestMem {
        GuestMem { arch: self.arch, cr3: cr3 }
    }

    /// The page-table root this reader is bound to.
    pub fn cr3(&self) -> u64 {
        self.cr3
    }

    /// Fill `buf` from `va`. `false` when any page of the span is unmapped, in
    /// which case `buf` may have been partly written.
    pub fn read(&self, buf: &mut [u8], va: u64) -> bool {
        // SAFETY: past first ring 3, so `cr3` names a live address space.
        unsafe { self.arch.read_virt_into_with_cr3(buf, va, self.cr3) }.is_ok()
    }

    /// The little-endian `u64` at `va`, or `None` if it is not mapped. A
    /// pointer field, a counter, a list link.
    pub fn u64(&self, va: u64) -> Option<u64> {
        let mut b = [0u8; 8];
        self.read(&mut b, va).then(|| u64::from_le_bytes(b))
    }

    /// The little-endian `u32` at `va`, or `None` if it is not mapped.
    pub fn u32(&self, va: u64) -> Option<u32> {
        let mut b = [0u8; 4];
        self.read(&mut b, va).then(|| u32::from_le_bytes(b))
    }

    /// `len` bytes at `va`, owned. `None` if any page of the span is unmapped —
    /// there is no partial answer.
    pub fn blob(&self, va: u64, len: usize) -> Option<Vec<u8>> {
        let mut buf = vec![0u8; len];
        self.read(&mut buf, va).then_some(buf)
    }

    /// A fixed-width inline string field at `va`, cut at its first NUL.
    ///
    /// `width` is clamped to [`MAX_INLINE_STR`]. Bytes that are not UTF-8 are
    /// replaced rather than rejected, so a `comm` field holding anything at all
    /// still gives you a printable name. `None` only when the read itself
    /// failed.
    pub fn inline_str(&self, va: u64, width: usize) -> Option<String> {
        let buf = self.blob(va, width.min(MAX_INLINE_STR))?;
        let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
        Some(String::from_utf8_lossy(&buf[..end]).into_owned())
    }

    /// Whether `va` is mapped executable under this root.
    ///
    /// `false` for an unmapped address as well as a non-executable one, so a
    /// root that is not a page table at all simply answers `false`. That makes
    /// this the cheap test for a candidate `cr3` found by scanning.
    pub fn executable(&self, va: u64) -> bool {
        // SAFETY: as `read`; a candidate root that is not a page table simply
        // fails to translate.
        unsafe { self.arch.translate_attr_with_cr3(VirtAddr(va), self.cr3) }
            .is_some_and(|(_, attr)| attr.executable())
    }

    /// The guest physical address behind `va`, page offset included, or `None`
    /// when nothing is mapped there.
    pub fn translate(&self, va: u64) -> Option<u64> {
        // SAFETY: as `read`.
        unsafe { self.arch.translate_with_cr3(VirtAddr(va), self.cr3) }.map(|pa| pa.0)
    }
}