use std::io::Write as _;
use super::super::Core;
use super::Debugger;
use super::agent::{self, Failed, Inspect};
use super::memory::peek;
use super::packet::{from_hex, hex};
use super::registers::value;
use crate::emulator::{AccessKind, DataModel};
use crate::soc::Width;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(super) struct Agent {
conditions: Vec<Vec<u8>>,
commands: Vec<Vec<u8>>,
}
impl Agent {
fn parse(rest: &str) -> Option<Self> {
let (conditions, commands) = match rest.split_once("cmds:") {
Some((conditions, commands)) => {
let (_persist, commands) = commands.split_once(',')?;
(conditions.strip_suffix(';').unwrap_or(conditions), commands)
}
None => (rest, ""),
};
Some(Self {
conditions: expressions(conditions)?,
commands: expressions(commands)?,
})
}
}
fn expressions(mut text: &str) -> Option<Vec<Vec<u8>>> {
let mut each = Vec::new();
while !text.is_empty() {
let (length, rest) = text.strip_prefix('X')?.split_once(',')?;
let digits = usize::try_from(hex(length)?).ok()?.checked_mul(2)?;
let code = rest.get(..digits)?;
each.push(from_hex(code)?);
text = rest.get(digits..)?;
}
Some(each)
}
struct Reading<'a>(&'a mut Core);
impl Inspect for Reading<'_> {
fn register(&mut self, number: u16) -> Option<u64> {
let endian = self.0.machine.memory.endian();
value(self.0.machine.cpu(), usize::from(number), endian)
}
fn read(&mut self, address: u64, width: Width) -> Option<u64> {
peek(self.0, address, width)
}
fn data_model(&mut self) -> DataModel {
self.0.machine.cpu().data_model()
}
}
const BREAKPOINTS: usize = 1 << 12;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum Breakpoint {
Software,
Hardware,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in super::super) enum Watchpoint {
Write,
Read,
Access,
}
impl Watchpoint {
fn of(kind: &str) -> Option<Self> {
match kind {
"2" => Some(Self::Write),
"3" => Some(Self::Read),
"4" => Some(Self::Access),
_ => None,
}
}
pub(in super::super) fn watches(self, kind: AccessKind) -> bool {
match self {
Self::Write => kind == AccessKind::Write,
Self::Read => kind == AccessKind::Read,
Self::Access => true,
}
}
pub(super) fn reason(self) -> &'static str {
match self {
Self::Write => "watch",
Self::Read => "rwatch",
Self::Access => "awatch",
}
}
}
impl Debugger<'_> {
pub(super) fn breakpoint_at(&self, address: u64) -> Option<Breakpoint> {
self.breakpoints
.range((address, Breakpoint::Software)..=(address, Breakpoint::Hardware))
.next()
.map(|(&(_, kind), _)| kind)
}
pub(super) fn stops_at(&mut self, core: &mut Core, address: u64) -> bool {
let points: Vec<Agent> = self
.breakpoints
.range((address, Breakpoint::Software)..=(address, Breakpoint::Hardware))
.map(|(_, agent)| agent.clone())
.collect();
let mut printed = Vec::new();
let mut stops = false;
for point in points {
let mut reading = Reading(core);
let holds = if point.conditions.is_empty() {
Ok(true)
} else {
point.conditions.iter().try_fold(false, |holds, condition| {
Ok::<_, Failed>(
holds || agent::evaluate(condition, &mut reading, &mut printed)? != 0,
)
})
};
let ran = match holds {
Ok(true) if point.commands.is_empty() => {
stops = true;
Ok(())
}
Ok(true) => point.commands.iter().try_for_each(|command| {
agent::evaluate(command, &mut reading, &mut printed).map(drop)
}),
Ok(false) => Ok(()),
Err(failed) => Err(failed),
};
if let Err(failed) = ran {
stops = true;
writeln!(
printed,
"the breakpoint at {address:#x} stopped the core: {failed}"
)
.expect("a Vec takes any bytes");
}
}
if !printed.is_empty() && self.history.first_print(core.machine.position()) {
if self.non_stop() {
let (at, count) = (core.machine.now(), core.machine.retired());
core.driven.debugger_print(at, count, &printed);
} else {
self.console(core, &printed);
}
}
stops
}
pub(super) fn point(&mut self, core: &mut Core, set: bool, rest: &str) -> &'static str {
let (rest, agent) = rest.split_once(';').unwrap_or((rest, ""));
let Some(agent) = Agent::parse(agent) else {
return "E01";
};
let mut fields = rest.split(',');
let (Some(kind), Some(address), Some(length)) = (
fields.next(),
fields.next().and_then(hex),
fields.next().and_then(hex),
) else {
return "E01";
};
match kind {
"0" | "1" => {
let each = if kind == "0" {
(address, Breakpoint::Software)
} else {
(address, Breakpoint::Hardware)
};
if set {
if !self.breakpoints.contains_key(&each)
&& (self.breakpoints.len() >= BREAKPOINTS
|| !core.machine.cpu().add_breakpoint(address))
{
return "E01";
}
self.breakpoints.insert(each, agent);
} else if self.breakpoints.remove(&each).is_some() {
core.machine.cpu().remove_breakpoint(address);
}
"OK"
}
_ => {
let Some(watchpoint) = Watchpoint::of(kind) else {
return "";
};
let Some(last) = length
.checked_sub(1)
.and_then(|extent| address.checked_add(extent))
else {
return "E01";
};
if core
.machine
.debugger_watch((address, last), watchpoint, set)
{
"OK"
} else {
"E01"
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn conditions_and_commands_parse_one_after_another() {
let never = vec![0x22, 0x00, 0x27];
let always = vec![0x22, 0x01, 0x27];
let both = Agent::parse("X3,220027X3,220127").unwrap();
assert_eq!(both.conditions, [never.clone(), always.clone()]);
assert!(both.commands.is_empty());
let printing = Agent::parse("X3,220027;cmds:1,X3,220127X3,220027").unwrap();
assert_eq!(printing.conditions, std::slice::from_ref(&never));
assert_eq!(printing.commands, [always, never]);
assert_eq!(Agent::parse(""), Some(Agent::default()));
for malformed in [
"X4,220027",
"X3,22zz27",
"Y3,220027",
"X3,220027;cmds:X3,220027",
] {
assert_eq!(Agent::parse(malformed), None, "{malformed}");
}
}
}