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
//! The core's registers, `g`, `G`, `p` and `P`, as the family numbers them and its target
//! description names them, and `$icount` after them, the instructions the core has retired, which
//! GDB reads as a register and never writes. A thread the kernel switched away from has the
//! registers its kernel saved, matched by the names the target description gives them, `xx` for
//! each one the save doesn't keep, section E.3, and the core's `$icount`.

use std::borrow::Cow;
use std::collections::BTreeMap;

use super::super::Core;
use super::packet::{from_hex, hex, to_hex};
use super::threads::Picked;
use crate::emulator::Processor;
use crate::emulator::memory::{from_target, to_target};
use crate::soc::Endian;

/// The bytes of `$icount`.
const ICOUNT_BYTES: usize = 8;

/// The target description GDB reads, "Debugging with GDB" for GDB 18.1, section G.2: the family's,
/// with a feature of the run's own before its `</target>` that gives `$icount` the number after the
/// family's last register. A description with no `</target>` is served as it is, with no `$icount`.
pub(super) fn description(cpu: &dyn Processor) -> Cow<'static, str> {
    with_icount(cpu.target_xml(), family_registers(cpu))
}

/// `xml` with `$icount` numbered `number`, or as it is when it has no `</target>`.
fn with_icount(xml: &'static str, number: usize) -> Cow<'static, str> {
    let Some((family, after)) = xml.rsplit_once("</target>") else {
        return Cow::Borrowed(xml);
    };
    Cow::Owned(format!(
        r#"{family}<feature name="org.rigless.run"><reg name="icount" bitsize="64" type="uint64" regnum="{number}" save-restore="no" group="rigless"/></feature></target>{after}"#
    ))
}

/// The register GDB numbers `number` of the core, `$icount` among them, as a number, or `None`
/// for one there's none of or one too wide for a number.
pub(super) fn value(cpu: &dyn Processor, number: usize, endian: Endian) -> Option<u64> {
    match cpu.gdb_register(number) {
        Some(bytes) => (bytes.len() <= ICOUNT_BYTES).then(|| from_target(&bytes, endian)),
        None => icount(cpu).filter(|_| number == family_registers(cpu)),
    }
}

/// How many registers the family numbers, which is `$icount`'s number.
fn family_registers(cpu: &dyn Processor) -> usize {
    (0..).map_while(|number| cpu.gdb_register(number)).count()
}

/// `$icount`, when the description has it.
fn icount(cpu: &dyn Processor) -> Option<u64> {
    cpu.target_xml()
        .contains("</target>")
        .then(|| cpu.retired())
}

/// The family's registers as the core holds them, by the names the target description gives them,
/// each as its bytes in the target's order.
pub(super) fn named(cpu: &dyn Processor) -> BTreeMap<&'static str, Vec<u8>> {
    numbered(cpu.target_xml())
        .into_iter()
        .filter_map(|(name, number)| Some((name, cpu.gdb_register(number)?)))
        .collect()
}

/// Each register `g` reads of the thread `picked` names, as its bytes, `None` for one its save
/// doesn't keep at the register's width, and its width in bytes, or `None` for a thread that's
/// gone.
fn values(
    core: &Core,
    picked: Picked<'_>,
    endian: Endian,
) -> Option<Vec<(Option<Vec<u8>>, usize)>> {
    let cpu = core
        .machine
        .cpu
        .as_deref()
        .expect("the processor between stretches");
    let saved = match picked {
        Picked::Running => None,
        Picked::Switched(saved) => Some(saved),
        Picked::Gone => return None,
    };
    let names: BTreeMap<usize, &str> = numbered(cpu.target_xml())
        .into_iter()
        .map(|(name, number)| (number, name))
        .collect();
    let family = (0..)
        .map_while(|number| cpu.gdb_register(number))
        .enumerate()
        .map(|(number, bytes)| {
            let width = bytes.len();
            match saved {
                None => (Some(bytes), width),
                Some(saved) => (
                    names
                        .get(&number)
                        .and_then(|name| saved.get(name))
                        .filter(|kept| kept.len() == width)
                        .cloned(),
                    width,
                ),
            }
        });
    let counted = icount(cpu).map(|count| {
        let bytes = to_target(count, ICOUNT_BYTES, endian)[..ICOUNT_BYTES].to_vec();
        (Some(bytes), ICOUNT_BYTES)
    });
    Some(family.chain(counted).collect())
}

/// One register's bytes as GDB reads them, or `xx` for each of them when nothing kept it.
fn value_hex((bytes, width): (Option<Vec<u8>>, usize)) -> String {
    bytes.map_or_else(|| "xx".repeat(width), |bytes| to_hex(&bytes))
}

/// `g`, every register of the thread `picked` names.
pub(super) fn registers(core: &Core, picked: Picked<'_>, endian: Endian) -> String {
    values(core, picked, endian).map_or_else(
        || "E01".to_owned(),
        |values| values.into_iter().map(value_hex).collect(),
    )
}

/// `G`, which writes every register `g` reads, or none when it doesn't carry exactly those bytes.
/// `$icount`'s bytes come last, and the core's count stays what it is.
pub(super) fn set_registers(cpu: &mut dyn Processor, rest: &str) -> &'static str {
    let Some(values) = register_values(cpu, rest) else {
        return "E01";
    };
    for (number, bytes) in values.iter().enumerate() {
        cpu.set_gdb_register(number, bytes);
    }
    "OK"
}

