use core::fmt;
use crate::core::clock::GlobalTime;
use crate::core::device::DeviceClass;
use crate::core::space::{AddressSpace, MemAttrs, RequesterId};
use crate::core::state::{ChunkReader, MachineShape, StateReader, StateWriter};
use crate::machine::Machine;
use super::arch::Arch;
#[must_use]
pub fn debug_attrs(requester: RequesterId) -> MemAttrs {
MemAttrs::DEBUG.with_requester(requester)
}
#[derive(Debug)]
pub enum TargetError {
NoSuchCpu,
NoSuchRegister,
Fault,
Unsupported,
LayoutMismatch {
class: &'static str,
expected: u32,
found: u32,
},
Machine(crate::Error),
}
impl TargetError {
#[must_use]
pub const fn code(&self) -> u8 {
match self {
TargetError::NoSuchCpu => 3, TargetError::Fault | TargetError::Machine(_) => 5, TargetError::NoSuchRegister | TargetError::Unsupported => 22, TargetError::LayoutMismatch { .. } => 8, }
}
}
impl fmt::Display for TargetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TargetError::NoSuchCpu => f.write_str("no such cpu"),
TargetError::NoSuchRegister => f.write_str("no such register"),
TargetError::Fault => f.write_str("the guest bus refused the access"),
TargetError::Unsupported => f.write_str("unsupported"),
TargetError::LayoutMismatch {
class,
expected,
found,
} => write!(
f,
"`{class}` state version {found} but its gdb register map was written \
against version {expected}"
),
TargetError::Machine(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for TargetError {}
impl From<crate::Error> for TargetError {
fn from(e: crate::Error) -> TargetError {
TargetError::Machine(e)
}
}
pub type TargetResult<T> = Result<T, TargetError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StopKind {
Trap,
Breakpoint,
Watchpoint {
addr: u64,
},
Interrupt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stop {
pub cpu: usize,
pub kind: StopKind,
}
impl Stop {
#[must_use]
pub const fn signal(&self) -> u8 {
match self.kind {
StopKind::Interrupt => 2,
_ => 5, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct WatchSupport {
pub write: bool,
pub read: bool,
pub access: bool,
}
pub trait DebugTarget {
fn cpu_count(&self) -> usize;
fn cpu_path(&self, cpu: usize) -> TargetResult<&str>;
fn arch(&self, cpu: usize) -> TargetResult<&'static Arch>;
fn read_registers(&self, cpu: usize) -> TargetResult<Vec<u8>>;
fn write_registers(&mut self, cpu: usize, data: &[u8]) -> TargetResult<()>;
fn read_register(&self, cpu: usize, index: usize) -> TargetResult<Vec<u8>>;
fn write_register(&mut self, cpu: usize, index: usize, data: &[u8]) -> TargetResult<()>;
fn read_memory(&self, cpu: usize, addr: u64, dst: &mut [u8]) -> TargetResult<()>;
fn write_memory(&mut self, cpu: usize, addr: u64, src: &[u8]) -> TargetResult<()>;
fn add_breakpoint(&mut self, addr: u64) -> TargetResult<()>;
fn remove_breakpoint(&mut self, addr: u64) -> TargetResult<()>;
fn watch_support(&self) -> WatchSupport {
WatchSupport::default()
}
fn add_watchpoint(&mut self, _addr: u64, _len: u64) -> TargetResult<()> {
Err(TargetError::Unsupported)
}
fn remove_watchpoint(&mut self, _addr: u64, _len: u64) -> TargetResult<()> {
Err(TargetError::Unsupported)
}
fn step(&mut self, cpu: usize) -> TargetResult<Stop>;
fn begin_resume(&mut self) {}
fn resume(&mut self) -> TargetResult<Option<Stop>>;
fn monitor(&mut self, _command: &str) -> Option<String> {
None
}
}
const FREE_SLICE: GlobalTime = GlobalTime::from_nanos(10_000_000);
const FINE_TICKS: u32 = 4096;
const MAX_TICKS_PER_INSN: u32 = 4096;
#[derive(Debug)]
struct Cpu {
device: usize,
path: String,
class: &'static DeviceClass,
arch: &'static Arch,
domain: crate::core::clock::DomainId,
requester: RequesterId,
space: Option<usize>,
}
#[derive(Debug)]
struct Watch {
addr: u64,
len: u64,
shadow: Vec<u8>,
}
#[derive(Debug)]
pub struct MachineTarget<'a> {
machine: &'a mut Machine,
cpus: Vec<Cpu>,
breakpoints: Vec<u64>,
watchpoints: Vec<Watch>,
suppress: Vec<Option<u64>>,
}
impl<'a> MachineTarget<'a> {
#[must_use]
pub fn new(machine: &'a mut Machine) -> MachineTarget<'a> {
let mut cpus = Vec::new();
for (index, entry) in machine.devices().iter().enumerate() {
let Some(arch) = super::arch::for_class(entry.class().name) else {
continue;
};
let Some(domain) = entry.domain() else {
continue;
};
cpus.push(Cpu {
device: index,
path: entry.path().to_string(),
class: entry.class(),
arch,
domain,
requester: entry.requester(),
space: entry.space_index(),
});
}
let suppress = vec![None; cpus.len()];
MachineTarget {
machine,
cpus,
breakpoints: Vec::new(),
watchpoints: Vec::new(),
suppress,
}
}
#[must_use]
pub fn machine(&self) -> &Machine {
self.machine
}
pub fn machine_mut(&mut self) -> &mut Machine {
self.machine
}
fn cpu(&self, index: usize) -> TargetResult<&Cpu> {
self.cpus.get(index).ok_or(TargetError::NoSuchCpu)
}
fn space(&self, cpu: &Cpu) -> TargetResult<&AddressSpace> {
let index = cpu.space.ok_or(TargetError::Fault)?;
self.machine
.spaces()
.get(index)
.map(|entry| entry.space().as_ref())
.ok_or(TargetError::Fault)
}
fn chunk(&self, cpu: &Cpu) -> TargetResult<Vec<u8>> {
if !cpu.arch.check() {
return Err(TargetError::LayoutMismatch {
class: cpu.class.name,
expected: cpu.arch.verified_version,
found: cpu.class.version,
});
}
let entry = self
.machine
.devices()
.get(cpu.device)
.ok_or(TargetError::NoSuchCpu)?;
let mut writer = StateWriter::new(MachineShape::new());
{
let mut chunk = writer.chunk(&cpu.path, cpu.class.name, cpu.class.version)?;
entry.device().save(&mut chunk)?;
}
let bytes = writer.to_vec()?;
let reader = StateReader::new(&bytes)?;
let (_, _, data) = reader.load_raw(&cpu.path)?;
if data.len() < cpu.arch.chunk_reach() {
return Err(TargetError::LayoutMismatch {
class: cpu.class.name,
expected: cpu.arch.verified_version,
found: cpu.class.version,
});
}
Ok(data.to_vec())
}
fn set_chunk(&mut self, cpu: usize, data: &[u8]) -> TargetResult<()> {
let device = self.cpu(cpu)?.device;
let entry = self
.machine
.devices()
.get(device)
.ok_or(TargetError::NoSuchCpu)?;
let mut reader = ChunkReader::new(data);
entry.device().load(&mut reader)?;
Ok(())
}
fn field(chunk: &[u8], offset: usize, bytes: usize) -> TargetResult<u64> {
let slice = chunk
.get(offset..offset.checked_add(bytes).ok_or(TargetError::Fault)?)
.ok_or(TargetError::Fault)?;
let mut value: u64 = 0;
for (i, byte) in slice.iter().enumerate() {
value |= u64::from(*byte) << (i * 8);
}
Ok(value)
}
fn pc_of(&self, index: usize) -> TargetResult<u64> {
let cpu = self.cpu(index)?;
let chunk = self.chunk(cpu)?;
let reg = cpu
.arch
.regs
.get(cpu.arch.pc)
.ok_or(TargetError::NoSuchRegister)?;
Self::field(&chunk, reg.offset, reg.bytes)
}
fn retired(&self, index: usize) -> TargetResult<Option<u64>> {
let cpu = self.cpu(index)?;
let Some(counter) = cpu.arch.retire else {
return Ok(None);
};
let chunk = self.chunk(cpu)?;
Self::field(&chunk, counter.offset, counter.bytes).map(Some)
}
fn tick(&mut self) -> TargetResult<()> {
let now = self.machine.now();
let mut deadline: Option<GlobalTime> = None;
{
let forest = self.machine.clocks();
for cpu in &self.cpus {
let Ok(tick) = forest.ticks(cpu.domain) else {
continue;
};
let mut ahead = 1u64;
while let Ok(at) =
forest.global_time_of_tick(cpu.domain, tick.saturating_add(ahead))
{
if at > now {
deadline = Some(match deadline {
Some(best) if best <= at => best,
_ => at,
});
break;
}
ahead += 1;
if ahead > 1024 {
break;
}
}
}
}
let deadline = deadline.unwrap_or_else(|| now.saturating_add(FREE_SLICE));
self.machine.step_until(deadline)?;
Ok(())
}
fn poll_watchpoints(&mut self) -> TargetResult<Option<u64>> {
if self.watchpoints.is_empty() {
return Ok(None);
}
let mut hit = None;
for i in 0..self.watchpoints.len() {
let (addr, len) = {
let watch = &self.watchpoints[i];
(watch.addr, watch.len)
};
let mut now = vec![0u8; usize::try_from(len).unwrap_or(0)];
if self.read_memory(0, addr, &mut now).is_err() {
continue;
}
let watch = &mut self.watchpoints[i];
if watch.shadow != now {
watch.shadow = now;
if hit.is_none() {
hit = Some(addr);
}
}
}
Ok(hit)
}
fn resync_watchpoints(&mut self) {
for i in 0..self.watchpoints.len() {
let (addr, len) = {
let watch = &self.watchpoints[i];
(watch.addr, watch.len)
};
let mut now = vec![0u8; usize::try_from(len).unwrap_or(0)];
if self.read_memory(0, addr, &mut now).is_ok() {
self.watchpoints[i].shadow = now;
}
}
}
fn breakpoint_hit(&mut self) -> TargetResult<Option<Stop>> {
if self.breakpoints.is_empty() {
return Ok(None);
}
for index in 0..self.cpus.len() {
let pc = self.pc_of(index)?;
if self.suppress.get(index).copied().flatten() == Some(pc) {
continue;
}
if let Some(slot) = self.suppress.get_mut(index) {
*slot = None;
}
if self.breakpoints.contains(&pc) {
return Ok(Some(Stop {
cpu: index,
kind: StopKind::Breakpoint,
}));
}
}
Ok(None)
}
}
impl DebugTarget for MachineTarget<'_> {
fn cpu_count(&self) -> usize {
self.cpus.len()
}
fn cpu_path(&self, cpu: usize) -> TargetResult<&str> {
Ok(self.cpu(cpu)?.path.as_str())
}
fn arch(&self, cpu: usize) -> TargetResult<&'static Arch> {
Ok(self.cpu(cpu)?.arch)
}
fn read_registers(&self, cpu: usize) -> TargetResult<Vec<u8>> {
let entry = self.cpu(cpu)?;
let chunk = self.chunk(entry)?;
let mut out = Vec::with_capacity(entry.arch.packet_len());
for reg in entry.arch.regs {
let slice = chunk
.get(reg.offset..reg.offset + reg.bytes)
.ok_or(TargetError::NoSuchRegister)?;
out.extend_from_slice(slice);
}
Ok(out)
}
fn write_registers(&mut self, cpu: usize, data: &[u8]) -> TargetResult<()> {
let entry = self.cpu(cpu)?;
if data.len() != entry.arch.packet_len() {
return Err(TargetError::NoSuchRegister);
}
let mut chunk = self.chunk(entry)?;
let mut at = 0usize;
for reg in entry.arch.regs {
let src = data.get(at..at + reg.bytes).ok_or(TargetError::Fault)?;
let dst = chunk
.get_mut(reg.offset..reg.offset + reg.bytes)
.ok_or(TargetError::Fault)?;
dst.copy_from_slice(src);
at += reg.bytes;
}
self.set_chunk(cpu, &chunk)
}
fn read_register(&self, cpu: usize, index: usize) -> TargetResult<Vec<u8>> {
let entry = self.cpu(cpu)?;
let reg = entry
.arch
.regs
.get(index)
.ok_or(TargetError::NoSuchRegister)?;
let chunk = self.chunk(entry)?;
chunk
.get(reg.offset..reg.offset + reg.bytes)
.map(<[u8]>::to_vec)
.ok_or(TargetError::NoSuchRegister)
}
fn write_register(&mut self, cpu: usize, index: usize, data: &[u8]) -> TargetResult<()> {
let entry = self.cpu(cpu)?;
let reg = *entry
.arch
.regs
.get(index)
.ok_or(TargetError::NoSuchRegister)?;
if data.len() != reg.bytes {
return Err(TargetError::NoSuchRegister);
}
let mut chunk = self.chunk(entry)?;
let dst = chunk
.get_mut(reg.offset..reg.offset + reg.bytes)
.ok_or(TargetError::Fault)?;
dst.copy_from_slice(data);
self.set_chunk(cpu, &chunk)
}
fn read_memory(&self, cpu: usize, addr: u64, dst: &mut [u8]) -> TargetResult<()> {
let entry = self.cpu(cpu)?;
let space = self.space(entry)?;
space
.read_bytes(addr, dst, debug_attrs(entry.requester))
.map_err(|_| TargetError::Fault)
}
fn write_memory(&mut self, cpu: usize, addr: u64, src: &[u8]) -> TargetResult<()> {
{
let entry = self.cpu(cpu)?;
let space = self.space(entry)?;
space
.write_bytes(addr, src, debug_attrs(entry.requester))
.map_err(|_| TargetError::Fault)?;
}
self.resync_watchpoints();
Ok(())
}
fn add_breakpoint(&mut self, addr: u64) -> TargetResult<()> {
if !self.breakpoints.contains(&addr) {
self.breakpoints.push(addr);
}
Ok(())
}
fn remove_breakpoint(&mut self, addr: u64) -> TargetResult<()> {
self.breakpoints.retain(|a| *a != addr);
Ok(())
}
fn watch_support(&self) -> WatchSupport {
WatchSupport {
write: true,
read: false,
access: false,
}
}
fn add_watchpoint(&mut self, addr: u64, len: u64) -> TargetResult<()> {
if len == 0 || len > 4096 {
return Err(TargetError::Unsupported);
}
if self
.watchpoints
.iter()
.any(|w| w.addr == addr && w.len == len)
{
return Ok(());
}
let mut shadow = vec![0u8; usize::try_from(len).map_err(|_| TargetError::Unsupported)?];
self.read_memory(0, addr, &mut shadow)?;
self.watchpoints.push(Watch { addr, len, shadow });
Ok(())
}
fn remove_watchpoint(&mut self, addr: u64, len: u64) -> TargetResult<()> {
self.watchpoints
.retain(|w| !(w.addr == addr && w.len == len));
Ok(())
}
fn step(&mut self, cpu: usize) -> TargetResult<Stop> {
let before_pc = self.pc_of(cpu)?;
let before_retired = self.retired(cpu)?;
for _ in 0..MAX_TICKS_PER_INSN {
self.tick()?;
let moved = match before_retired {
Some(before) => self.retired(cpu)? != Some(before),
None => self.pc_of(cpu)? != before_pc,
};
if moved {
break;
}
}
if let Some(slot) = self.suppress.get_mut(cpu) {
*slot = None;
}
if let Some(addr) = self.poll_watchpoints()? {
return Ok(Stop {
cpu,
kind: StopKind::Watchpoint { addr },
});
}
Ok(Stop {
cpu,
kind: StopKind::Trap,
})
}
fn begin_resume(&mut self) {
for index in 0..self.cpus.len() {
let here = self.pc_of(index).ok();
if let Some(slot) = self.suppress.get_mut(index) {
*slot = here;
}
}
}
fn resume(&mut self) -> TargetResult<Option<Stop>> {
if self.breakpoints.is_empty() && self.watchpoints.is_empty() {
let deadline = self.machine.now().saturating_add(FREE_SLICE);
self.machine.run_until(deadline)?;
return Ok(None);
}
for _ in 0..FINE_TICKS {
self.tick()?;
if let Some(stop) = self.breakpoint_hit()? {
return Ok(Some(stop));
}
if let Some(addr) = self.poll_watchpoints()? {
return Ok(Some(Stop {
cpu: 0,
kind: StopKind::Watchpoint { addr },
}));
}
}
Ok(None)
}
fn monitor(&mut self, command: &str) -> Option<String> {
let mut words = command.split_whitespace();
match words.next()? {
"help" => Some(
"rsemu monitor commands:\n \
devices the device tree, with class and instance path\n \
time the machine's current virtual instant\n \
hash the machine state hash (ROADMAP.md \u{a7}0)\n"
.to_string(),
),
"devices" => {
let mut out = String::new();
for entry in self.machine.devices() {
out.push_str(entry.path());
out.push_str(" ");
out.push_str(entry.class().name);
out.push('\n');
}
Some(out)
}
"time" => Some(format!("{} ns\n", self.machine.now().as_nanos())),
"hash" => Some(match self.machine.state_hash() {
Ok(hash) => format!("{hash:#018x}\n"),
Err(e) => format!("cannot hash state: {e}\n"),
}),
_ => None,
}
}
}