rigless 0.2.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Memory, `m`, `M` and `X`, answered the way the core's own accesses are, except that a read is
//! a peek until `monitor peek off`, and flash, `vFlashErase` and `vFlashWrite`.

use super::super::Core;
use super::Debugger;
use super::history::Input;
use super::packet::{PACKET_SIZE, from_hex, hex, to_hex};
use crate::emulator::memory::{from_target, to_target};
use crate::emulator::{Access, AccessKind, BitAlias};
use crate::soc::Width;

/// An address and a length, as `m` and `M` write them before their `:`, when the span doesn't run
/// past the end of the address space.
pub(super) fn span(text: &str) -> Option<(u64, u64)> {
    let (address, length) = text.split_once(',')?;
    let (address, length) = (hex(address)?, hex(length)?);
    address.checked_add(length)?;
    Some((address, length))
}

/// What a debugger's reads of memory are.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Reads {
    /// What a read would answer, with nothing in the run changed and nobody told.
    Peeks,
    /// The firmware's reads, side effects and all, which the run hears of.
    Firmware,
}

pub(super) fn read_memory(core: &mut Core, rest: &str, reads: Reads) -> Option<String> {
    let (address, length) = span(rest)?;
    let length = length.min(PACKET_SIZE as u64 / 2);
    let bytes = core.debugger_read(address, length, reads);
    // The bytes before the first one nothing answers, and GDB asks again from there.
    (!bytes.is_empty()).then(|| to_hex(&bytes))
}

/// `M`'s address and bytes, when it's well formed.
pub(super) fn hex_write(rest: &str) -> Option<(u64, Vec<u8>)> {
    let (span_text, data) = rest.split_once(':')?;
    let (address, length) = span(span_text)?;
    let bytes = from_hex(data)?;
    (bytes.len() as u64 == length).then_some((address, bytes))
}

/// `X`'s address and bytes, when it's well formed. Its data is binary, with `}` escaping the byte
/// after it.
pub(super) fn binary_write(rest: &str) -> Option<(u64, Vec<u8>)> {
    let (span_text, data) = rest.split_once(':')?;
    let (address, length) = span(span_text)?;
    let bytes = binary(data)?;
    (bytes.len() as u64 == length).then_some((address, bytes))
}

/// Binary data as `X` and `vFlashWrite` carry it, with `}` escaping the byte after it.
fn binary(data: &str) -> Option<Vec<u8>> {
    let mut bytes = Vec::with_capacity(data.len());
    let mut escaped = false;
    for character in data.chars() {
        let byte = u8::try_from(u32::from(character)).ok()?;
        if escaped {
            bytes.push(byte ^ 0x20);
            escaped = false;
        } else if byte == b'}' {
            escaped = true;
        } else {
            bytes.push(byte);
        }
    }
    Some(bytes)
}

