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.
@@ -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
+