use std::collections::BTreeMap;
use std::fmt;
use std::net::TcpListener;
use std::num::NonZeroU64;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use crate::device::Level;
use crate::image::Image;
use crate::machine::Board;
use crate::session::Session;
use crate::soc::{Endian, Width};
use crate::time::VirtualNanos;
#[cfg(not(emulator_absent))]
pub(crate) mod core;
#[cfg_attr(
all(emulator_absent, not(test)),
expect(
dead_code,
reason = "only the core uses it, and Miri builds the crate without one"
)
)]
pub(crate) mod memory;
mod printed;
mod processor;
#[cfg(not(emulator_absent))]
mod runner;
pub use printed::{Accesses, NO_DEADLINE, Printed, run_printed};
pub use processor::{BitAlias, CoreBus, Exit, HaltReason, Processor, Queued};
#[cfg(all(fuzzing, not(emulator_absent)))]
pub(crate) use runner::Fuzzed;
#[cfg(not(emulator_absent))]
pub use runner::run;
#[cfg(emulator_absent)]
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,
{
Err(Error::NoEmulator)
}
pub const INTERRUPT_LINES: u16 = 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct InterruptLine(u16);
impl InterruptLine {
#[must_use]
pub const fn new(index: u16) -> Option<Self> {
if index < INTERRUPT_LINES {
Some(Self(index))
} else {
None
}
}
#[must_use]
pub const fn index(self) -> u16 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DataModel {
Ilp32,
Lp64,
}
impl DataModel {
#[must_use]
pub const fn pointer(self) -> Width {
match self {
Self::Ilp32 => Width::W32,
Self::Lp64 => Width::W64,
}
}
#[must_use]
pub const fn long(self) -> Width {
match self {
Self::Ilp32 => Width::W32,
Self::Lp64 => Width::W64,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum AccessKind {
Read,
Write,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Access {
pub kind: AccessKind,
pub address: u64,
pub width: Width,
pub value: u64,
pub instruction_count: u64,
pub virtual_time: VirtualNanos,
pub program_counter: Option<u64>,
}
impl Access {
#[must_use]
pub const fn new(kind: AccessKind, address: u64, width: Width, value: u64) -> Self {
Self {
kind,
address,
width,
value,
instruction_count: 0,
virtual_time: VirtualNanos(0),
program_counter: None,
}
}
#[must_use]
pub const fn at(self, instruction_count: u64, virtual_time: VirtualNanos) -> Self {
Self {
instruction_count,
virtual_time,
..self
}
}
#[must_use]
pub const fn by_instruction(self, program_counter: u64) -> Self {
Self {
program_counter: Some(program_counter),
..self
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum StopReason {
Deadline,
Idle,
Reset,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Stop {
pub at: VirtualNanos,
pub instruction_count: u64,
pub reason: StopReason,
}
pub trait Memory {
fn read(&mut self, address: u64, width: Width) -> Result<u64, Error>;
fn write(&mut self, address: u64, width: Width, value: u64) -> Result<(), Error>;
fn endian(&self) -> Endian {
Endian::Little
}
}
pub trait Host: Memory {
fn set_interrupt(&mut self, line: InterruptLine, level: Level);
fn request_reset(&mut self);
fn save_state(&mut self) -> Result<Vec<u8>, Error> {
Err(Error::State)
}
fn watch(&mut self, watch: Watch) -> bool {
let _ = watch;
false
}
fn unwatch(&mut self, watch: Watch) {
let _ = watch;
}
}
pub type ProcessorConstructor =
Arc<dyn Fn(Endian) -> Result<Box<dyn Processor>, Error> + Send + Sync>;
#[derive(Clone)]
#[non_exhaustive]
pub struct CoreSpec {
pub processor: ProcessorConstructor,
pub instructions_per_second: NonZeroU64,
pub aliases: Vec<Alias>,
pub boot: Boot,
pub elf: Option<crate::elf::Machine>,
pub code_address: fn(u64) -> u64,
}
impl CoreSpec {
#[must_use]
pub fn new(
processor: ProcessorConstructor,
instructions_per_second: NonZeroU64,
boot: Boot,
) -> Self {
Self {
processor,
instructions_per_second,
aliases: Vec::new(),
boot,
elf: None,
code_address: std::convert::identity,
}
}
}
impl fmt::Debug for CoreSpec {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("CoreSpec")
.field("instructions_per_second", &self.instructions_per_second)
.field("aliases", &self.aliases)
.field("boot", &self.boot)
.field("elf", &self.elf)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Alias {
pub region: &'static str,
pub at: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Boot {
pub reset: u64,
pub alignment: NonZeroU64,
}
impl Boot {
#[must_use]
pub const fn new(reset: u64, alignment: NonZeroU64) -> Self {
Self { reset, alignment }
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Start<'a> {
pub images: &'a [Image],
pub boot: Option<u64>,
pub debugger: Option<&'a TcpListener>,
pub wait_for_debugger: bool,
pub resume: Option<&'a Resume<'a>>,
pub semihosting: bool,
pub trace: Option<&'a TcpListener>,
pub rtt: Option<&'a TcpListener>,
pub kernels: &'a [Box<dyn WalkThreads>],
pub interrupt: Option<&'a AtomicBool>,
}
impl<'a> Start<'a> {
#[must_use]
pub fn new(images: &'a [Image]) -> Self {
Self {
images,
boot: None,
debugger: None,
wait_for_debugger: false,
resume: None,
semihosting: false,
trace: None,
rtt: None,
kernels: &[],
interrupt: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Resume<'a> {
pub(crate) stop: Stop,
pub(crate) state: &'a [u8],
pub(crate) memory: Vec<(u64, &'a [u8])>,
}
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[non_exhaustive]
pub enum Watch {
Instruction(u64),
Writes {
address: u64,
length: u64,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Hit {
pub kind: HitKind,
pub address: u64,
pub at: VirtualNanos,
pub instruction_count: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HitKind {
Instruction,
Write,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Settled {
Access {
access: Access,
value: u64,
},
Stop(Stop),
Hit(Hit),
}
pub trait Driver<D: Board + ?Sized>: Send {
fn on_settled(&mut self, session: &mut Session<D>, host: &mut dyn Host, settled: Settled) {
let _ = (session, host, settled);
}
fn on_stop(
&mut self,
session: &mut Session<D>,
stop: Stop,
host: &mut dyn Host,
) -> Option<VirtualNanos>;
fn on_console(&mut self, at: VirtualNanos, instruction_count: u64, bytes: &[u8]) {
let _ = (at, instruction_count, bytes);
}
fn on_debugger_print(&mut self, at: VirtualNanos, instruction_count: u64, text: &[u8]) {
let _ = (at, instruction_count, text);
}
fn on_debugger_attached(&mut self, at: VirtualNanos, instruction_count: u64, attached: bool) {
let _ = (at, instruction_count, attached);
}
fn may_rewind(&self) -> bool {
false
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Finished<D: Board + ?Sized, R> {
pub session: Session<D>,
pub driver: R,
pub stop: Option<Stop>,
pub exit_status: Option<u32>,
pub interrupted: Option<Interrupted>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Interrupted {
pub at: VirtualNanos,
pub instruction_count: u64,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("{machine} has no target {target}: it has {count}")]
NoTarget {
machine: &'static str,
target: usize,
count: usize,
},
#[error("{machine} puts a region called {region} at {at:#x}, but {target} has no such region (it has {})", regions.join(", "))]
NoRegion {
machine: &'static str,
target: &'static str,
region: &'static str,
at: u64,
regions: Vec<&'static str>,
},
#[error("{machine} puts {region} at {at:#x}, where {target} already has {other}")]
OverlappingAlias {
machine: &'static str,
target: &'static str,
region: &'static str,
at: u64,
other: &'static str,
},
#[error("guest memory has nothing at {address:#x} for a {} bit access", width.bits())]
Memory {
address: u64,
width: Width,
},
#[error("serving the debugger: {0}")]
Debugger(std::io::Error),
#[error("this build has no emulator, and only Miri builds the crate without one")]
NoEmulator,
#[error("writing the run's output: {0}")]
Output(#[from] std::io::Error),
#[error("a period of 0 ns would read the outputs forever at one instant")]
ZeroPeriod,
#[error("the emulator couldn't watch {0:?}")]
Unwatched(Watch),
#[error(transparent)]
Image(#[from] crate::image::Error),
#[error(transparent)]
Edge(#[from] crate::edge::Mismatch),
#[error(
"{machine} has no edge, so there are no outputs to read. Implement `Edge` for it and have `Board::edge` answer it"
)]
NoEdge {
machine: &'static str,
},
#[error("{0}")]
Machine(String),
#[error(
"the emulator couldn't save or load its own state: it holds none, or the state isn't one this build saved"
)]
State,
#[error(
"a debugger wrote memory the guest can't write, which a snapshot keeps only as the images placed it, so the run takes no snapshot"
)]
WroteReadOnly,
#[error("serving an output port: {0}")]
OutputPort(std::io::Error),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum HostCall {
Write {
handle: u64,
address: u64,
length: u64,
},
Exit {
code: u32,
},
Clock,
Time,
Elapsed {
block: u64,
},
TickFrequency,
Open {
path: u64,
length: u64,
mode: OpenMode,
},
OpenConsole {
handle: u64,
},
Close {
handle: u64,
},
Read {
handle: u64,
address: u64,
length: u64,
},
Seek {
handle: u64,
position: u64,
},
Length {
handle: u64,
},
IsTty {
handle: u64,
},
Errno,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OpenMode {
Read,
ReadWrite,
Write,
WriteRead,
Append,
AppendRead,
}
pub trait WalkThreads: fmt::Debug + Sync {
fn symbols(&self) -> &[KernelSymbol];
fn walk(
&self,
found: &[Option<u64>],
registers: &BTreeMap<&str, Vec<u8>>,
endian: Endian,
read: &mut dyn FnMut(u64, Width) -> Option<u64>,
) -> Option<Vec<KernelThread>>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KernelSymbol {
pub name: &'static str,
pub required: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct KernelThread {
pub id: NonZeroU64,
pub name: Option<String>,
pub state: &'static str,
pub saved: Option<BTreeMap<&'static str, Vec<u8>>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_line_exists_below_the_ceiling_and_not_at_it() {
assert_eq!(InterruptLine::new(0).map(InterruptLine::index), Some(0));
assert!(InterruptLine::new(INTERRUPT_LINES - 1).is_some());
assert!(InterruptLine::new(INTERRUPT_LINES).is_none());
}
}