impl Debugger<'_> {
    /// `vFlashErase`, `vFlashWrite` and `vFlashDone`, "Debugging with GDB" for GDB 18.1, section
    /// E.3, `rest` being the packet after its `v`. Erases and writes are done as they come, so
    /// `vFlashDone` has nothing left to do.
    pub(super) fn flash(&mut self, core: &mut Core, rest: &str) -> &'static str {
        if let Some(erased) = rest.strip_prefix("FlashErase:") {
            self.flash_erase(core, erased)
        } else if let Some(written) = rest.strip_prefix("FlashWrite:") {
            self.flash_write(core, written)
        } else if rest == "FlashDone" {
            "OK"
        } else {
            ""
        }
    }

    /// `vFlashErase:ADDR,LENGTH`: the blocks erased to what their region erases to, when they're
    /// whole blocks of one flash region, or `E01`.
    fn flash_erase(&mut self, core: &mut Core, rest: &str) -> &'static str {
        let Some((address, erased)) = span(rest).and_then(|(address, length)| {
            let value = self.map.erases(address, length)?;
            let length = usize::try_from(length).expect("a region in memory");
            Some((address, vec![value; length]))
        }) else {
            return "E01";
        };
        let flash = Input::Flash {
            address,
            bytes: erased.clone(),
        };
        self.note(core, flash, |core| core.debugger_flash(address, &erased));
        "OK"
    }

    /// `vFlashWrite:ADDR:DATA`: the bytes written, or `E.memtype` where they aren't all in one
    /// flash region.
    fn flash_write(&mut self, core: &mut Core, rest: &str) -> &'static str {
        let written = rest.split_once(':').and_then(|(address, data)| {
            let address = hex(address)?;
            let bytes = binary(data)?;
            address.checked_add(bytes.len() as u64)?;
            Some((address, bytes))
        });
        match written {
            Some((address, bytes)) if self.map.holds(address, bytes.len() as u64) => {
                let flash = Input::Flash {
                    address,
                    bytes: bytes.clone(),
                };
                self.note(core, flash, |core| core.debugger_flash(address, &bytes));
                "OK"
            }
            Some(_) => "E.memtype",
            None => "E01",
        }
    }
}

/// The widest access a debugger's span makes at `address` with `left` bytes to go, no wider than a
/// pointer of the family's data model, `pointer`: as wide as the address is aligned to and the
/// bytes left cover, so a peripheral's register is read or written once.
fn widest(address: u64, left: u64, pointer: Width) -> Width {
    [Width::W64, Width::W32, Width::W16]
        .into_iter()
        .filter(|width| width.bytes() <= pointer.bytes())
        .find(|width| {
            let bytes = u64::from(width.bytes());
            address.is_multiple_of(bytes) && left >= bytes
        })
        .unwrap_or(Width::W8)
}

/// The value of `width` at `address` as a debugger peeks it, in the part's byte order, or `None`
/// where nothing answers.
pub(super) fn peek(core: &mut Core, address: u64, width: Width) -> Option<u64> {
    let bytes = core.debugger_read(address, u64::from(width.bytes()), Reads::Peeks);
    (bytes.len() as u64 == u64::from(width.bytes()))
        .then(|| from_target(&bytes, core.machine.cpu().byte_order(address)))
}

impl Core {
    /// A debugger's read of `length` bytes from `address`, answered as the core's own reads are,
    /// with no program counter, up to the first byte nothing answers, as `reads` says the
    /// debugger's reads are. A span that runs off the end of the address space reads nothing.
    pub(super) fn debugger_read(&mut self, address: u64, length: u64, reads: Reads) -> Vec<u8> {
        let mut bytes = Vec::new();
        let Some(end) = address.checked_add(length) else {
            return bytes;
        };
        let mut at = address;
        while at < end {
            let width = if self.machine.mapped_byte(at) {
                Width::W8
            } else {
                widest(at, end - at, self.machine.cpu().data_model().pointer())
            };
            let Some(value) =
                self.debugger_serve(self.debugger_access(AccessKind::Read, at, width, 0), reads)
            else {
                break;
            };
            let count = usize::try_from(width.bytes()).expect("a width fits");
            let order = self.machine.cpu().byte_order(at);
            bytes.extend_from_slice(&to_target(value, count, order)[..count]);
            at += u64::from(width.bytes());
        }
        bytes
    }

    /// A debugger's write of `bytes` at `address`, and whether something took every byte. Memory
    /// the core maps takes it whether or not the guest may write there. The span is inside the
    /// address space.
    pub(super) fn debugger_write(&mut self, address: u64, bytes: &[u8]) -> bool {
        let mut at = 0;
        while at < bytes.len() {
            let target = address + at as u64;
            if self.machine.mapped_byte(target) {
                self.machine.read_only_written |= self.machine.memory.read_only(target);
                self.machine.memory.place(target, &bytes[at..=at]);
                self.machine.invalidate(target, 1);
                at += 1;
                continue;
            }
            let pointer = self.machine.cpu().data_model().pointer();
            let width = widest(target, (bytes.len() - at) as u64, pointer);
            let count = usize::try_from(width.bytes()).expect("a width fits");
            let value = from_target(
                &bytes[at..at + count],
                self.machine.cpu().byte_order(target),
            );
            if self
                .debugger_serve(
                    self.debugger_access(AccessKind::Write, target, width, value),
                    Reads::Firmware,
                )
                .is_none()
            {
                return false;
            }
            at += count;
        }
        true
    }