/// The bytes `G` writes to each register, in GDB's order, when it carries exactly theirs.
pub(super) fn register_values(cpu: &dyn Processor, rest: &str) -> Option<Vec<Vec<u8>>> {
    let widths: Vec<usize> = (0..)
        .map_while(|number| cpu.gdb_register(number))
        .map(|bytes| bytes.len())
        .collect();
    let counted = icount(cpu).map_or(0, |_| ICOUNT_BYTES);
    let bytes =
        from_hex(rest).filter(|bytes| bytes.len() == widths.iter().sum::<usize>() + counted)?;
    let mut at = 0;
    let values = widths.into_iter().map(|width| {
        let value = bytes[at..at + width].to_vec();
        at += width;
        value
    });
    Some(values.collect())
}

/// `p`, one register of the thread `picked` names.
pub(super) fn register(
    core: &Core,
    picked: Picked<'_>,
    rest: &str,
    endian: Endian,
) -> Option<String> {
    let number = usize::try_from(hex(rest)?).ok()?;
    let value = values(core, picked, endian)?.get(number)?.clone();
    Some(value_hex(value))
}

/// The register `P` writes, by GDB's number, and its bytes, when they're exactly the register's.
/// `$icount` isn't the family's, so it's never written.
pub(super) fn register_write(cpu: &dyn Processor, rest: &str) -> Option<(usize, Vec<u8>)> {
    let (number, value) = rest.split_once('=')?;
    let number = hex(number).and_then(|number| usize::try_from(number).ok())?;
    let width = cpu.gdb_register(number).map(|bytes| bytes.len());
    let bytes = from_hex(value).filter(|bytes| Some(bytes.len()) == width)?;
    Some((number, bytes))
}

/// `value` as the bytes of the register GDB numbers `number`, at its width in the target's order,
/// or `None` when there's no such register or it's too wide for a number.
pub(super) fn register_bytes(
    cpu: &dyn Processor,
    number: usize,
    value: u64,
    endian: Endian,
) -> Option<Vec<u8>> {
    let width = cpu.gdb_register(number)?.len();
    (width <= ICOUNT_BYTES).then(|| to_target(value, width, endian)[..width].to_vec())
}

/// The number GDB gives the register `target_xml` names `pc`. GDB finds the program counter by
/// that name, and so does the server.
pub(super) fn pc_number(target_xml: &str) -> Option<usize> {
    numbered(target_xml)
        .into_iter()
        .find_map(|(name, number)| (name == "pc").then_some(number))
}

/// Each register `target_xml` names, with the number GDB gives it: its `regnum`, or one past the
/// register before it, "Debugging with GDB" for GDB 18.1, section G.2.
fn numbered(target_xml: &str) -> Vec<(&str, usize)> {
    let mut next = 0;
    let mut named = Vec::new();
    for register in target_xml.split("<reg").skip(1) {
        let number = attribute(register, "regnum")
            .and_then(|number| number.parse().ok())
            .unwrap_or(next);
        named.extend(attribute(register, "name").map(|name| (name, number)));
        next = number + 1;
    }
    named
}

/// The value of the attribute `name` in the element `element` starts, which runs to its `>`.
fn attribute<'a>(element: &'a str, name: &str) -> Option<&'a str> {
    let (element, _) = element.split_once('>')?;
    element.split_whitespace().find_map(|each| {
        let quoted = each.strip_prefix(name)?.strip_prefix('=')?;
        Some(quoted.strip_prefix(QUOTES)?.split_once(QUOTES)?.0)
    })
}

/// What an XML attribute's value may be quoted with.
const QUOTES: [char; 2] = ['"', '\''];

#[cfg(test)]
mod tests {
    use super::*;

    /// A register is numbered after the one before it unless it says `regnum`, and the program
    /// counter is the one named `pc`, wherever a description puts it.
    #[test]
    fn the_program_counter_is_the_register_the_description_names_pc() {
        let numbered = r#"<target><feature name="a"><reg name="r0" bitsize="32"/>
            <reg bitsize="32" name="rpc"/><reg name="pc" bitsize="32"/></feature></target>"#;
        assert_eq!(pc_number(numbered), Some(2));
        let placed = r#"<target><reg name="x" regnum="7"/><reg name="pc" type="code_ptr"/>
            <reg name="sp" regnum="40"/></target>"#;
        assert_eq!(pc_number(placed), Some(8));
        assert_eq!(pc_number("<target><reg name='pc' regnum='15'/>"), Some(15));
        assert_eq!(pc_number("<target/>"), None);
    }

    /// `$icount` goes last, before `</target>`, numbered where the family's registers end, and a
    /// description with no `</target>` stays as it is.
    #[test]
    fn icount_follows_the_familys_registers_in_the_description() {
        let xml = r#"<target><feature name="a"><reg name="r0" bitsize="32"/></feature></target>"#;
        let described = with_icount(xml, 1);
        let (family, rest) = described
            .split_once(r#"<feature name="org.rigless.run">"#)
            .unwrap();
        assert_eq!(family, xml.strip_suffix("</target>").unwrap());
        assert!(rest.ends_with("</feature></target>"), "{described}");
        assert!(
            rest.contains(r#"<reg name="icount" bitsize="64" type="uint64" regnum="1""#),
            "{described}"
        );
        assert_eq!(with_icount("<target/>", 40), "<target/>");
    }
}