virtual-machine 0.0.0-rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +1 -0
- package/Cargo.toml +48 -0
- package/build/cli.js +904 -0
- package/build/index.d.ts +127 -0
- package/build/index.js +723 -0
- package/build/index.mjs +17 -0
- package/build/riscv_vm-CYH5SWIJ.mjs +672 -0
- package/build.sh +26 -0
- package/cli.ts +251 -0
- package/index.ts +15 -0
- package/package.json +30 -0
- package/src/bus.rs +558 -0
- package/src/clint.rs +132 -0
- package/src/console.rs +83 -0
- package/src/cpu.rs +1913 -0
- package/src/csr.rs +67 -0
- package/src/decoder.rs +789 -0
- package/src/dram.rs +146 -0
- package/src/emulator.rs +603 -0
- package/src/lib.rs +270 -0
- package/src/main.rs +363 -0
- package/src/mmu.rs +331 -0
- package/src/net.rs +121 -0
- package/src/net_tap.rs +164 -0
- package/src/net_webtransport.rs +446 -0
- package/src/net_ws.rs +396 -0
- package/src/plic.rs +261 -0
- package/src/uart.rs +233 -0
- package/src/virtio.rs +1074 -0
- package/tsconfig.json +19 -0
- package/tsup/index.ts +80 -0
- package/tsup/tsup.cli.ts +8 -0
- package/tsup/tsup.core.cjs.ts +7 -0
- package/tsup/tsup.core.esm.ts +8 -0
package/src/emulator.rs
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
use crate::bus::{SystemBus, DRAM_BASE};
|
|
2
|
+
use crate::cpu::Cpu;
|
|
3
|
+
use crate::Trap;
|
|
4
|
+
use goblin::elf::{program_header::PT_LOAD, Elf};
|
|
5
|
+
use std::fs::File;
|
|
6
|
+
use std::io::{Read, Write};
|
|
7
|
+
use std::path::Path;
|
|
8
|
+
|
|
9
|
+
use serde::{Deserialize, Serialize};
|
|
10
|
+
use sha2::{Digest, Sha256};
|
|
11
|
+
use std::collections::HashMap;
|
|
12
|
+
|
|
13
|
+
/// Default DRAM size used when constructing an [`Emulator`] via [`Emulator::new`].
|
|
14
|
+
///
|
|
15
|
+
/// This is large enough for riscv-arch-test binaries and small kernels, while
|
|
16
|
+
/// still being reasonably light for host machines.
|
|
17
|
+
const DEFAULT_DRAM_MIB: usize = 128;
|
|
18
|
+
|
|
19
|
+
/// Default size of the signature region when only a base address is provided.
|
|
20
|
+
///
|
|
21
|
+
/// RISCOF test signatures are typically small; 4 KiB is a conservative
|
|
22
|
+
/// default and can be overridden via [`Emulator::set_signature_region`].
|
|
23
|
+
const DEFAULT_SIGNATURE_SIZE: u64 = 4 * 1024;
|
|
24
|
+
|
|
25
|
+
/// High-level emulator wrapper used by test harnesses (e.g. RISCOF backend).
|
|
26
|
+
///
|
|
27
|
+
/// This mirrors the sketch in `phase-6.md`:
|
|
28
|
+
///
|
|
29
|
+
/// ```ignore
|
|
30
|
+
/// let mut emu = Emulator::new();
|
|
31
|
+
/// emu.load_elf("test.elf")?;
|
|
32
|
+
/// emu.set_signature_addr(0x8001_0000);
|
|
33
|
+
/// while !emu.trapped() { emu.step()?; }
|
|
34
|
+
/// let sig = emu.read_signature()?;
|
|
35
|
+
/// ```
|
|
36
|
+
pub struct Emulator {
|
|
37
|
+
/// CPU core (GPRs, CSRs, privilege mode, TLB, etc).
|
|
38
|
+
pub cpu: Cpu,
|
|
39
|
+
/// System bus with DRAM and all memory-mapped devices.
|
|
40
|
+
pub bus: SystemBus,
|
|
41
|
+
|
|
42
|
+
signature_addr: Option<u64>,
|
|
43
|
+
signature_size: u64,
|
|
44
|
+
|
|
45
|
+
trapped: bool,
|
|
46
|
+
last_trap: Option<Trap>,
|
|
47
|
+
|
|
48
|
+
/// Optional UART output callback invoked once per transmitted byte.
|
|
49
|
+
///
|
|
50
|
+
/// This provides a deterministic, buffered integration point for hosts
|
|
51
|
+
/// (CLI, web UI, tests) without requiring them to poll the UART FIFO.
|
|
52
|
+
uart_callback: Option<Box<dyn FnMut(u8) + 'static>>,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl Emulator {
|
|
56
|
+
/// Create a new emulator instance with the default DRAM size and reset PC.
|
|
57
|
+
///
|
|
58
|
+
/// The reset PC is initialised to the DRAM base (0x8000_0000) but will be
|
|
59
|
+
/// overwritten by [`load_elf`] when an ELF image is loaded.
|
|
60
|
+
pub fn new() -> Self {
|
|
61
|
+
Self::with_memory(DEFAULT_DRAM_MIB * 1024 * 1024)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Create a new emulator instance with an explicit DRAM size in bytes.
|
|
65
|
+
pub fn with_memory(dram_size_bytes: usize) -> Self {
|
|
66
|
+
let dram_base = DRAM_BASE;
|
|
67
|
+
let bus = SystemBus::new(dram_base, dram_size_bytes);
|
|
68
|
+
let cpu = Cpu::new(dram_base);
|
|
69
|
+
|
|
70
|
+
Self {
|
|
71
|
+
cpu,
|
|
72
|
+
bus,
|
|
73
|
+
signature_addr: None,
|
|
74
|
+
signature_size: 0,
|
|
75
|
+
trapped: false,
|
|
76
|
+
last_trap: None,
|
|
77
|
+
uart_callback: None,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Returns `true` once execution has terminated due to a trap or
|
|
82
|
+
/// an explicit host-level stop condition.
|
|
83
|
+
pub fn trapped(&self) -> bool {
|
|
84
|
+
self.trapped
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Returns the last architectural trap observed, if any.
|
|
88
|
+
pub fn last_trap(&self) -> Option<&Trap> {
|
|
89
|
+
self.last_trap.as_ref()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Register a UART output callback.
|
|
93
|
+
///
|
|
94
|
+
/// The callback is invoked from [`step`] for each byte emitted by the
|
|
95
|
+
/// emulated NS16550A UART. Hosts that prefer pull-based I/O can ignore
|
|
96
|
+
/// this and call [`drain_uart_output`] instead.
|
|
97
|
+
pub fn set_uart_callback<F>(&mut self, cb: F)
|
|
98
|
+
where
|
|
99
|
+
F: FnMut(u8) + 'static,
|
|
100
|
+
{
|
|
101
|
+
self.uart_callback = Some(Box::new(cb));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Push a single input byte into the UART RX FIFO.
|
|
105
|
+
///
|
|
106
|
+
/// This models a host keystroke or serial input event in a buffered,
|
|
107
|
+
/// deterministic way: given the same sequence of calls and instruction
|
|
108
|
+
/// stream, the guest will see identical input ordering.
|
|
109
|
+
pub fn push_key(&mut self, byte: u8) {
|
|
110
|
+
self.bus.uart.push_input(byte);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Drain all pending UART output bytes into a vector.
|
|
114
|
+
///
|
|
115
|
+
/// This is useful for tests or hosts that do not wish to use the callback
|
|
116
|
+
/// interface.
|
|
117
|
+
pub fn drain_uart_output(&mut self) -> Vec<u8> {
|
|
118
|
+
let mut out = Vec::new();
|
|
119
|
+
while let Some(b) = self.bus.uart.pop_output() {
|
|
120
|
+
out.push(b);
|
|
121
|
+
}
|
|
122
|
+
out
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Execute a single instruction.
|
|
126
|
+
///
|
|
127
|
+
/// On success, returns `Ok(())`. On architectural traps, this records the
|
|
128
|
+
/// trap in [`last_trap`] and sets [`trapped`] before returning `Err(trap)`.
|
|
129
|
+
pub fn step(&mut self) -> Result<(), Trap> {
|
|
130
|
+
match self.cpu.step(&mut self.bus) {
|
|
131
|
+
Ok(()) => {
|
|
132
|
+
// Deliver UART bytes to host callback if registered.
|
|
133
|
+
if let Some(cb) = self.uart_callback.as_mut() {
|
|
134
|
+
while let Some(byte) = self.bus.uart.pop_output() {
|
|
135
|
+
cb(byte);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Ok(())
|
|
140
|
+
}
|
|
141
|
+
Err(trap) => {
|
|
142
|
+
self.trapped = true;
|
|
143
|
+
self.last_trap = Some(trap.clone());
|
|
144
|
+
Err(trap)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Load an ELF image from disk into DRAM and update the CPU's PC to the
|
|
150
|
+
/// ELF entry point.
|
|
151
|
+
///
|
|
152
|
+
/// Returns the resolved entry PC on success.
|
|
153
|
+
pub fn load_elf<P: AsRef<Path>>(
|
|
154
|
+
&mut self,
|
|
155
|
+
path: P,
|
|
156
|
+
) -> Result<u64, Box<dyn std::error::Error>> {
|
|
157
|
+
let mut file = File::open(path)?;
|
|
158
|
+
let mut buffer = Vec::new();
|
|
159
|
+
file.read_to_end(&mut buffer)?;
|
|
160
|
+
|
|
161
|
+
let entry_pc = load_elf_into_dram(&buffer, &mut self.bus)?;
|
|
162
|
+
self.cpu.pc = entry_pc;
|
|
163
|
+
Ok(entry_pc)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Configure the signature region used by `read_signature`.
|
|
167
|
+
///
|
|
168
|
+
/// - `base` is the physical start address of the signature buffer.
|
|
169
|
+
/// - `size` is the number of bytes to read.
|
|
170
|
+
pub fn set_signature_region(&mut self, base: u64, size: u64) {
|
|
171
|
+
self.signature_addr = Some(base);
|
|
172
|
+
self.signature_size = size;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Convenience helper matching the `phase-6.md` sketch.
|
|
176
|
+
///
|
|
177
|
+
/// This sets the base address and uses a default size of 4 KiB unless a
|
|
178
|
+
/// region size has already been configured via [`set_signature_region`].
|
|
179
|
+
pub fn set_signature_addr(&mut self, base: u64) {
|
|
180
|
+
self.signature_addr = Some(base);
|
|
181
|
+
if self.signature_size == 0 {
|
|
182
|
+
self.signature_size = DEFAULT_SIGNATURE_SIZE;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Read the configured signature region from DRAM.
|
|
187
|
+
///
|
|
188
|
+
/// Returns an owned `Vec<u8>` which callers can hex-encode or compare
|
|
189
|
+
/// against reference signatures.
|
|
190
|
+
pub fn read_signature(&self) -> Result<Vec<u8>, String> {
|
|
191
|
+
let base = self
|
|
192
|
+
.signature_addr
|
|
193
|
+
.ok_or_else(|| "signature address not configured".to_string())?;
|
|
194
|
+
if self.signature_size == 0 {
|
|
195
|
+
return Err("signature size is zero; call set_signature_region first".to_string());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let dram_base = self.bus.dram_base();
|
|
199
|
+
let dram_size = self.bus.dram_size() as u64;
|
|
200
|
+
|
|
201
|
+
if base < dram_base || base >= dram_base + dram_size {
|
|
202
|
+
return Err(format!(
|
|
203
|
+
"signature base 0x{base:016x} lies outside DRAM (0x{dram_base:016x}..0x{:016x})",
|
|
204
|
+
dram_base + dram_size
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let offset = (base - dram_base) as usize;
|
|
209
|
+
let end = offset
|
|
210
|
+
.checked_add(self.signature_size as usize)
|
|
211
|
+
.ok_or_else(|| "signature range overflow".to_string())?;
|
|
212
|
+
|
|
213
|
+
if end > self.bus.dram_size() {
|
|
214
|
+
return Err("signature range extends beyond DRAM".to_string());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// SAFETY: bounds checked above.
|
|
218
|
+
Ok(self.bus.dram.data[offset..end].to_vec())
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const SNAPSHOT_VERSION: &str = "2.0";
|
|
223
|
+
|
|
224
|
+
/// Serializable CPU state used in snapshots.
|
|
225
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
226
|
+
pub struct CpuSnapshot {
|
|
227
|
+
pub pc: u64,
|
|
228
|
+
pub mode: crate::csr::Mode,
|
|
229
|
+
pub regs: [u64; 32],
|
|
230
|
+
pub csrs: HashMap<u16, u64>,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/// Serializable CLINT state.
|
|
234
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
235
|
+
pub struct ClintSnapshot {
|
|
236
|
+
pub msip: [u32; crate::clint::MAX_HARTS],
|
|
237
|
+
pub mtime: u64,
|
|
238
|
+
pub mtimecmp: [u64; crate::clint::MAX_HARTS],
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Serializable PLIC state.
|
|
242
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
243
|
+
pub struct PlicSnapshot {
|
|
244
|
+
pub priority: Vec<u32>,
|
|
245
|
+
pub pending: u32,
|
|
246
|
+
pub enable: Vec<u32>,
|
|
247
|
+
pub threshold: Vec<u32>,
|
|
248
|
+
pub active: Vec<u32>,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Serializable UART state.
|
|
252
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
253
|
+
pub struct UartSnapshot {
|
|
254
|
+
pub rx_fifo: Vec<u8>,
|
|
255
|
+
pub tx_fifo: Vec<u8>,
|
|
256
|
+
pub ier: u8,
|
|
257
|
+
pub iir: u8,
|
|
258
|
+
pub fcr: u8,
|
|
259
|
+
pub lcr: u8,
|
|
260
|
+
pub mcr: u8,
|
|
261
|
+
pub lsr: u8,
|
|
262
|
+
pub msr: u8,
|
|
263
|
+
pub scr: u8,
|
|
264
|
+
pub dll: u8,
|
|
265
|
+
pub dlm: u8,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// Serializable device state bundle.
|
|
269
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
270
|
+
pub struct DeviceSnapshot {
|
|
271
|
+
pub clint: ClintSnapshot,
|
|
272
|
+
pub plic: PlicSnapshot,
|
|
273
|
+
pub uart: UartSnapshot,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Memory region snapshot (currently we only snapshot DRAM as a single region).
|
|
277
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
278
|
+
pub struct MemRegionSnapshot {
|
|
279
|
+
pub base: u64,
|
|
280
|
+
pub size: u64,
|
|
281
|
+
pub hash: String,
|
|
282
|
+
pub data: Option<Vec<u8>>,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Full emulator snapshot including CPU, devices and DRAM.
|
|
286
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
287
|
+
pub struct Snapshot {
|
|
288
|
+
pub version: String,
|
|
289
|
+
pub cpu: CpuSnapshot,
|
|
290
|
+
pub devices: DeviceSnapshot,
|
|
291
|
+
pub memory: Vec<MemRegionSnapshot>,
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
impl Emulator {
|
|
295
|
+
/// Capture a complete, deterministic snapshot of the current emulator state.
|
|
296
|
+
pub fn snapshot(&self) -> Snapshot {
|
|
297
|
+
let cpu = CpuSnapshot {
|
|
298
|
+
pc: self.cpu.pc,
|
|
299
|
+
mode: self.cpu.mode,
|
|
300
|
+
regs: self.cpu.regs,
|
|
301
|
+
csrs: self.cpu.export_csrs(),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let clint = ClintSnapshot {
|
|
305
|
+
msip: self.bus.clint.msip,
|
|
306
|
+
mtime: self.bus.clint.mtime,
|
|
307
|
+
mtimecmp: self.bus.clint.mtimecmp,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
let plic = PlicSnapshot {
|
|
311
|
+
priority: self.bus.plic.priority.to_vec(),
|
|
312
|
+
pending: self.bus.plic.pending,
|
|
313
|
+
enable: self.bus.plic.enable.to_vec(),
|
|
314
|
+
threshold: self.bus.plic.threshold.to_vec(),
|
|
315
|
+
active: self.bus.plic.active.to_vec(),
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
let uart = UartSnapshot {
|
|
319
|
+
rx_fifo: self.bus.uart.input.iter().copied().collect(),
|
|
320
|
+
tx_fifo: self.bus.uart.output.iter().copied().collect(),
|
|
321
|
+
ier: self.bus.uart.ier,
|
|
322
|
+
iir: self.bus.uart.iir,
|
|
323
|
+
fcr: self.bus.uart.fcr,
|
|
324
|
+
lcr: self.bus.uart.lcr,
|
|
325
|
+
mcr: self.bus.uart.mcr,
|
|
326
|
+
lsr: self.bus.uart.lsr,
|
|
327
|
+
msr: self.bus.uart.msr,
|
|
328
|
+
scr: self.bus.uart.scr,
|
|
329
|
+
dll: self.bus.uart.dll,
|
|
330
|
+
dlm: self.bus.uart.dlm,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
let mut hasher = Sha256::new();
|
|
334
|
+
hasher.update(&self.bus.dram.data);
|
|
335
|
+
let hash = hex::encode(hasher.finalize());
|
|
336
|
+
|
|
337
|
+
let region = MemRegionSnapshot {
|
|
338
|
+
base: self.bus.dram.base,
|
|
339
|
+
size: self.bus.dram.data.len() as u64,
|
|
340
|
+
hash,
|
|
341
|
+
data: Some(self.bus.dram.data.clone()),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
Snapshot {
|
|
345
|
+
version: SNAPSHOT_VERSION.to_string(),
|
|
346
|
+
cpu,
|
|
347
|
+
devices: DeviceSnapshot { clint, plic, uart },
|
|
348
|
+
memory: vec![region],
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// Restore emulator state from a previously captured snapshot.
|
|
353
|
+
pub fn apply_snapshot(&mut self, snapshot: &Snapshot) -> Result<(), String> {
|
|
354
|
+
if snapshot.version != SNAPSHOT_VERSION {
|
|
355
|
+
return Err(format!(
|
|
356
|
+
"snapshot version mismatch: expected {}, found {}",
|
|
357
|
+
SNAPSHOT_VERSION, snapshot.version
|
|
358
|
+
));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Restore CPU core.
|
|
362
|
+
self.cpu.pc = snapshot.cpu.pc;
|
|
363
|
+
self.cpu.mode = snapshot.cpu.mode;
|
|
364
|
+
self.cpu.regs = snapshot.cpu.regs;
|
|
365
|
+
self.cpu.import_csrs(&snapshot.cpu.csrs);
|
|
366
|
+
self.trapped = false;
|
|
367
|
+
self.last_trap = None;
|
|
368
|
+
|
|
369
|
+
// Restore CLINT.
|
|
370
|
+
self.bus.clint.msip = snapshot.devices.clint.msip;
|
|
371
|
+
self.bus.clint.set_mtime(snapshot.devices.clint.mtime);
|
|
372
|
+
self.bus.clint.mtimecmp = snapshot.devices.clint.mtimecmp;
|
|
373
|
+
|
|
374
|
+
// Restore PLIC (truncate if snapshot has more sources/contexts).
|
|
375
|
+
for (i, &val) in snapshot.devices.plic.priority.iter().enumerate() {
|
|
376
|
+
if i < self.bus.plic.priority.len() {
|
|
377
|
+
self.bus.plic.priority[i] = val;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
self.bus.plic.pending = snapshot.devices.plic.pending;
|
|
381
|
+
for (i, &val) in snapshot.devices.plic.enable.iter().enumerate() {
|
|
382
|
+
if i < self.bus.plic.enable.len() {
|
|
383
|
+
self.bus.plic.enable[i] = val;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (i, &val) in snapshot.devices.plic.threshold.iter().enumerate() {
|
|
387
|
+
if i < self.bus.plic.threshold.len() {
|
|
388
|
+
self.bus.plic.threshold[i] = val;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
for (i, &val) in snapshot.devices.plic.active.iter().enumerate() {
|
|
392
|
+
if i < self.bus.plic.active.len() {
|
|
393
|
+
self.bus.plic.active[i] = val;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Restore UART.
|
|
398
|
+
self.bus.uart.input.clear();
|
|
399
|
+
self.bus.uart.input.extend(snapshot.devices.uart.rx_fifo.iter().copied());
|
|
400
|
+
self.bus.uart.output.clear();
|
|
401
|
+
self.bus.uart.output.extend(snapshot.devices.uart.tx_fifo.iter().copied());
|
|
402
|
+
self.bus.uart.ier = snapshot.devices.uart.ier;
|
|
403
|
+
self.bus.uart.iir = snapshot.devices.uart.iir;
|
|
404
|
+
self.bus.uart.fcr = snapshot.devices.uart.fcr;
|
|
405
|
+
self.bus.uart.lcr = snapshot.devices.uart.lcr;
|
|
406
|
+
self.bus.uart.mcr = snapshot.devices.uart.mcr;
|
|
407
|
+
self.bus.uart.lsr = snapshot.devices.uart.lsr;
|
|
408
|
+
self.bus.uart.msr = snapshot.devices.uart.msr;
|
|
409
|
+
self.bus.uart.scr = snapshot.devices.uart.scr;
|
|
410
|
+
self.bus.uart.dll = snapshot.devices.uart.dll;
|
|
411
|
+
self.bus.uart.dlm = snapshot.devices.uart.dlm;
|
|
412
|
+
self.bus.uart.update_interrupts();
|
|
413
|
+
|
|
414
|
+
// Restore DRAM.
|
|
415
|
+
let region = snapshot
|
|
416
|
+
.memory
|
|
417
|
+
.get(0)
|
|
418
|
+
.ok_or_else(|| "snapshot missing primary memory region".to_string())?;
|
|
419
|
+
|
|
420
|
+
let data = region
|
|
421
|
+
.data
|
|
422
|
+
.as_ref()
|
|
423
|
+
.ok_or_else(|| "snapshot memory region has no inline data".to_string())?;
|
|
424
|
+
|
|
425
|
+
if self.bus.dram.base != region.base {
|
|
426
|
+
return Err(format!(
|
|
427
|
+
"snapshot DRAM base mismatch: emulator=0x{:x}, snapshot=0x{:x}",
|
|
428
|
+
self.bus.dram.base, region.base
|
|
429
|
+
));
|
|
430
|
+
}
|
|
431
|
+
if self.bus.dram.data.len() != data.len() {
|
|
432
|
+
return Err(format!(
|
|
433
|
+
"snapshot DRAM size mismatch: emulator={} bytes, snapshot={} bytes",
|
|
434
|
+
self.bus.dram.data.len(),
|
|
435
|
+
data.len()
|
|
436
|
+
));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let mut hasher = Sha256::new();
|
|
440
|
+
hasher.update(data);
|
|
441
|
+
let current_hash = hex::encode(hasher.finalize());
|
|
442
|
+
if current_hash != region.hash {
|
|
443
|
+
return Err(format!(
|
|
444
|
+
"snapshot DRAM hash mismatch for base 0x{:x}",
|
|
445
|
+
region.base
|
|
446
|
+
));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
self.bus.dram.data.clone_from_slice(data);
|
|
450
|
+
|
|
451
|
+
Ok(())
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/// Construct a new emulator instance from a snapshot.
|
|
455
|
+
pub fn from_snapshot(snapshot: Snapshot) -> Result<Self, String> {
|
|
456
|
+
let region = snapshot
|
|
457
|
+
.memory
|
|
458
|
+
.get(0)
|
|
459
|
+
.ok_or_else(|| "snapshot missing primary memory region".to_string())?;
|
|
460
|
+
let dram_size = region
|
|
461
|
+
.size
|
|
462
|
+
.try_into()
|
|
463
|
+
.map_err(|_| "snapshot DRAM size does not fit in usize".to_string())?;
|
|
464
|
+
|
|
465
|
+
let mut emu = Emulator::with_memory(dram_size);
|
|
466
|
+
emu.apply_snapshot(&snapshot)?;
|
|
467
|
+
Ok(emu)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// Save a snapshot to disk using bincode.
|
|
471
|
+
pub fn save_snapshot_to_path<P: AsRef<Path>>(
|
|
472
|
+
&self,
|
|
473
|
+
path: P,
|
|
474
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
475
|
+
let snap = self.snapshot();
|
|
476
|
+
let mut file = File::create(path)?;
|
|
477
|
+
bincode::serialize_into(&mut file, &snap)?;
|
|
478
|
+
file.flush()?;
|
|
479
|
+
Ok(())
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/// Load a snapshot from disk and construct a new emulator instance.
|
|
483
|
+
pub fn load_snapshot_from_path<P: AsRef<Path>>(
|
|
484
|
+
path: P,
|
|
485
|
+
) -> Result<Self, Box<dyn std::error::Error>> {
|
|
486
|
+
let mut file = File::open(path)?;
|
|
487
|
+
let snapshot: Snapshot = bincode::deserialize_from(&mut file)?;
|
|
488
|
+
let emu = Emulator::from_snapshot(snapshot)
|
|
489
|
+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
|
490
|
+
Ok(emu)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
#[cfg(test)]
|
|
495
|
+
mod tests {
|
|
496
|
+
use super::*;
|
|
497
|
+
use crate::bus::Bus;
|
|
498
|
+
|
|
499
|
+
#[test]
|
|
500
|
+
fn snapshot_roundtrip_preserves_state() {
|
|
501
|
+
let mut emu = Emulator::with_memory(1024 * 1024);
|
|
502
|
+
|
|
503
|
+
// Simple CPU state.
|
|
504
|
+
emu.cpu.pc = DRAM_BASE + 0x1000;
|
|
505
|
+
emu.cpu.write_reg(crate::decoder::Register::X5, 0xdead_beef_dead_beef);
|
|
506
|
+
|
|
507
|
+
// Touch DRAM and devices.
|
|
508
|
+
let addr = emu.bus.dram_base() + 0x80;
|
|
509
|
+
emu.bus.write64(addr, 0x0123_4567_89ab_cdef).unwrap();
|
|
510
|
+
emu.bus.clint.mtime = 1234;
|
|
511
|
+
emu.bus.clint.mtimecmp[0] = 5678;
|
|
512
|
+
emu.bus.uart.push_input(b'A');
|
|
513
|
+
|
|
514
|
+
let snap = emu.snapshot();
|
|
515
|
+
let bytes = bincode::serialize(&snap).unwrap();
|
|
516
|
+
let snap2: Snapshot = bincode::deserialize(&bytes).unwrap();
|
|
517
|
+
|
|
518
|
+
let emu2 = Emulator::from_snapshot(snap2).unwrap();
|
|
519
|
+
|
|
520
|
+
assert_eq!(emu.cpu.pc, emu2.cpu.pc);
|
|
521
|
+
assert_eq!(
|
|
522
|
+
emu.cpu.read_reg(crate::decoder::Register::X5),
|
|
523
|
+
emu2.cpu.read_reg(crate::decoder::Register::X5)
|
|
524
|
+
);
|
|
525
|
+
assert_eq!(emu.bus.dram.data, emu2.bus.dram.data);
|
|
526
|
+
assert_eq!(emu.bus.clint.mtime, emu2.bus.clint.mtime);
|
|
527
|
+
assert_eq!(emu.bus.clint.mtimecmp, emu2.bus.clint.mtimecmp);
|
|
528
|
+
assert_eq!(emu.bus.uart.input, emu2.bus.uart.input);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
fn load_elf_into_dram(
|
|
533
|
+
buffer: &[u8],
|
|
534
|
+
bus: &mut SystemBus,
|
|
535
|
+
) -> Result<u64, Box<dyn std::error::Error>> {
|
|
536
|
+
let elf = Elf::parse(buffer)?;
|
|
537
|
+
let base = bus.dram_base();
|
|
538
|
+
let dram_end = base + bus.dram_size() as u64;
|
|
539
|
+
|
|
540
|
+
for ph in &elf.program_headers {
|
|
541
|
+
if ph.p_type != PT_LOAD || ph.p_memsz == 0 {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let file_size = ph.p_filesz as usize;
|
|
546
|
+
let mem_size = ph.p_memsz as usize;
|
|
547
|
+
let file_offset = ph.p_offset as usize;
|
|
548
|
+
if file_offset + file_size > buffer.len() {
|
|
549
|
+
return Err(format!(
|
|
550
|
+
"ELF segment exceeds file bounds (offset 0x{:x})",
|
|
551
|
+
file_offset
|
|
552
|
+
)
|
|
553
|
+
.into());
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let target_addr = if ph.p_paddr != 0 {
|
|
557
|
+
ph.p_paddr
|
|
558
|
+
} else {
|
|
559
|
+
ph.p_vaddr
|
|
560
|
+
};
|
|
561
|
+
if target_addr < base {
|
|
562
|
+
return Err(format!(
|
|
563
|
+
"Segment start 0x{:x} lies below DRAM base 0x{:x}",
|
|
564
|
+
target_addr, base
|
|
565
|
+
)
|
|
566
|
+
.into());
|
|
567
|
+
}
|
|
568
|
+
let seg_end = target_addr
|
|
569
|
+
.checked_add(mem_size as u64)
|
|
570
|
+
.ok_or_else(|| "Segment end overflow".to_string())?;
|
|
571
|
+
if seg_end > dram_end {
|
|
572
|
+
return Err(format!(
|
|
573
|
+
"Segment 0x{:x}-0x{:x} exceeds DRAM (end 0x{:x})",
|
|
574
|
+
target_addr, seg_end, dram_end
|
|
575
|
+
)
|
|
576
|
+
.into());
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
let dram_offset = (target_addr - base) as u64;
|
|
580
|
+
if file_size > 0 {
|
|
581
|
+
let end = file_offset + file_size;
|
|
582
|
+
bus.dram
|
|
583
|
+
.load(&buffer[file_offset..end], dram_offset)
|
|
584
|
+
.map_err(|e| format!("Failed to load segment: {}", e))?;
|
|
585
|
+
}
|
|
586
|
+
if mem_size > file_size {
|
|
587
|
+
let zero_start = dram_offset as usize + file_size;
|
|
588
|
+
bus.dram
|
|
589
|
+
.zero_range(zero_start, mem_size - file_size)
|
|
590
|
+
.map_err(|e| format!("Failed to zero bss: {}", e))?;
|
|
591
|
+
}
|
|
592
|
+
log::debug!(
|
|
593
|
+
"Loaded segment: addr=0x{:x}, filesz=0x{:x}, memsz=0x{:x}",
|
|
594
|
+
target_addr,
|
|
595
|
+
file_size,
|
|
596
|
+
mem_size
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
Ok(elf.entry)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
|