    /// Places `bytes` a debugger flashes at `address`, all in one flash region the core maps, as
    /// [`Core::debugger_write`] places them.
    pub(super) fn debugger_flash(&mut self, address: u64, bytes: &[u8]) {
        let placed = self.machine.memory.place(address, bytes);
        debug_assert!(placed, "flash in the memory map is memory the core maps");
        self.machine.read_only_written |= self.machine.memory.read_only(address);
        self.machine.invalidate(address, bytes.len() as u64);
    }

    /// Answers a debugger's access as the processor's own are answered: by its own bus, by the
    /// memory it maps, by the unit a bit alias stands for, or by the models, or `None` where
    /// nothing does. A read the models answer is their peek, which nobody hears of, when `reads`
    /// says reads are peeks. A write is a write.
    fn debugger_serve(&mut self, access: Access, reads: Reads) -> Option<u64> {
        let write = (access.kind == AccessKind::Write).then_some(access.value);
        let machine = &mut self.machine;
        if let Some(answer) =
            machine
                .cpu()
                .debugger_access(write, access.address, access.width, access.virtual_time)
        {
            let traced = machine.cpu().take_debugger_trace();
            if let Some(trace) = self.trace.as_mut().filter(|_| !traced.is_empty()) {
                trace.send(&traced);
            }
            return Some(answer);
        }
        if let Some(answer) =
            machine.mapped(access.kind, access.address, access.width, access.value)
        {
            return Some(answer);
        }
        if let Some(BitAlias { unit, bit }) = machine.cpu().bit_alias(access.address, access.width)
        {
            return self.debugger_bit(access, unit, bit, reads);
        }
        if access.kind == AccessKind::Read && reads == Reads::Peeks {
            return self
                .driven
                .peek(access.virtual_time, access.address, access.width);
        }
        let (answer, next, _) = self.driven.access(access, machine);
        // The models may need the run back sooner after a debugger's access, as after the
        // firmware's.
        machine.deadline = next;
        (!self.driven.refused()).then_some(answer)
    }

    /// A debugger's access at a bit alias, as bit `bit` of the unit at `unit`: a read answers the
    /// bit of the unit, read as `reads` says, and a write peeks the unit and writes it back with
    /// the bit set or cleared, so a model sees one write of the unit, as it does from the core.
    fn debugger_bit(&mut self, access: Access, unit: u64, bit: u32, reads: Reads) -> Option<u64> {
        let (reads, write) = match access.kind {
            AccessKind::Read => (reads, None),
            AccessKind::Write => (Reads::Peeks, Some(access.value & 1)),
        };
        let read = Access {
            kind: AccessKind::Read,
            address: unit,
            value: 0,
            ..access
        };
        let held = self.debugger_serve(read, reads)?;
        let Some(value) = write else {
            return Some(held >> bit & 1);
        };
        let written = Access {
            address: unit,
            value: held & !(1 << bit) | value << bit,
            ..access
        };
        self.debugger_serve(written, Reads::Firmware).map(|_| value)
    }

    /// An access the debugger makes, at the instant the run stands at.
    fn debugger_access(&self, kind: AccessKind, address: u64, width: Width, value: u64) -> Access {
        let retired = self.machine.retired();
        Access {
            kind,
            address,
            width,
            value,
            instruction_count: retired,
            virtual_time: self.machine.time(retired),
            program_counter: None,
        }
    }
}