mod gdb;
mod run;
mod stream;
use std::any::Any;
use std::collections::{BTreeMap, BTreeSet};
use std::net::TcpListener;
use std::num::NonZeroU64;
use std::sync::atomic::{AtomicBool, Ordering};
use super::memory::{Mapped, Memory, PAGE_ALIGNMENT, to_target};
use super::{
Access, AccessKind, CoreBus, Driver, Error, Exit, Finished, Hit, HitKind, Host, HostCall,
InterruptLine, Interrupted, Memory as GuestMemory, NO_DEADLINE, OpenMode, Processor, Queued,
Resume, Start, Stop, StopReason, Watch,
};
use crate::device::Level;
use crate::image::{self, Segment};
use crate::machine::Board;
use crate::mcu::Operate;
use crate::rtt::{self, Rtt};
use crate::session::Session;
use crate::snapshot;
use crate::soc::{Endian, Width};
use crate::soc::{Region, RegionKind};
use crate::symbols::Symbols;
use crate::time::VirtualNanos;
#[cfg(fuzzing)]
pub(crate) use gdb::Fuzzed;
use gdb::{Debugger, Fresult, Map, Watchpoint};
use run::{Driven, Kept, Run};
use stream::Stream;
const LONGEST_STRETCH: u64 = 1 << 30;
const CORE_STACK: usize = 64 << 20;
const RUN_ROUTED_BYTES: u64 = (1 << 20) - (64 << 10);
const DEBUGGER_ROUTED_BYTES: u64 = 1 << 26;
const DEBUGGER_WATCHPOINTS: usize = 1 << 12;
struct Core {
driven: Box<dyn Driven>,
machine: Machine,
semihosting: bool,
trace: Option<Stream>,
terminal: Option<Stream>,
}
const HOST_CALL_FAILED: u64 = u64::MAX;
const TICKS_PER_SECOND: u64 = 1_000_000_000;
const CLOCK_TICK: u64 = 10_000_000;
const FILE_MODE: u64 = 0o644;
const SEEK_SET: u64 = 0;
const SEEK_CUR: u64 = 1;
const SEEK_END: u64 = 2;
fn open_flags(mode: OpenMode) -> u64 {
const RDONLY: u64 = 0x0;
const WRONLY: u64 = 0x1;
const RDWR: u64 = 0x2;
const APPEND: u64 = 0x8;
const CREAT: u64 = 0x200;
const TRUNC: u64 = 0x400;
match mode {
OpenMode::Read => RDONLY,
OpenMode::ReadWrite => RDWR,
OpenMode::Write => WRONLY | CREAT | TRUNC,
OpenMode::WriteRead => RDWR | CREAT | TRUNC,
OpenMode::Append => WRONLY | CREAT | APPEND,
OpenMode::AppendRead => RDWR | CREAT | APPEND,
}
}
enum Answered {
Served,
Unserved,
Exited(u32),
}
#[derive(Debug)]
struct Machine {
cpu: Option<Box<dyn Processor>>,
memory: Mapped,
queued: Vec<Queued>,
rate: NonZeroU64,
page: u64,
breakpoints: BTreeMap<u64, bool>,
warped: u64,
deadline: VirtualNanos,
horizon: Option<VirtualNanos>,
drain: Option<VirtualNanos>,
segments: Vec<Segment>,
boot: u64,
restart: bool,
read_only_written: bool,
watching: Watching,
requested: bool,
errno: u64,
rtt: Option<Rtt>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(super) struct Position {
pub(super) count: u64,
pub(super) at: VirtualNanos,
}
#[derive(Debug)]
pub(super) struct Checkpoint {
pub(super) position: Position,
machine: Vec<u8>,
drain: Option<VirtualNanos>,
memory: Vec<(u64, Vec<u8>)>,
run: Kept,
sent: (u64, u64),
}
#[derive(Debug, Default)]
struct Watching {
instructions: BTreeSet<u64>,
writes: Vec<(u64, u64)>,
arrived: Option<(u64, u64)>,
written: Option<u64>,
debugged: Vec<((u64, u64), Watchpoint)>,
debugger_hit: Option<(u64, Watchpoint)>,
passing: Option<(u64, u64)>,
}
fn touches((first, last): (u64, u64), address: u64, width: Width) -> bool {
address <= last && first <= address.saturating_add(u64::from(width.bytes()) - 1)
}
fn pages_of<'a>(ranges: impl IntoIterator<Item = &'a (u64, u64)>, size: u64) -> BTreeSet<u64> {
ranges
.into_iter()
.flat_map(|&(first, last)| (first / size..=last / size).map(move |page| page * size))
.collect()
}
fn fit(ranges: &[(u64, u64)], range: (u64, u64), limit: u64, size: u64) -> bool {
let pages = limit / size;
range.1 / size - range.0 / size < pages
&& pages_of(ranges.iter().chain([&range]), size).len() as u64 <= pages
}
fn nanos(rate: NonZeroU64, instructions: u64) -> u64 {
let nanos = u128::from(instructions) * NANOS_PER_SECOND / u128::from(rate.get());
u64::try_from(nanos).unwrap_or(u64::MAX)
}
fn instructions_until(rate: NonZeroU64, warped: u64, retired: u64, target: VirtualNanos) -> u32 {
let since = u128::from(target.0.saturating_sub(warped));
let needed = (since * u128::from(rate.get())).div_ceil(NANOS_PER_SECOND);
let more = needed
.saturating_sub(u128::from(retired))
.clamp(1, u128::from(LONGEST_STRETCH));
u32::try_from(more).expect("clamped to a stretch")
}
const NANOS_PER_SECOND: u128 = 1_000_000_000;
impl Watching {
fn pages(&self, size: u64) -> BTreeSet<u64> {
pages_of(
self.writes
.iter()
.chain(self.debugged.iter().map(|(range, _)| range)),
size,
)
}
fn accessed(&mut self, kind: AccessKind, address: u64, width: Width) {
if kind == AccessKind::Write && self.written.is_none() {
self.written = self
.writes
.iter()
.find(|&&range| touches(range, address, width))
.map(|&(first, _)| address.max(first));
}
}
fn hit(&self, access: &Access) -> Option<(u64, Watchpoint)> {
let at = access
.program_counter
.map(|pc| (pc, access.instruction_count));
if self.debugged.is_empty() || at.is_some_and(|at| self.passing == Some(at)) {
return None;
}
let Access {
kind,
address,
width,
..
} = *access;
self.debugged
.iter()
.find(|&&(range, watchpoint)| {
watchpoint.watches(kind) && touches(range, address, width)
})
.map(|&((first, _), watchpoint)| (address.max(first), watchpoint))
}
}
impl Machine {
fn new(
memory: Memory,
cpu: Box<dyn Processor>,
rate: NonZeroU64,
segments: Vec<Segment>,
boot: u64,
) -> Self {
Self {
memory: Mapped::new(memory),
page: cpu.page_size(),
breakpoints: BTreeMap::new(),
cpu: Some(cpu),
queued: Vec::new(),
rate,
warped: 0,
deadline: NO_DEADLINE,
horizon: None,
drain: None,
segments,
boot,
restart: false,
read_only_written: false,
watching: Watching::default(),
requested: false,
errno: 0,
rtt: None,
}
}
fn pc(&self) -> u64 {
self.cpu.as_deref().expect("between stretches").pc()
}
fn position(&self) -> Position {
Position {
count: self.retired(),
at: self.now(),
}
}
fn map_memory(&mut self) {
let cpu = self
.cpu
.as_deref_mut()
.expect("the processor between stretches");
#[expect(
unsafe_code,
reason = "the processor keeps pointers into the machine's memory"
)]
unsafe {
self.memory.map_into(cpu);
}
}
fn cpu(&mut self) -> &mut dyn Processor {
self.cpu
.as_deref_mut()
.expect("the processor between stretches")
}
fn ask(&mut self, queued: Queued) {
match self.cpu.as_mut() {
Some(cpu) => cpu.ask(queued),
None => self.queued.push(queued),
}
}
fn breakpoint(&mut self, address: u64, set: bool) -> bool {
match self.cpu.as_mut() {
Some(cpu) if set => cpu.add_breakpoint(address),
Some(cpu) => {
cpu.remove_breakpoint(address);
true
}
None => {
self.breakpoints.entry(address).or_insert(!set);
self.ask(Queued::Return);
true
}
}
}
fn rerouted(&mut self, change: impl FnOnce(&mut Watching)) {
let routed = self.watching.pages(self.page);
change(&mut self.watching);
let still = self.watching.pages(self.page);
for &page in still.difference(&routed) {
self.ask(Queued::Route(page));
}
for &page in routed.difference(&still) {
self.ask(Queued::Unroute(page));
}
}
fn time(&self, retired: u64) -> VirtualNanos {
VirtualNanos(self.warped.saturating_add(nanos(self.rate, retired)))
}
fn retired(&self) -> u64 {
self.cpu.as_ref().expect("between stretches").retired()
}
fn now(&self) -> VirtualNanos {
self.time(self.retired())
}
fn boot(&mut self) {
let Self {
memory, cpu, boot, ..
} = self;
cpu.as_mut()
.expect("between stretches")
.boot(*boot, &|address, width| memory.read(address, width));
}
fn reset(&mut self) {
self.cpu().reset();
for segment in &self.segments {
if self.memory.writable(segment.address) {
self.memory.place(segment.address, &segment.bytes);
}
}
self.boot();
self.watching.arrived = None;
}
fn invalidate(&mut self, address: u64, length: u64) {
self.ask(Queued::Invalidate(address, address + length));
}
fn mapped(&mut self, kind: AccessKind, address: u64, width: Width, value: u64) -> Option<u64> {
match kind {
AccessKind::Read => self.memory.read(address, width),
AccessKind::Write if self.memory.read_only(address) => Some(value),
AccessKind::Write => self.memory.write(address, width, value).then(|| {
self.invalidate(address, u64::from(width.bytes()));
value
}),
}
}
fn mapped_byte(&self, address: u64) -> bool {
self.memory.read(address, Width::W8).is_some()
}
fn debugger_watch(&mut self, range: (u64, u64), watchpoint: Watchpoint, set: bool) -> bool {
let each = (range, watchpoint);
if !set {
self.rerouted(|watching| watching.debugged.retain(|&other| other != each));
return true;
}
if self.watching.debugged.contains(&each) {
return true;
}
if self.watching.debugged.len() >= DEBUGGER_WATCHPOINTS {
return false;
}
let ranges: Vec<(u64, u64)> = self
.watching
.debugged
.iter()
.map(|&(range, _)| range)
.collect();
if !fit(&ranges, range, DEBUGGER_ROUTED_BYTES, self.page) {
return false;
}
self.rerouted(|watching| watching.debugged.push(each));
true
}
fn debugger_unwatch_all(&mut self) {
self.rerouted(|watching| watching.debugged.clear());
self.watching.debugger_hit = None;
}
fn save(&mut self) -> Vec<u8> {
let rtt = self.rtt.take();
let terminal = rtt.as_ref().map(|rtt| (rtt.found(), rtt.waiting(self)));
self.rtt = rtt;
let saved = Saved {
core: self.cpu().save(),
warped: self.warped,
restart: self.restart,
arrived: self.watching.arrived,
errno: self.errno,
terminal,
};
postcard::to_allocvec(&saved).expect("the core's state serializes")
}
fn carried(
&self,
instructions: u64,
event: Option<VirtualNanos>,
until: Option<u64>,
) -> Option<u32> {
let left = until?.checked_sub(instructions).filter(|&left| left > 0)?;
if self.restart || self.watching.written.is_some() || self.watching.debugger_hit.is_some() {
return None;
}
let now = self.time(instructions);
let target = self.earliest(event);
if target <= now {
return None;
}
let stretch = instructions_until(self.rate, self.warped, instructions, target);
Some(stretch.min(u32::try_from(left).unwrap_or(u32::MAX)))
}
fn earliest(&self, event: Option<VirtualNanos>) -> VirtualNanos {
[event, self.horizon, self.drain]
.into_iter()
.flatten()
.fold(self.deadline, VirtualNanos::min)
}
fn put_back(&mut self, state: &[u8], memory: &[(u64, &[u8])]) -> Result<(), Error> {
self.load(state)?;
for &(base, bytes) in memory {
if !self.memory.place(base, bytes) {
return Err(Error::State);
}
self.invalidate(base, bytes.len() as u64);
}
Ok(())
}
fn load(&mut self, bytes: &[u8]) -> Result<(), Error> {
let saved: Saved = match postcard::take_from_bytes(bytes) {
Ok((saved, [])) => saved,
Ok(_) | Err(_) => return Err(Error::State),
};
self.cpu().load(&saved.core)?;
self.warped = saved.warped;
self.restart = saved.restart;
self.watching.arrived = saved.arrived;
self.errno = saved.errno;
if let (Some(rtt), Some((found, waiting))) = (self.rtt.as_mut(), saved.terminal) {
rtt.put_back(found, waiting);
}
for page in self.watching.pages(self.page) {
self.ask(Queued::Route(page));
}
Ok(())
}
}
#[derive(serde::Serialize, serde::Deserialize)]
struct Saved {
core: Vec<u8>,
warped: u64,
restart: bool,
arrived: Option<(u64, u64)>,
errno: u64,
terminal: Option<(Option<u64>, u64)>,
}
impl GuestMemory for Machine {
fn read(&mut self, address: u64, width: Width) -> Result<u64, Error> {
self.memory
.read(address, width)
.ok_or(Error::Memory { address, width })
}
fn write(&mut self, address: u64, width: Width, value: u64) -> Result<(), Error> {
if self.memory.read_only(address) || !self.memory.write(address, width, value) {
return Err(Error::Memory { address, width });
}
self.invalidate(address, u64::from(width.bytes()));
Ok(())
}
fn endian(&self) -> Endian {
self.memory.endian()
}
}
impl Host for Machine {
fn set_interrupt(&mut self, line: InterruptLine, level: Level) {
self.ask(Queued::Line(line, level));
}
fn request_reset(&mut self) {
self.restart = true;
self.ask(Queued::Return);
}
fn save_state(&mut self) -> Result<Vec<u8>, Error> {
if self.read_only_written {
return Err(Error::WroteReadOnly);
}
if self.cpu.is_none() {
return Err(Error::State);
}
Ok(self.save())
}
fn watch(&mut self, watch: Watch) -> bool {
match watch {
Watch::Instruction(address) => {
if self.watching.instructions.insert(address) && !self.breakpoint(address, true) {
self.watching.instructions.remove(&address);
return false;
}
true
}
Watch::Writes { address, length } => {
let Some(range) = length
.checked_sub(1)
.and_then(|extent| address.checked_add(extent))
.map(|last| (address, last))
.filter(|&range| {
fit(&self.watching.writes, range, RUN_ROUTED_BYTES, self.page)
})
else {
return false;
};
self.rerouted(|watching| watching.writes.push(range));
true
}
}
}
fn unwatch(&mut self, watch: Watch) {
match watch {
Watch::Instruction(address) => {
if self.watching.instructions.remove(&address) {
self.breakpoint(address, false);
}
}
Watch::Writes { address, length } => {
let range = (address, address.wrapping_add(length.wrapping_sub(1)));
self.rerouted(|watching| {
if let Some(index) = watching.writes.iter().position(|&each| each == range) {
watching.writes.remove(index);
}
});
}
}
}
}
struct Served<'a> {
machine: &'a mut Machine,
driven: &'a mut dyn Driven,
carry_until: Option<u64>,
asked: Option<Access>,
modeled: Option<(u64, u64)>,
trace: Option<&'a mut Stream>,
}
impl CoreBus for Served<'_> {
fn access(&mut self, access: Access, from_code: bool) -> Option<u64> {
let asked = self.asked.take() == Some(access);
let machine = &mut *self.machine;
let mapped = machine.mapped(access.kind, access.address, access.width, access.value);
let answer = if let Some(answer) = mapped {
answer
} else {
if from_code {
self.modeled = access
.program_counter
.map(|pc| (pc, access.instruction_count));
}
let (answer, next, failed) = self.driven.access(access, machine);
machine.deadline = next;
if failed {
return None;
}
answer
};
if from_code {
let watching = &mut machine.watching;
watching.accessed(access.kind, access.address, access.width);
if !asked && let Some(hit) = watching.hit(&access) {
watching.debugger_hit.get_or_insert(hit);
}
}
Some(answer)
}
fn time(&self, instructions: u64) -> VirtualNanos {
self.machine.time(instructions)
}
fn queued(&mut self) -> Vec<Queued> {
std::mem::take(&mut self.machine.queued)
}
fn carry_on(&mut self, instructions: u64, event: Option<VirtualNanos>) -> Option<u32> {
self.machine.carried(instructions, event, self.carry_until)
}
fn stops_before(&mut self, access: &Access) -> bool {
self.asked = Some(*access);
let watching = &mut self.machine.watching;
let Some(hit) = watching.hit(access) else {
return false;
};
let at = access
.program_counter
.map(|pc| (pc, access.instruction_count));
if at.is_some() && self.modeled == at {
watching.debugger_hit.get_or_insert(hit);
return false;
}
watching.written = None;
watching.debugger_hit = Some(hit);
true
}
fn send_trace(&mut self, bytes: &[u8]) {
if let Some(stream) = self.trace.as_deref_mut() {
stream.send(bytes);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Stretch {
Run(u32),
Over(u64),
}
impl Core {
fn stop(&mut self, reason: StopReason) -> bool {
let stop = Stop {
at: self.machine.now(),
instruction_count: self.machine.retired(),
reason,
};
match self.driven.stop(stop, &mut self.machine) {
Some(next) => {
self.machine.deadline = next;
true
}
None => false,
}
}
fn debugger_reset(&mut self) {
let goes_on = self.stop(StopReason::Reset);
debug_assert!(goes_on, "a run ended at a reset");
self.machine.reset();
}
fn hit(&mut self, kind: HitKind, address: u64) {
let count = self.machine.retired();
let hit = Hit {
kind,
address,
at: self.machine.time(count),
instruction_count: count,
};
self.machine.deadline = self.driven.hit(hit, &mut self.machine);
}
fn next(&mut self) -> Option<Stretch> {
loop {
if std::mem::take(&mut self.machine.restart) {
self.machine.reset();
}
if std::mem::take(&mut self.machine.requested)
|| self.machine.cpu().take_reset_request()
{
if !self.stop(StopReason::Reset) {
return None;
}
self.machine.reset();
continue;
}
let now = self.machine.now();
if let Some(event) = self.machine.cpu().next_event()
&& event <= now
{
self.machine.cpu().on_event(now);
continue;
}
if now >= self.machine.deadline {
let reason = if self.machine.cpu().waiting() {
StopReason::Idle
} else {
StopReason::Deadline
};
if !self.stop(reason) {
return None;
}
continue;
}
if !self.machine.watching.instructions.is_empty() {
let pc = self.machine.cpu().pc();
let watching = &self.machine.watching;
if watching.arrived == Some((pc, self.machine.retired()))
&& watching.instructions.contains(&pc)
{
return Some(Stretch::Over(pc));
}
}
let target = self.target();
let machine = &self.machine;
return Some(Stretch::Run(instructions_until(
machine.rate,
machine.warped,
machine.retired(),
target,
)));
}
}
fn target(&mut self) -> VirtualNanos {
let event = self.machine.cpu().next_event();
self.machine.earliest(event)
}
fn execute(&mut self, instructions: u32, carry_until: Option<u64>) -> (Exit, u64) {
let mut cpu = self.machine.cpu.take().expect("between stretches");
let ran = cpu.run(
instructions,
&mut Served {
machine: &mut self.machine,
driven: &mut *self.driven,
carry_until,
asked: None,
modeled: None,
trace: self.trace.as_mut(),
},
);
self.machine.cpu = Some(cpu);
let queued = std::mem::take(&mut self.machine.queued);
for queued in queued {
self.machine.ask(queued);
}
for (address, had) in std::mem::take(&mut self.machine.breakpoints) {
let wanted = self.machine.watching.instructions.contains(&address);
if wanted != had && !self.machine.breakpoint(address, wanted) {
self.machine.watching.instructions.remove(&address);
self.driven.unwatched(Watch::Instruction(address));
}
}
ran
}
fn run_stretch(&mut self, stretch: Stretch, carry_until: Option<u64>) -> (Exit, u64) {
let ran = match stretch {
Stretch::Run(instructions) => self.execute(instructions, carry_until),
Stretch::Over(address) => {
self.machine.cpu().pass_breakpoint(address);
self.execute(1, None)
}
};
self.machine.requested |= self.machine.cpu().take_reset_request();
ran
}
fn checkpoint(&mut self) -> Result<Checkpoint, snapshot::Error> {
self.machine.requested |= self.machine.cpu().take_reset_request();
if self.machine.restart || self.machine.requested {
return Err(snapshot::Error::Resetting);
}
let run = self.driven.keep()?;
let sent = |stream: Option<&Stream>| stream.map_or(0, Stream::sent);
Ok(Checkpoint {
position: self.machine.position(),
machine: self.machine.save(),
drain: self.machine.drain,
memory: self.machine.memory.contents(self.machine.read_only_written),
run,
sent: (sent(self.trace.as_ref()), sent(self.terminal.as_ref())),
})
}
fn restore(&mut self, checkpoint: &Checkpoint) -> Result<(), String> {
let machine = &mut self.machine;
machine.cpu().take_reset_request();
for address in std::mem::take(&mut machine.watching.instructions) {
machine.breakpoint(address, false);
}
machine.rerouted(|watching| watching.writes.clear());
let kept_read_only = checkpoint
.memory
.iter()
.any(|&(base, _)| machine.memory.read_only(base));
let mut memory: Vec<(u64, Vec<u8>)> = Vec::new();
if machine.read_only_written && !kept_read_only {
memory = machine.memory.erased_read_only();
for segment in &machine.segments {
if machine.memory.read_only(segment.address) {
memory.push((segment.address, segment.bytes.clone()));
}
}
}
let ranges: Vec<(u64, &[u8])> = memory
.iter()
.chain(&checkpoint.memory)
.map(|(base, bytes)| (*base, bytes.as_slice()))
.collect();
machine
.put_back(&checkpoint.machine, &ranges)
.map_err(|error| error.to_string())?;
machine.drain = checkpoint.drain;
machine.horizon = None;
machine.requested = false;
machine.restart = false;
let watching = &mut machine.watching;
watching.passing = None;
watching.debugger_hit = None;
watching.written = None;
if let Some(rtt) = self.machine.rtt.as_mut() {
rtt.put_back(rtt.found(), 0);
}
if let Some(trace) = self.trace.as_mut() {
trace.went_back(checkpoint.sent.0);
}
if let Some(terminal) = self.terminal.as_mut() {
terminal.went_back(checkpoint.sent.1);
}
self.driven
.put_back(&checkpoint.run, &mut self.machine)
.map_err(|error| error.to_string())?;
self.machine.deadline = self.driven.first_deadline();
Ok(())
}
fn new_way(&mut self) {
self.driven.new_way(self.machine.now());
if let Some(trace) = self.trace.as_mut() {
trace.new_way();
}
if let Some(terminal) = self.terminal.as_mut() {
terminal.new_way();
}
}
fn host_call(&mut self, mut debugger: Option<&mut Debugger<'_>>) -> Answered {
let machine = &self.machine;
let call = self.semihosting.then(|| {
let cpu = machine.cpu.as_deref().expect("between stretches");
cpu.host_call(&mut |address, width| machine.memory.read(address, width))
});
let answer = match call {
None => {
self.machine.cpu().answer_host_call(None);
return Answered::Unserved;
}
Some(Some(HostCall::Exit { code })) => return Answered::Exited(code),
Some(Some(call)) => self.serve(call, debugger.as_deref_mut()),
Some(None) => HOST_CALL_FAILED,
};
self.machine.cpu().answer_host_call(Some(answer));
if let Some(debugger) = debugger {
debugger.after_file_call(self);
}
Answered::Served
}
fn serve(&mut self, call: HostCall, debugger: Option<&mut Debugger<'_>>) -> u64 {
let now = self.machine.now();
match call {
HostCall::Write {
handle: 1 | 2,
address,
length,
} => {
let memory = &self.machine.memory;
let bytes: Vec<u8> = (address..address.saturating_add(length))
.map_while(|at| memory.read(at, Width::W8))
.map(|byte| u8::try_from(byte).expect("a byte"))
.collect();
let news = self.driven.console(now, self.machine.retired(), &bytes);
if news && let Some(debugger) = debugger {
debugger.console(self, &bytes);
}
length - bytes.len() as u64
}
HostCall::OpenConsole { handle } => handle,
HostCall::IsTty { handle: 0..=2 } => 1,
HostCall::Clock => now.0 / CLOCK_TICK,
HostCall::Time => now.0 / TICKS_PER_SECOND,
HostCall::Elapsed { block } => self.elapsed(block, now.0),
HostCall::TickFrequency => TICKS_PER_SECOND,
HostCall::Errno => self.machine.errno,
call => {
debugger.map_or_else(|| failed(call), |debugger| self.file_call(debugger, call))
}
}
}
fn elapsed(&mut self, block: u64, ticks: u64) -> u64 {
let cpu = self.machine.cpu();
let order = cpu.byte_order(block);
let bytes = if cpu.data_model().pointer() == Width::W32 {
let mut words = to_target(ticks & u64::from(u32::MAX), 4, order)[..4].to_vec();
words.extend_from_slice(&to_target(ticks >> 32, 4, order)[..4]);
words
} else {
to_target(ticks, 8, order).to_vec()
};
if self.machine.memory.read_only(block) || !self.machine.memory.place(block, &bytes) {
return HOST_CALL_FAILED;
}
self.machine.invalidate(block, 8);
0
}
fn file_call(&mut self, debugger: &mut Debugger<'_>, call: HostCall) -> u64 {
let mut ask = |core: &mut Self, request: String| {
let Fresult { result, errno } = debugger.file_call(core, &request)?;
if result < 0
&& let Some(errno) = errno
{
core.machine.errno = errno;
}
u64::try_from(result).ok()
};
let answered = match call {
HostCall::Open { path, length, mode } => ask(
self,
format!(
"Fopen,{path:x}/{:x},{:x},{FILE_MODE:x}",
length + 1,
open_flags(mode)
),
),
HostCall::Close { handle } => ask(self, format!("Fclose,{handle:x}")),
HostCall::Read {
handle,
address,
length,
} => ask(self, format!("Fread,{handle:x},{address:x},{length:x}"))
.map(|read| length.saturating_sub(read)),
HostCall::Write {
handle,
address,
length,
} => ask(self, format!("Fwrite,{handle:x},{address:x},{length:x}"))
.map(|written| length.saturating_sub(written)),
HostCall::Seek { handle, position } => {
ask(self, format!("Flseek,{handle:x},{position:x},{SEEK_SET:x}")).map(|_| 0)
}
HostCall::Length { handle } => ask(self, format!("Flseek,{handle:x},0,{SEEK_CUR:x}"))
.and_then(|position| {
let end = ask(self, format!("Flseek,{handle:x},0,{SEEK_END:x}"))?;
ask(self, format!("Flseek,{handle:x},{position:x},{SEEK_SET:x}"))?;
Some(end)
}),
HostCall::IsTty { handle } => ask(self, format!("Fisatty,{handle:x}")),
_ => None,
};
answered.unwrap_or_else(|| failed(call))
}
fn finish_ports(&mut self) {
if let Some(trace) = self.trace.take() {
trace.finish();
}
self.drain_rtt(true);
if let Some(terminal) = self.terminal.take() {
terminal.finish();
}
}
fn drain_rtt(&mut self, now: bool) {
let Some(terminal) = self.terminal.as_mut() else {
return;
};
let machine = &mut self.machine;
let at = machine.now();
if now || machine.drain.is_some_and(|drain| at >= drain) {
if let Some(mut rtt) = machine.rtt.take() {
terminal.send(&rtt.drain(machine));
machine.rtt = Some(rtt);
}
machine.drain = Some(rtt::next_drain(at));
}
terminal.flush();
}
fn after(&mut self, exit: Exit, ran: u64) {
if let Some(address) = self.machine.watching.written.take() {
self.hit(HitKind::Write, address);
}
if exit == Exit::Breakpoint {
let arrival = (self.machine.cpu().pc(), self.machine.retired());
let watching = &mut self.machine.watching;
if watching.instructions.contains(&arrival.0) && watching.arrived != Some(arrival) {
watching.arrived = Some(arrival);
self.hit(HitKind::Instruction, arrival.0);
}
return;
}
let halted = matches!(exit, Exit::Waiting | Exit::Lockup) && ran == 0;
if halted || (self.machine.cpu().waiting() && ran == 0) {
let now = self.machine.now();
let target = self.target();
if target > now && target != NO_DEADLINE {
self.machine.warped += target.0 - now.0;
}
}
}
}
#[expect(
clippy::disallowed_methods,
reason = "the core's own thread, the only one a run has, joined before this returns"
)]
pub fn run<D, R>(session: Session<D>, start: &Start<'_>, driver: R) -> Result<Finished<D, R>, Error>
where
D: Board + ?Sized + 'static,
R: Driver<D> + 'static,
{
let mut session = session;
let (spec, map, peripherals, segments, endian) = {
let target = session.target();
let mut targets = session.board_mut().targets();
let chosen = targets.swap_remove(target);
let map = chosen.mcu.bus().memory().to_vec();
let endian = chosen.mcu.cpu().endian;
let segments = image::place(start.images, &map, &chosen.core, endian, start.boot)?;
let peripherals: Vec<_> = chosen
.mcu
.bus()
.peripherals()
.map(|peripheral| peripheral.base..peripheral.base + peripheral.size)
.collect();
(chosen.core, map, peripherals, segments, endian)
};
let cpu = (spec.processor)(endian)?;
let model = cpu.data_model();
let memory_map = start
.debugger
.map(|_| Map::new(&map, &spec.aliases, &peripherals, &cpu.own_ranges()));
let page = cpu.page_size();
if !page.is_power_of_two() || page > PAGE_ALIGNMENT as u64 {
return Err(Error::Machine(format!(
"the core asks for pages of {page:#x} bytes, and a page has to be a power of two no larger than {PAGE_ALIGNMENT:#x}"
)));
}
let memory = Memory::new(&map, &spec.aliases, endian);
if let Some(mapping) = memory.partial_page(page) {
return Err(Error::Machine(format!(
"the core maps memory in pages of {page:#x} bytes, and the {:#x} bytes at {:#x} aren't whole pages",
mapping.length, mapping.start
)));
}
if let Some(mapping) = memory
.mappings()
.into_iter()
.find(|mapping| !cpu.maps(mapping.start, mapping.length))
{
return Err(Error::Machine(format!(
"the core can't hold the {:#x} bytes of memory at {:#x}, outside its addresses or over ones it answers itself",
mapping.length, mapping.start
)));
}
let resume = start.resume;
let driven: Box<dyn Driven> =
Box::new(Run::new(session, driver, resume.map(|resume| resume.stop)));
let mut machine = Machine::new(
memory,
cpu,
spec.instructions_per_second,
segments,
start.boot.unwrap_or(spec.boot.reset),
);
let debugger = start
.debugger
.zip(memory_map)
.map(|(listener, map)| {
Debugger::listen(
listener,
start.wait_for_debugger,
map,
start.kernels,
start.interrupt,
)
})
.transpose()?;
let trace = start.trace.map(Stream::listen).transpose()?;
let (rtt, terminal) = start
.rtt
.map(|listener| terminal(start, &map, (model.pointer(), spec.code_address), listener))
.transpose()?
.unzip();
machine.drain = rtt
.as_ref()
.filter(|rtt| !rtt.has_none())
.map(|_| VirtualNanos(rtt::PERIOD));
machine.rtt = rtt;
let (left, exit_status, interrupted) = std::thread::scope(|scope| {
std::thread::Builder::new()
.name("core".into())
.stack_size(CORE_STACK)
.spawn_scoped(scope, move || {
run_core(
Core {
driven,
machine,
semihosting: start.semihosting,
trace,
terminal,
},
resume,
debugger,
start.interrupt,
)
})
.expect("a thread for the core")
.join()
.unwrap_or_else(|panic| std::panic::resume_unwind(panic))
})?;
let run = left
.downcast::<Run<D, R>>()
.unwrap_or_else(|_| unreachable!("the run that was prepared"));
let Run {
session,
driver,
last,
..
} = *run;
Ok(Finished {
session,
driver,
stop: last,
exit_status,
interrupted,
})
}
fn failed(call: HostCall) -> u64 {
match call {
HostCall::Read { length, .. } | HostCall::Write { length, .. } => length,
_ => HOST_CALL_FAILED,
}
}
fn terminal(
start: &Start<'_>,
map: &[Region],
(pointer, code_address): (Width, fn(u64) -> u64),
listener: &TcpListener,
) -> Result<(Rtt, Stream), Error> {
let ram = map
.iter()
.filter(|region| region.kind == RegionKind::Ram)
.map(|region| region.base..region.base + region.size)
.collect();
let symbols = start
.images
.first()
.and_then(|image| Symbols::read(image.bytes(), code_address).ok());
let symbol = symbols
.as_ref()
.and_then(|symbols| symbols.get(rtt::SYMBOL).ok());
let has_symbols = symbols.as_ref().is_some_and(Symbols::any);
Ok((
Rtt::new(symbol, has_symbols, ram, pointer),
Stream::listen(listener)?,
))
}
type Left = (Box<dyn Any + Send>, Option<u32>, Option<Interrupted>);
fn run_core(
mut core: Core,
resume: Option<&Resume<'_>>,
mut debugger: Option<Debugger<'_>>,
interrupt: Option<&AtomicBool>,
) -> Result<Left, Error> {
let machine = &mut core.machine;
machine.map_memory();
for segment in &machine.segments {
let placed = machine.memory.place(segment.address, &segment.bytes);
assert!(placed, "image::place keeps every segment inside memory");
}
machine.boot();
if let Some(resume) = resume {
machine.put_back(resume.state, &resume.memory)?;
if machine.now() != resume.stop.at || machine.retired() != resume.stop.instruction_count {
return Err(Error::State);
}
if machine.drain.is_some() {
machine.drain = Some(rtt::next_drain(machine.now()));
}
}
core.machine.deadline = core.driven.first_deadline();
let mut interrupted = None;
loop {
if let Some(debugger) = debugger.as_mut() {
debugger.serve(&mut core);
}
let Some(stretch) = core.next() else {
break;
};
if interrupt.is_some_and(|flag| flag.load(Ordering::Relaxed)) {
interrupted = Some(Interrupted {
at: core.machine.now(),
instruction_count: core.machine.retired(),
});
break;
}
let (stretch, carry_until) = match debugger.as_mut() {
Some(debugger) => {
debugger.hold_interrupts(&mut core);
(debugger.limit(&core, stretch), debugger.carry_until(&core))
}
None => (stretch, Some(u64::MAX)),
};
let (exit, ran) = core.run_stretch(stretch, carry_until);
let (exit, ran) = if exit == Exit::HostCall {
match core.host_call(debugger.as_mut()) {
Answered::Served => (exit, ran),
Answered::Unserved => {
let (exit, more) = core.run_stretch(Stretch::Run(0), carry_until);
(exit, ran + more)
}
Answered::Exited(code) => {
core.after(exit, ran);
if let Some(debugger) = debugger {
debugger.ended(code);
}
core.finish_ports();
return Ok((core.driven.into_any(), Some(code), None));
}
}
} else {
(exit, ran)
};
core.after(exit, ran);
if let Some(trace) = core.trace.as_mut() {
trace.flush();
}
core.drain_rtt(false);
if let Some(debugger) = debugger.as_mut() {
if core.next().is_none() {
break;
}
debugger.after(&mut core, exit, ran);
}
}
if let Some(debugger) = debugger {
debugger.exited();
}
core.finish_ports();
Ok((core.driven.into_any(), None, interrupted))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_rate_of_four_nanoseconds_is_the_clock_targets_always_ran_on() {
let rate = NonZeroU64::new(250_000_000).unwrap();
for retired in [0, 1, 7, 1_000_003, u64::from(u32::MAX) * 3] {
assert_eq!(nanos(rate, retired), retired * 4);
for (warped, ahead) in [(0, 1), (0, 4), (6, 5), (1_000, 4_000_001)] {
let now = warped + retired * 4;
let target = VirtualNanos(now + ahead);
let old = ahead.div_ceil(4).clamp(1, LONGEST_STRETCH);
assert_eq!(
u64::from(instructions_until(rate, warped, retired, target)),
old,
"{retired} retired, {warped} warped, {ahead} ahead"
);
}
}
}
#[test]
fn a_stretch_ends_at_the_first_instruction_at_or_past_its_target() {
let rate = NonZeroU64::new(168_000_000).unwrap();
assert_eq!(nanos(rate, 1), 5);
assert_eq!(nanos(rate, 168), 1_000);
assert_eq!(instructions_until(rate, 0, 0, VirtualNanos(1_000)), 168);
assert_eq!(instructions_until(rate, 0, 0, VirtualNanos(1_001)), 169);
for target in 1..3_000 {
let n = u64::from(instructions_until(rate, 0, 0, VirtualNanos(target)));
assert!(nanos(rate, n) >= target, "{n} reaches {target}");
assert!(nanos(rate, n - 1) < target, "{} doesn't", n - 1);
}
assert_eq!(
instructions_until(rate, 0, 500, VirtualNanos(10)),
1,
"past it already"
);
}
#[test]
fn a_range_fits_while_its_pages_and_those_already_routed_are_within_the_limit() {
let page = 0x400;
let four = 4 * page;
assert!(fit(&[], (0, page * 4 - 1), four, page));
assert!(!fit(&[], (0, page * 4), four, page), "a fifth page");
assert!(
fit(&[(0, page - 1)], (0, page * 3), four, page),
"a page both touch counts once"
);
assert!(
!fit(&[(0, page - 1)], (page * 10, page * 13), four, page),
"four more on top of one"
);
assert!(
!fit(&[], (0, u64::MAX), four, page),
"the whole address space"
);
assert!(
fit(&[], (0, 0x3FF), four, 1),
"a kibibyte of byte-sized pages fits the same bytes"
);
}
#[test]
fn the_runs_own_watches_keep_their_budget() {
let page = 0x400;
assert_eq!(RUN_ROUTED_BYTES, 960 << 10);
assert!(fit(&[], (0, RUN_ROUTED_BYTES - 1), RUN_ROUTED_BYTES, page));
assert!(
!fit(&[], (0, RUN_ROUTED_BYTES), RUN_ROUTED_BYTES, page),
"a page more"
);
let quarter = (0x2000_0000, 0x2003_FFFF);
assert!(!fit(&[], quarter, 64 << 10, page), "the old budget");
assert!(fit(&[], quarter, DEBUGGER_ROUTED_BYTES, page));
}
#[test]
fn memory_of_whole_kibibytes_is_whole_pages() {
const KIB: u64 = 1024;
for size in [2 * KIB, 6 * KIB, 10 * KIB] {
let memory = Memory::new(
&[Region::ram("sram", 0x2000_0000, size)],
&[],
Endian::Little,
);
assert_eq!(
memory.partial_page(crate::emulator::core::PAGE_SIZE),
None,
"{size:#x}"
);
}
}
fn machine(deadline: VirtualNanos) -> Machine {
Machine {
cpu: None,
memory: Mapped::new(Memory::new(&[], &[], Endian::Little)),
queued: Vec::new(),
rate: NonZeroU64::new(250_000_000).unwrap(),
page: 1,
breakpoints: BTreeMap::new(),
warped: 0,
deadline,
horizon: None,
drain: None,
segments: Vec::new(),
boot: 0,
restart: false,
read_only_written: false,
watching: Watching::default(),
requested: false,
errno: 0,
rtt: None,
}
}
#[test]
fn carry_never_runs_past_where_the_debugger_needs_the_core() {
let mut machine = machine(VirtualNanos(4_000));
assert_eq!(machine.carried(100, None, Some(u64::MAX)), Some(900));
assert_eq!(
machine.carried(100, Some(VirtualNanos(800)), Some(u64::MAX)),
Some(100),
"the processor's own event first"
);
assert_eq!(machine.carried(100, None, Some(150)), Some(50));
assert_eq!(machine.carried(150, None, Some(150)), None, "there");
assert_eq!(machine.carried(151, None, Some(150)), None, "past it");
assert_eq!(machine.carried(100, None, None), None, "a step");
assert_eq!(
machine.carried(1_000, None, Some(u64::MAX)),
None,
"at the deadline"
);
machine.horizon = Some(VirtualNanos(2_000));
assert_eq!(machine.carried(100, None, Some(u64::MAX)), Some(400));
}
fn access(kind: AccessKind, address: u64, pc: u64, count: u64) -> Access {
Access {
kind,
address,
width: Width::W32,
value: 0,
instruction_count: count,
virtual_time: VirtualNanos(count * 4),
program_counter: Some(pc),
}
}
#[test]
fn a_debugger_watchpoint_is_hit_by_what_it_watches_for() {
let hit = |watchpoint, kind, address| {
let watching = Watching {
debugged: vec![((0x2000_0010, 0x2000_0013), watchpoint)],
..Watching::default()
};
watching.hit(&access(kind, address, 0x100, 7))
};
let (read, write) = (AccessKind::Read, AccessKind::Write);
assert_eq!(
hit(Watchpoint::Write, write, 0x2000_0010),
Some((0x2000_0010, Watchpoint::Write))
);
assert_eq!(hit(Watchpoint::Write, read, 0x2000_0010), None);
assert_eq!(
hit(Watchpoint::Read, read, 0x2000_000E),
Some((0x2000_0010, Watchpoint::Read))
);
assert_eq!(hit(Watchpoint::Read, write, 0x2000_0010), None);
for kind in [read, write] {
assert_eq!(
hit(Watchpoint::Access, kind, 0x2000_0012),
Some((0x2000_0012, Watchpoint::Access))
);
}
assert_eq!(hit(Watchpoint::Access, read, 0x2000_0014), None, "past it");
let watching = Watching {
debugged: vec![((0x2000_0010, 0x2000_0013), Watchpoint::Access)],
passing: Some((0x100, 7)),
..Watching::default()
};
assert_eq!(watching.hit(&access(read, 0x2000_0010, 0x100, 7)), None);
assert!(watching.hit(&access(read, 0x2000_0010, 0x100, 9)).is_some());
}
#[test]
fn a_debuggers_watchpoint_set_again_is_the_one_there() {
let mut machine = machine(VirtualNanos(4_000));
let word = (0x2000_0000, 0x2000_0003);
for _ in 0..3 {
assert!(machine.debugger_watch(word, Watchpoint::Write, true));
}
assert_eq!(machine.watching.debugged.len(), 1);
assert!(machine.debugger_watch(word, Watchpoint::Write, false));
assert!(machine.watching.debugged.is_empty());
}
#[test]
fn the_first_store_into_a_watched_range_is_noted_at_its_first_byte_inside() {
let mut watching = Watching {
writes: vec![(0x2000_0010, 0x2000_001F)],
..Watching::default()
};
watching.accessed(AccessKind::Write, 0x2000_0000, Width::W32);
assert_eq!(watching.written, None);
watching.accessed(AccessKind::Read, 0x2000_0010, Width::W32);
assert_eq!(watching.written, None, "a load is no store");
watching.accessed(AccessKind::Write, 0x2000_000E, Width::W32);
assert_eq!(watching.written, Some(0x2000_0010));
watching.accessed(AccessKind::Write, 0x2000_0018, Width::W8);
assert_eq!(
watching.written,
Some(0x2000_0010),
"the instruction's first store"
);
}
}