use std::fmt::Write as _;
use super::super::Core;
use super::history::{Halted, Input};
use super::memory::Reads;
use super::packet::{from_hex, to_hex};
use super::threads::Reason;
use super::{Asked, Debugger, HaltAt, Held};
use crate::emulator::HaltReason;
use crate::time::parse_virtual_time;
struct Command {
words: &'static str,
help: &'static str,
needs_core: bool,
takes_argument: bool,
run: fn(&mut Debugger<'_>, &mut Core, &str) -> String,
}
const COMMANDS: &[Command] = &[
Command {
words: "reset",
help: "resets the machine as the firmware's own request does, and holds the core at its first \
instruction. GDB keeps the registers it read, so ask again with `maintenance flush \
register-cache`, or restart with `run` under `target extended-remote`.",
needs_core: true,
takes_argument: false,
run: reset,
},
Command {
words: "reset halt",
help: "`monitor reset`, which holds the core already.",
needs_core: true,
takes_argument: false,
run: reset,
},
Command {
words: "reset init",
help: "`monitor reset`.",
needs_core: true,
takes_argument: false,
run: reset,
},
Command {
words: "system_reset",
help: "`monitor reset`.",
needs_core: true,
takes_argument: false,
run: reset,
},
Command {
words: "halt",
help: "says whether the core is held, which it is whenever a debugger has it.",
needs_core: false,
takes_argument: false,
run: halt,
},
Command {
words: "help",
help: "this.",
needs_core: false,
takes_argument: false,
run: help,
},
Command {
words: "icount",
help: "prints how many instructions the core has retired, which `$icount` reads too, and \
the virtual time it's at.",
needs_core: true,
takes_argument: false,
run: icount,
},
Command {
words: "until-icount",
help: "`until-icount N` stops the core once it has retired N instructions, when you next \
continue, as a breakpoint stops it. An N the run has passed is a place \
`reverse-continue` stops at.",
needs_core: true,
takes_argument: true,
run: until_icount,
},
Command {
words: "until-time",
help: "`until-time 5ms` stops the core at that instant of virtual time, waiting or not, when \
you next continue. It takes ns, us, ms and s.",
needs_core: true,
takes_argument: true,
run: until_time,
},
Command {
words: "peek on",
help: "makes your reads of memory peeks, as they are when the run starts: a read of a \
peripheral answers what the firmware's would, and changes nothing in the run.",
needs_core: false,
takes_argument: false,
run: peek_on,
},
Command {
words: "peek off",
help: "makes your reads of memory the firmware's for the rest of the run, side effects \
and all, and the run prints each with `pc=debugger`.",
needs_core: false,
takes_argument: false,
run: peek_off,
},
Command {
words: "save",
help: "`save NAME` keeps the run where it stands under NAME for this session, in memory, to \
go back or forward to with `monitor restore NAME`.",
needs_core: true,
takes_argument: true,
run: save,
},
Command {
words: "restore",
help: "`restore NAME` takes the run to where `monitor save NAME` kept it, back or forward, \
and holds the core there. GDB keeps what it read, so ask again with `maintenance \
flush register-cache` and `maintenance flush dcache`. Going on from there is the \
same run: the driver hears nothing twice, and what you wrote or stepped comes in \
again where it came, until you do something it didn't.",
needs_core: true,
takes_argument: true,
run: restore,
},
Command {
words: "snapshots",
help: "lists what `monitor save` kept, each with its icount and virtual time.",
needs_core: false,
takes_argument: false,
run: snapshots,
},
];
impl Debugger<'_> {
pub(super) fn monitor(&mut self, core: &mut Core, command: &str) -> Asked {
let Some(command) = from_hex(command).and_then(|bytes| String::from_utf8(bytes).ok())
else {
return Asked::Reply("E01".to_owned());
};
let text = command.trim();
let said = match command_in(text) {
Some((command, argument)) if !command.takes_argument && !argument.is_empty() => {
format!("no monitor command \"{text}\", try \"monitor help\"\n")
}
Some((command, _)) if command.needs_core && !self.attached() => {
"no debugger has the core, so attach first, with `attach 1`\n".to_owned()
}
Some((command, argument)) => (command.run)(self, core, argument),
None if !self.attached() => {
"no debugger has the core, so attach first, with `attach 1`\n".to_owned()
}
None => match core.machine.cpu().monitor_command(text) {
Some(said) => {
self.note(core, Input::Monitor(text.to_owned()), |_| ());
said
}
None => format!("no monitor command \"{text}\", try \"monitor help\"\n"),
},
};
Asked::Reply(to_hex(said.as_bytes()))
}
}
fn command_in(text: &str) -> Option<(&'static Command, &str)> {
COMMANDS
.iter()
.filter_map(|command| {
let rest = text.strip_prefix(command.words)?;
(rest.is_empty() || rest.starts_with(char::is_whitespace))
.then(|| (command, rest.trim_start()))
})
.max_by_key(|(command, _)| command.words.len())
}
fn reset(debugger: &mut Debugger<'_>, core: &mut Core, _argument: &str) -> String {
debugger.note(core, Input::Reset, super::history::reset);
debugger.disarm(core);
debugger.threads.forget();
debugger.stopped = debugger.threads.stopped(core, Reason::Trap);
"reset, and held at the first instruction\n".to_owned()
}
fn save(debugger: &mut Debugger<'_>, core: &mut Core, argument: &str) -> String {
debugger.save(core, argument)
}
fn restore(debugger: &mut Debugger<'_>, core: &mut Core, argument: &str) -> String {
debugger.restore(core, argument)
}
fn snapshots(debugger: &mut Debugger<'_>, _core: &mut Core, _argument: &str) -> String {
debugger.snapshots()
}
fn halt(debugger: &mut Debugger<'_>, core: &mut Core, _argument: &str) -> String {
if debugger.core != Held::Halted {
return "running\n".to_owned();
}
if debugger.attached() {
let reason = debugger.stopped.reason;
debugger.hold(core, reason, Halted::By(HaltReason::Requested));
}
"held\n".to_owned()
}
fn peek_on(debugger: &mut Debugger<'_>, _core: &mut Core, _argument: &str) -> String {
debugger.reads = Reads::Peeks;
"your reads are peeks\n".to_owned()
}
fn peek_off(debugger: &mut Debugger<'_>, _core: &mut Core, _argument: &str) -> String {
debugger.reads = Reads::Firmware;
"your reads are the firmware's\n".to_owned()
}
fn help(_debugger: &mut Debugger<'_>, core: &mut Core, _argument: &str) -> String {
let mut said = COMMANDS.iter().fold(String::new(), |mut said, command| {
writeln!(said, "monitor {}: {}", command.words, command.help).expect("a String takes it");
said
});
said.push_str(
&core
.machine
.cpu()
.monitor_command("help")
.unwrap_or_default(),
);
said.push_str(WATCHES);
said
}
fn icount(_debugger: &mut Debugger<'_>, core: &mut Core, _argument: &str) -> String {
let retired = core.machine.retired();
format!("icount {retired} at {} ns\n", core.machine.time(retired).0)
}
fn until_icount(debugger: &mut Debugger<'_>, core: &mut Core, argument: &str) -> String {
let Ok(count) = argument.parse::<u64>() else {
return "until-icount takes an instruction count, like until-icount 123456\n".to_owned();
};
let retired = core.machine.retired();
if count < retired && Debugger::reverses(core) {
debugger.history.behind = Some(count);
return format!("the core stops at icount {count} when you reverse-continue\n");
}
if count <= retired {
return format!("the run is at icount {retired} already\n");
}
debugger.arm(core, HaltAt::Count(count));
format!("the core stops at icount {count} when you continue\n")
}
fn until_time(debugger: &mut Debugger<'_>, core: &mut Core, argument: &str) -> String {
let instant = match parse_virtual_time(argument) {
Ok(instant) => instant,
Err(error) => {
return format!("until-time takes an instant, like until-time 5ms: {error}\n");
}
};
let now = core.machine.now();
if instant <= now {
return format!("the run is at {} ns already\n", now.0);
}
debugger.arm(core, HaltAt::Instant(instant));
format!("the core stops at {} ns when you continue\n", instant.0)
}
const WATCHES: &str = "A watchpoint has every access to each page of memory it touches checked, so \
one over a lot of memory slows the run while it's in.\n";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_command_is_the_longest_words_its_text_starts_with() {
let words = |text| command_in(text).map(|(command, argument)| (command.words, argument));
assert_eq!(words("reset"), Some(("reset", "")));
assert_eq!(words("reset halt"), Some(("reset halt", "")));
assert_eq!(words("reset run now"), Some(("reset", "run now")));
assert_eq!(words("reset halted"), Some(("reset", "halted")));
assert_eq!(words("resets"), None);
assert_eq!(words("info registers"), None);
}
}