use std::collections::BTreeMap;
use std::fmt::Write as _;
use std::sync::Arc;
use super::super::{Checkpoint, Core, Position};
use super::hosted::Fresult;
use super::registers::set_registers;
use super::threads::Reason;
use super::{Debugger, Held};
use crate::emulator::{Exit, HaltReason};
use crate::snapshot;
const CHECKPOINT_EVERY: u64 = 1 << 22;
const CHECKPOINTS: usize = 64;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum Input {
Write { address: u64, bytes: Vec<u8> },
Flash { address: u64, bytes: Vec<u8> },
Read { address: u64, length: u64 },
Register { number: usize, bytes: Vec<u8> },
Registers(String),
Monitor(String),
Stepped,
Reset,
HostCall(Option<Fresult>),
Stopped { reason: Reason, halted: Halted },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Halted {
By(HaltReason),
Itself,
AtWatchpoint,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct Place {
position: Position,
pc: u64,
}
impl Place {
fn of(core: &Core) -> Self {
Self {
position: core.machine.position(),
pc: core.machine.pc(),
}
}
}
#[derive(Debug)]
pub(super) struct Mark {
pub(super) checkpoint: Checkpoint,
pub(super) inputs: usize,
}
#[derive(Debug, Default)]
pub(super) struct History {
inputs: Vec<(Place, Input)>,
settling: Option<Place>,
next: usize,
saved: BTreeMap<String, Arc<Mark>>,
pub(super) replaying: bool,
checkpoints: Vec<Arc<Mark>>,
next_checkpoint: u64,
going: Option<Going>,
furthest: Option<Position>,
stopped_at: Option<Place>,
unnoted: Option<(Place, Input)>,
printed: Option<Position>,
pub(super) behind: Option<u64>,
}
#[derive(Clone, Copy, Debug)]
enum Going {
Back { count: u64, settled: Option<Place> },
Looking {
mark: usize,
end: Position,
stops: u32,
},
Last { left: u32 },
After { reason: Reason, count: u64 },
}
fn keep_one<T>(kept: &mut Vec<T>, at: usize, one: T) {
kept.insert(at, one);
if kept.len() > CHECKPOINTS {
thin(kept);
}
}
fn thin<T>(kept: &mut Vec<T>) {
let older = kept.len() / 2;
let mut index = 0;
kept.retain(|_| {
let keep = index >= older || index % 2 == 0;
index += 1;
keep
});
}
impl History {
pub(super) fn stopped(&mut self) {
self.going = None;
self.replaying = false;
}
pub(super) fn first_print(&mut self, here: Position) -> bool {
if self.printed.is_some_and(|printed| here <= printed) {
return false;
}
self.printed = Some(here);
true
}
}
impl Debugger<'_> {
pub(super) fn rewinds(core: &Core) -> bool {
core.driven.may_rewind()
}
fn keeping(&self, core: &Core) -> bool {
self.attached() && Self::rewinds(core)
}
fn behind(&self, core: &Core) -> bool {
let here = core.machine.position();
self.history.next < self.history.inputs.len()
|| core.driven.retracing()
|| self
.history
.furthest
.is_some_and(|furthest| here < furthest)
}
pub(super) fn note<T>(
&mut self,
core: &mut Core,
input: Input,
make: impl FnOnce(&mut Core) -> T,
) -> T {
let history = &self.history;
let next = history.inputs.get(history.next);
if self.keeping(core) && next == Some(&(Place::of(core), input.clone())) {
self.history.next += 1;
return make(core);
}
let here = self.new_input(core);
let made = make(core);
self.noted(here, input);
made
}
pub(super) fn new_input(&mut self, core: &mut Core) -> Option<Place> {
if !self.keeping(core) {
return None;
}
if self.behind(core) {
self.new_way(core);
}
Some(Place::of(core))
}
pub(super) fn noted(&mut self, here: Option<Place>, input: Input) {
if let Some(here) = here {
self.history.inputs.push((here, input));
self.history.next = self.history.inputs.len();
}
}
pub(super) fn note_stop(
&mut self,
core: &mut Core,
reason: Reason,
halted: Halted,
changed: bool,
) {
let here = Place::of(core);
self.history.stopped_at = Some(here);
let stop = Input::Stopped { reason, halted };
if !self.keeping(core) {
return;
}
let history = &mut self.history;
if history.inputs.get(history.next) == Some(&(here, stop.clone())) {
history.next += 1;
return;
}
if !changed && self.behind(core) {
self.history.unnoted = Some((here, stop));
return;
}
let here = self.new_input(core);
self.noted(here, stop);
}
fn stops_here(&self, core: &Core, reason: Reason, halted: Halted) -> bool {
let (pc, count) = (core.machine.pc(), core.machine.retired());
let watching = &core.machine.watching;
match reason {
_ if halted == Halted::Itself => true,
Reason::Breakpoint(_) => {
self.breakpoint_at(pc).is_some() && self.resumed != Some((pc, count))
}
Reason::Watch(watchpoint, address) => {
watching.passing != Some((pc, count + 1))
&& watching.debugged.iter().any(|&((first, last), kind)| {
kind == watchpoint && (first..=last).contains(&address)
})
}
_ => false,
}
}
fn new_way(&mut self, core: &mut Core) {
core.new_way();
let here = core.machine.position();
let history = &mut self.history;
history.inputs.truncate(history.next);
let next = history.next;
let kept = |mark: &Arc<Mark>| mark.inputs <= next && mark.checkpoint.position <= here;
history.saved.retain(|_, mark| kept(mark));
history.checkpoints.retain(kept);
history.next_checkpoint = here.count;
history.furthest = None;
history.printed = history.printed.map(|printed| printed.min(here));
let place = Place::of(core);
if let Some((at, stop)) = history.unnoted.take()
&& at == place
{
history.inputs.push((at, stop));
history.next = history.inputs.len();
}
}
pub(super) fn put_back_here(&mut self, core: &mut Core) -> bool {
loop {
let here = Place::of(core);
let Some((at, input)) = self.history.inputs.get(self.history.next).cloned() else {
return false;
};
if at.position < here.position {
self.new_way(core);
return false;
}
if at != here {
return false;
}
match input {
Input::Stepped => return true,
Input::HostCall(_) => return false,
Input::Stopped { reason, halted }
if self.history.stopped_at != Some(here)
&& self.stops_here(core, reason, halted) =>
{
return false;
}
input => put_back(core, &input),
}
self.history.next += 1;
}
}
fn give_up_short_of(&mut self, core: &mut Core) {
let here = Place::of(core);
let history = &mut self.history;
let short = matches!(
history.inputs.get(history.next),
Some((at, _)) if at.position == here.position && at.pc != here.pc
);
if !short {
history.settling = None;
} else if history.settling == Some(here) {
history.settling = None;
self.new_way(core);
} else {
history.settling = Some(here);
}
}
pub(super) fn replay(&mut self, core: &mut Core) {
self.give_up_short_of(core);
if self.put_back_here(core) {
self.history.next += 1;
self.history.replaying = true;
self.core = Held::Stepping;
}
self.aim(core);
}
#[expect(
clippy::option_option,
reason = "whether the history has the call apart from what GDB answered it, which may be nothing"
)]
pub(super) fn answered_here(&mut self, core: &mut Core) -> Option<Option<Fresult>> {
self.put_back_here(core);
let here = Place::of(core);
match self.history.inputs.get(self.history.next) {
Some(&(at, Input::HostCall(answer))) if at == here => {
self.history.next += 1;
Some(answer)
}
_ => None,
}
}
pub(super) fn history_step(&mut self) {
self.history.next += 1;
}
pub(super) fn next_input(&self, core: &Core) -> Option<Position> {
let here = Place::of(core);
let (at, _) = self.history.inputs.get(self.history.next)?;
(at.position > here.position || (at.position == here.position && at.pc != here.pc))
.then_some(at.position)
}
pub(super) fn save(&mut self, core: &mut Core, name: &str) -> String {
if let Err(why) = Self::can_go_back(core) {
return why;
}
if name.is_empty() {
return "save takes a name, like save before-the-fault\n".to_owned();
}
match core.checkpoint() {
Ok(checkpoint) => {
let position = checkpoint.position;
let mark = Mark {
checkpoint,
inputs: self.history.next,
};
self.history.saved.insert(name.to_owned(), Arc::new(mark));
format!(
"saved {name} at icount {} at {} ns\n",
position.count, position.at.0
)
}
Err(why) => format!("can't save here: {why}\n"),
}
}
pub(super) fn restore(&mut self, core: &mut Core, name: &str) -> String {
if let Err(why) = Self::can_go_back(core) {
return why;
}
let Some(mark) = self.history.saved.get(name).cloned() else {
return format!("no snapshot {name}, see monitor snapshots\n");
};
match self.go_to(core, &mark) {
Ok(()) => format!(
"restored {name} at icount {} at {} ns\n",
mark.checkpoint.position.count, mark.checkpoint.position.at.0
),
Err(why) => format!("can't restore {name}: {why}\n"),
}
}
pub(super) fn snapshots(&self) -> String {
if self.history.saved.is_empty() {
return "no snapshots, monitor save NAME takes one\n".to_owned();
}
self.history
.saved
.iter()
.fold(String::new(), |mut said, (name, mark)| {
let at = mark.checkpoint.position;
writeln!(said, "{name} at icount {} at {} ns", at.count, at.at.0)
.expect("a String takes it");
said
})
}
fn can_go_back(core: &Core) -> Result<(), String> {
if Self::rewinds(core) {
Ok(())
} else {
Err(
"this run can't go back: its driver doesn't say it may, and one under --link \
never does, since an engine's inputs aren't kept\n"
.to_owned(),
)
}
}
pub(super) fn go_to(&mut self, core: &mut Core, mark: &Mark) -> Result<(), String> {
let left = core.machine.position();
let furthest = self.history.furthest.get_or_insert(left);
*furthest = left.max(*furthest);
core.restore(&mark.checkpoint)?;
self.history.next = mark.inputs;
self.history.stopped_at = None;
self.history.unnoted = None;
self.halt_at = None;
self.resumed = None;
self.next_look = 0;
self.next_warp = 0;
self.history.replaying = false;
self.core = Held::Halted;
self.aim(core);
self.threads.forget();
self.stopped = self.threads.stopped(core, Reason::Trap);
Ok(())
}
pub(super) fn forget_history(&mut self, core: &mut Core) {
if self.behind(core) {
core.new_way();
}
self.history = History::default();
}
pub(super) fn keep_up(&mut self, core: &mut Core) {
let here = core.machine.position();
if here.count < self.history.next_checkpoint || !self.keeping(core) {
return;
}
let at = self
.history
.checkpoints
.partition_point(|mark| mark.checkpoint.position <= here);
let near = at
.checked_sub(1)
.map(|newest| self.history.checkpoints[newest].checkpoint.position.count)
.map(|count| count.saturating_add(CHECKPOINT_EVERY))
.filter(|&next| here.count < next);
if let Some(next) = near {
self.history.next_checkpoint = next;
return;
}
let checkpoint = match core.checkpoint() {
Ok(checkpoint) => checkpoint,
Err(snapshot::Error::Resetting | snapshot::Error::Resuming) => return,
Err(_) => {
self.history.next_checkpoint = here.count.saturating_add(CHECKPOINT_EVERY);
return;
}
};
self.history.next_checkpoint = here.count.saturating_add(CHECKPOINT_EVERY);
let checkpoints = &mut self.history.checkpoints;
let mark = Mark {
checkpoint,
inputs: self.history.next,
};
keep_one(checkpoints, at, Arc::new(mark));
}
pub(super) fn reverses(core: &Core) -> bool {
Self::rewinds(core)
}
pub(super) fn step_back(&mut self, core: &mut Core) -> Result<bool, String> {
let here = core.machine.position();
let target = here.count.checked_sub(1);
let from = target.and_then(|target| {
self.history
.checkpoints
.iter()
.rposition(|mark| mark.checkpoint.position.count <= target)
});
let (Some(target), Some(from)) = (target, from) else {
self.back_to_the_beginning(core)?;
return Ok(false);
};
let mark = Arc::clone(&self.history.checkpoints[from]);
self.go_to(core, &mark)?;
self.history.going = Some(Going::Back {
count: target,
settled: None,
});
self.travel(core);
Ok(true)
}
pub(super) fn continue_back(&mut self, core: &mut Core) -> Result<bool, String> {
let here = core.machine.position();
let Some(mark) = self
.history
.checkpoints
.iter()
.rposition(|mark| mark.checkpoint.position < here)
else {
self.back_to_the_beginning(core)?;
return Ok(false);
};
self.look_over(core, mark, here)?;
Ok(true)
}
fn look_over(&mut self, core: &mut Core, mark: usize, end: Position) -> Result<(), String> {
let from = Arc::clone(&self.history.checkpoints[mark]);
self.go_to(core, &from)?;
self.history.going = Some(Going::Looking {
mark,
end,
stops: 0,
});
self.travel(core);
Ok(())
}
fn back_to_the_beginning(&mut self, core: &mut Core) -> Result<(), String> {
let oldest = self
.history
.checkpoints
.first()
.cloned()
.ok_or("the history keeps no checkpoint")?;
if oldest.checkpoint.position != core.machine.position() {
self.go_to(core, &oldest)?;
}
self.stop_back(core, Reason::Begin, Halted::By(HaltReason::Requested));
Ok(())
}
fn travel(&mut self, core: &mut Core) {
self.core = Held::Running;
self.replay(core);
}
fn stop_back(&mut self, core: &mut Core, reason: Reason, halted: Halted) {
if self.halt(core, reason, halted).is_err() {
self.disconnect(core);
}
}
pub(super) fn going_until(&self, core: &Core) -> Option<Position> {
let going = match self.history.going? {
Going::Back { count, .. } | Going::After { count, .. } => Some(Position {
count,
at: crate::time::VirtualNanos(u64::MAX),
}),
Going::Looking { end, .. } => Some(end),
Going::Last { .. } => None,
};
let behind = self
.history
.behind
.filter(|&count| count > core.machine.retired());
going
.into_iter()
.chain(behind.map(|count| Position {
count,
at: crate::time::VirtualNanos(u64::MAX),
}))
.min()
}
pub(super) fn going_back(&mut self, core: &mut Core, exit: Exit, ran: u64) -> bool {
let Some(going) = self.history.going else {
return false;
};
if self.core == Held::Stepping && (ran > 0 || exit != Exit::Breakpoint) {
self.history.replaying = false;
self.core = Held::Running;
}
let at = (core.machine.cpu().pc(), core.machine.retired());
let hit = (exit == Exit::Breakpoint && self.resumed != Some(at))
.then(|| self.breakpoint_at(at.0))
.flatten();
let stop = if let Some((address, watchpoint)) = core.machine.watching.debugger_hit.take() {
let halted = if exit == Exit::Halted {
core.machine.watching.passing = Some((at.0, at.1 + 1));
Halted::AtWatchpoint
} else {
Halted::By(HaltReason::Watchpoint)
};
Some((Reason::Watch(watchpoint, address), halted))
} else if let Some(kind) = hit
&& self.stops_at(core, at.0)
{
Some((Reason::Breakpoint(kind), Halted::By(HaltReason::Breakpoint)))
} else if exit == Exit::Halted {
Some((Reason::Trap, Halted::Itself))
} else if self.history.behind == Some(at.1) && ran > 0 {
Some((Reason::Trap, Halted::By(HaltReason::Requested)))
} else {
None
};
if hit.is_some() {
self.pass_own_breakpoint(core);
}
if stop.is_some() {
self.history.stopped_at = Some(Place::of(core));
}
let here = core.machine.position();
match going {
Going::Back { count, .. } if here.count < count => self.replay(core),
Going::Back { settled, .. } if settled == Some(Place::of(core)) => {
self.stop_back(core, Reason::Trap, Halted::By(HaltReason::Requested));
}
Going::Back { count, .. } => {
self.history.going = Some(Going::Back {
count,
settled: Some(Place::of(core)),
});
self.aim(core);
}
Going::Looking { mark, end, stops } if here >= end => self.looked(core, mark, stops),
Going::Looking { mark, end, stops } => {
self.history.going = Some(Going::Looking {
mark,
end,
stops: stops + u32::from(stop.is_some()),
});
self.replay(core);
}
Going::After { reason, count } if here.count >= count => {
self.stop_back(core, reason, Halted::By(HaltReason::Watchpoint));
}
Going::After { .. } => self.replay(core),
Going::Last { left } => match stop {
Some((reason @ Reason::Watch(..), _)) if left <= 1 && exit == Exit::Halted => {
self.history.going = Some(Going::After {
reason,
count: at.1 + 1,
});
self.aim(core);
}
Some((reason, halted)) if left <= 1 => {
if self.history.behind == Some(at.1) {
self.history.behind = None;
}
self.stop_back(core, reason, halted);
}
Some(_) => {
self.history.going = Some(Going::Last { left: left - 1 });
self.replay(core);
}
None => self.replay(core),
},
}
true
}
fn looked(&mut self, core: &mut Core, mark: usize, stops: u32) {
let went = if stops > 0 {
let from = Arc::clone(&self.history.checkpoints[mark]);
self.go_to(core, &from).map(|()| {
self.history.going = Some(Going::Last { left: stops });
self.travel(core);
})
} else if mark > 0 {
let end = self.history.checkpoints[mark].checkpoint.position;
self.look_over(core, mark - 1, end)
} else {
self.back_to_the_beginning(core)
};
if went.is_err() {
self.stop_back(core, Reason::Trap, Halted::By(HaltReason::Requested));
}
}
}
fn put_back(core: &mut Core, input: &Input) {
match input {
Input::Write { address, bytes } => {
core.debugger_write(*address, bytes);
}
Input::Flash { address, bytes } => core.debugger_flash(*address, bytes),
Input::Read { address, length } => {
core.debugger_read(*address, *length, super::memory::Reads::Firmware);
}
Input::Register { number, bytes } => {
core.machine.cpu().set_gdb_register(*number, bytes);
}
Input::Registers(hex) => {
set_registers(core.machine.cpu(), hex);
}
Input::Monitor(command) => {
core.machine.cpu().monitor_command(command);
}
Input::Reset => reset(core),
Input::Stopped { halted, .. } => {
let halt = match *halted {
Halted::By(halt) => halt,
Halted::AtWatchpoint => HaltReason::Watchpoint,
Halted::Itself => return,
};
core.machine.cpu().debugger_halted(halt);
}
Input::Stepped | Input::HostCall(_) => {}
}
}
pub(super) fn reset(core: &mut Core) {
core.debugger_reset();
core.machine.cpu().debugger_halted(HaltReason::Requested);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thinning_keeps_recent_checkpoints_close_and_old_ones_far() {
let mut kept: Vec<u64> = (0..=64).collect();
thin(&mut kept);
assert_eq!(kept.len(), 49);
assert_eq!(&kept[..4], [0, 2, 4, 6]);
assert_eq!(&kept[16..], (32..=64).collect::<Vec<_>>());
let mut kept: Vec<u64> = Vec::new();
for taken in 0..10_000 {
let newest = kept.len();
keep_one(&mut kept, newest, taken);
}
assert!(kept.len() <= CHECKPOINTS);
assert_eq!(kept.first(), Some(&0), "the oldest stays");
let newest: Vec<u64> = kept
.windows(2)
.rev()
.take(8)
.map(|pair| pair[1] - pair[0])
.collect();
assert!(newest.iter().all(|&gap| gap == 1), "{newest:?}");
let oldest = kept[1] - kept[0];
assert!(oldest > 64, "the oldest gap is {oldest}");
}
}