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/src/lib.rs ADDED
@@ -0,0 +1,270 @@
1
+ pub mod bus;
2
+ pub mod cpu;
3
+ pub mod decoder;
4
+ pub mod csr;
5
+ pub mod mmu;
6
+ pub mod dram;
7
+ pub mod clint;
8
+ pub mod plic;
9
+ pub mod uart;
10
+ pub mod net;
11
+ pub mod net_ws;
12
+ pub mod net_webtransport;
13
+ pub mod virtio;
14
+ pub mod emulator;
15
+
16
+ #[cfg(not(target_arch = "wasm32"))]
17
+ pub mod console;
18
+
19
+ #[cfg(not(target_arch = "wasm32"))]
20
+ pub mod net_tap;
21
+
22
+ use serde::{Deserialize, Serialize};
23
+
24
+ // WASM bindings
25
+ #[cfg(target_arch = "wasm32")]
26
+ use wasm_bindgen::prelude::*;
27
+
28
+ #[cfg(target_arch = "wasm32")]
29
+ use crate::bus::{SystemBus, DRAM_BASE};
30
+
31
+ /// Network connection status for the WASM VM.
32
+ #[cfg(target_arch = "wasm32")]
33
+ #[wasm_bindgen]
34
+ #[derive(Clone, Copy, PartialEq, Eq)]
35
+ pub enum NetworkStatus {
36
+ Disconnected = 0,
37
+ Connecting = 1,
38
+ Connected = 2,
39
+ Error = 3,
40
+ }
41
+
42
+ /// WASM-exposed VM wrapper for running RISC-V kernels in the browser.
43
+ #[cfg(target_arch = "wasm32")]
44
+ #[wasm_bindgen]
45
+ pub struct WasmVm {
46
+ bus: SystemBus,
47
+ cpu: cpu::Cpu,
48
+ net_status: NetworkStatus,
49
+ poll_counter: u32,
50
+ }
51
+
52
+ #[cfg(target_arch = "wasm32")]
53
+ #[wasm_bindgen]
54
+ impl WasmVm {
55
+ /// Create a new VM instance and load a kernel (ELF or raw binary).
56
+ #[wasm_bindgen(constructor)]
57
+ pub fn new(kernel: &[u8]) -> Result<WasmVm, JsValue> {
58
+ // Set up panic hook for better error messages in the browser console
59
+ console_error_panic_hook::set_once();
60
+
61
+ const DRAM_SIZE: usize = 512 * 1024 * 1024; // 512 MiB
62
+ let mut bus = SystemBus::new(DRAM_BASE, DRAM_SIZE);
63
+
64
+ // Check if it's an ELF file and load appropriately
65
+ let entry_pc = if kernel.starts_with(b"\x7FELF") {
66
+ // Parse and load ELF
67
+ load_elf_wasm(kernel, &mut bus)
68
+ .map_err(|e| JsValue::from_str(&format!("Failed to load ELF kernel: {}", e)))?
69
+ } else {
70
+ // Load raw binary at DRAM_BASE
71
+ bus.dram
72
+ .load(kernel, 0)
73
+ .map_err(|e| JsValue::from_str(&format!("Failed to load kernel: {}", e)))?;
74
+ DRAM_BASE
75
+ };
76
+
77
+ let cpu = cpu::Cpu::new(entry_pc);
78
+
79
+ Ok(WasmVm {
80
+ bus,
81
+ cpu,
82
+ net_status: NetworkStatus::Disconnected,
83
+ poll_counter: 0,
84
+ })
85
+ }
86
+
87
+ /// Load a disk image and attach it as a VirtIO block device.
88
+ /// This should be called before starting execution if the kernel needs a filesystem.
89
+ pub fn load_disk(&mut self, disk_image: &[u8]) {
90
+ let vblk = virtio::VirtioBlock::new(disk_image.to_vec());
91
+ self.bus.virtio_devices.push(Box::new(vblk));
92
+ }
93
+
94
+ /// Connect to a WebSocket relay server for networking.
95
+ /// The URL should be like "ws://localhost:8765".
96
+ pub fn connect_network(&mut self, ws_url: &str) -> Result<(), JsValue> {
97
+ use crate::net_ws::WsBackend;
98
+ use crate::virtio::VirtioNet;
99
+
100
+ self.net_status = NetworkStatus::Connecting;
101
+
102
+ let backend = WsBackend::new(ws_url);
103
+ let mut vnet = VirtioNet::new(Box::new(backend));
104
+ vnet.debug = false; // Set to true for debugging
105
+
106
+ self.bus.virtio_devices.push(Box::new(vnet));
107
+ self.net_status = NetworkStatus::Connected;
108
+
109
+ Ok(())
110
+ }
111
+
112
+ /// Connect to a WebTransport relay server.
113
+ pub fn connect_webtransport(&mut self, url: &str, cert_hash: Option<String>) -> Result<(), JsValue> {
114
+ use crate::net_webtransport::WebTransportBackend;
115
+ use crate::virtio::VirtioNet;
116
+
117
+ self.net_status = NetworkStatus::Connecting;
118
+
119
+ let backend = WebTransportBackend::new(url, cert_hash);
120
+ // Note: WebTransport connect is async, so backend.init() will start connection
121
+ // but actual connection happens in background.
122
+ let mut vnet = VirtioNet::new(Box::new(backend));
123
+ vnet.debug = false;
124
+
125
+ self.bus.virtio_devices.push(Box::new(vnet));
126
+ self.net_status = NetworkStatus::Connected;
127
+
128
+ Ok(())
129
+ }
130
+
131
+ /// Disconnect from the network.
132
+ pub fn disconnect_network(&mut self) {
133
+ // Remove VirtioNet devices (device_id == 1)
134
+ self.bus.virtio_devices.retain(|dev| dev.device_id() != 1);
135
+ self.net_status = NetworkStatus::Disconnected;
136
+ }
137
+
138
+ /// Get the current network connection status.
139
+ pub fn network_status(&self) -> NetworkStatus {
140
+ self.net_status
141
+ }
142
+
143
+ /// Execute a single instruction.
144
+ pub fn step(&mut self) {
145
+ // Poll VirtIO devices periodically for incoming network packets
146
+ // Poll every 100 instructions for good network responsiveness
147
+ self.poll_counter = self.poll_counter.wrapping_add(1);
148
+ if self.poll_counter % 100 == 0 {
149
+ self.bus.poll_virtio();
150
+ }
151
+
152
+ // Ignore traps for now - the kernel handles them
153
+ let _ = self.cpu.step(&mut self.bus);
154
+ }
155
+
156
+ /// Get a byte from the UART output buffer, if available.
157
+ pub fn get_output(&mut self) -> Option<u8> {
158
+ self.bus.uart.pop_output()
159
+ }
160
+
161
+ /// Push an input byte to the UART.
162
+ pub fn input(&mut self, byte: u8) {
163
+ self.bus.uart.push_input(byte);
164
+ }
165
+
166
+ /// Get current memory usage (DRAM size) in bytes.
167
+ pub fn get_memory_usage(&self) -> u64 {
168
+ self.bus.dram.data.len() as u64
169
+ }
170
+ }
171
+
172
+ /// Load an ELF kernel into DRAM (WASM-compatible version).
173
+ #[cfg(target_arch = "wasm32")]
174
+ fn load_elf_wasm(buffer: &[u8], bus: &mut SystemBus) -> Result<u64, String> {
175
+ use goblin::elf::{program_header::PT_LOAD, Elf};
176
+
177
+ let elf = Elf::parse(buffer).map_err(|e| format!("ELF parse error: {}", e))?;
178
+ let base = bus.dram_base();
179
+ let dram_end = base + bus.dram_size() as u64;
180
+
181
+ for ph in &elf.program_headers {
182
+ if ph.p_type != PT_LOAD || ph.p_memsz == 0 {
183
+ continue;
184
+ }
185
+
186
+ let file_size = ph.p_filesz as usize;
187
+ let mem_size = ph.p_memsz as usize;
188
+ let file_offset = ph.p_offset as usize;
189
+ if file_offset + file_size > buffer.len() {
190
+ return Err(format!(
191
+ "ELF segment exceeds file bounds (offset 0x{:x})",
192
+ file_offset
193
+ ));
194
+ }
195
+
196
+ let target_addr = if ph.p_paddr != 0 {
197
+ ph.p_paddr
198
+ } else {
199
+ ph.p_vaddr
200
+ };
201
+ if target_addr < base {
202
+ return Err(format!(
203
+ "Segment start 0x{:x} lies below DRAM base 0x{:x}",
204
+ target_addr, base
205
+ ));
206
+ }
207
+ let seg_end = target_addr
208
+ .checked_add(mem_size as u64)
209
+ .ok_or_else(|| "Segment end overflow".to_string())?;
210
+ if seg_end > dram_end {
211
+ return Err(format!(
212
+ "Segment 0x{:x}-0x{:x} exceeds DRAM (end 0x{:x})",
213
+ target_addr, seg_end, dram_end
214
+ ));
215
+ }
216
+
217
+ let dram_offset = (target_addr - base) as u64;
218
+ if file_size > 0 {
219
+ let end = file_offset + file_size;
220
+ bus.dram
221
+ .load(&buffer[file_offset..end], dram_offset)
222
+ .map_err(|e| format!("Failed to load segment: {}", e))?;
223
+ }
224
+ if mem_size > file_size {
225
+ let zero_start = dram_offset as usize + file_size;
226
+ bus.dram
227
+ .zero_range(zero_start, mem_size - file_size)
228
+ .map_err(|e| format!("Failed to zero bss: {}", e))?;
229
+ }
230
+ }
231
+
232
+ Ok(elf.entry)
233
+ }
234
+
235
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236
+ pub enum Trap {
237
+ InstructionAddressMisaligned(u64),
238
+ InstructionAccessFault(u64),
239
+ IllegalInstruction(u64),
240
+ Breakpoint,
241
+ LoadAddressMisaligned(u64),
242
+ LoadAccessFault(u64),
243
+ StoreAddressMisaligned(u64),
244
+ StoreAccessFault(u64),
245
+ EnvironmentCallFromU,
246
+ EnvironmentCallFromS,
247
+ EnvironmentCallFromM,
248
+ InstructionPageFault(u64),
249
+ LoadPageFault(u64),
250
+ StorePageFault(u64),
251
+
252
+ MachineSoftwareInterrupt,
253
+ MachineTimerInterrupt,
254
+ MachineExternalInterrupt,
255
+ SupervisorSoftwareInterrupt,
256
+ SupervisorTimerInterrupt,
257
+ SupervisorExternalInterrupt,
258
+
259
+ // Custom internal errors
260
+ RequestedTrap(u64), // For testing (software interrupts, etc)
261
+ Fatal(String),
262
+ }
263
+
264
+ impl std::fmt::Display for Trap {
265
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266
+ write!(f, "{:?}", self)
267
+ }
268
+ }
269
+
270
+ impl std::error::Error for Trap {}
package/src/main.rs ADDED
@@ -0,0 +1,363 @@
1
+ use clap::Parser;
2
+ use goblin::elf::{program_header::PT_LOAD, Elf};
3
+ use riscv_vm::bus::{Bus, SystemBus};
4
+ use riscv_vm::cpu::Cpu;
5
+ use riscv_vm::Trap;
6
+ use riscv_vm::csr::{CSR_MCAUSE, CSR_MEPC, CSR_MTVAL, CSR_MTVEC, CSR_SCAUSE, CSR_SEPC, CSR_STVAL, CSR_STVEC};
7
+ use std::fs::File;
8
+ use std::io::Read;
9
+ use std::path::PathBuf;
10
+
11
+ use riscv_vm::console::Console;
12
+
13
+ #[derive(Parser, Debug)]
14
+ #[command(author, version, about, long_about = None)]
15
+ struct Args {
16
+ /// Path to binary to load
17
+ #[arg(short, long)]
18
+ kernel: PathBuf,
19
+
20
+ /// Address to load kernel at (default 0x8000_0000)
21
+ #[arg(long, default_value_t = 0x8000_0000)]
22
+ load_addr: u64,
23
+
24
+ /// DRAM size in MiB
25
+ #[arg(long, default_value_t = 512)]
26
+ mem_mib: usize,
27
+
28
+ /// Optional path to a VirtIO Block disk image (e.g. xv6 fs.img)
29
+ #[arg(long)]
30
+ disk: Option<PathBuf>,
31
+
32
+ /// Optional TAP interface name for VirtIO network device (e.g. tap0)
33
+ /// Requires the interface to exist: sudo ip tuntap add dev tap0 mode tap
34
+ #[arg(long)]
35
+ net_tap: Option<String>,
36
+
37
+ /// Enable VirtIO network device with a dummy backend (for testing, no actual packets)
38
+ #[arg(long)]
39
+ net_dummy: bool,
40
+
41
+ /// Connect to a WebSocket server for networking (e.g. ws://localhost:8765)
42
+ /// Works on macOS and in browser/WASM
43
+ #[arg(long)]
44
+ net_ws: Option<String>,
45
+
46
+ /// Connect to a WebTransport relay for networking (e.g. https://127.0.0.1:4433)
47
+ /// Supports NAT traversal and peer-to-peer connections
48
+ #[arg(long)]
49
+ net_webtransport: Option<String>,
50
+
51
+ /// Certificate hash for WebTransport (hex string)
52
+ /// Required for self-signed certificates
53
+ #[arg(long)]
54
+ net_cert_hash: Option<String>,
55
+ }
56
+
57
+ // Debug helper: dump VirtIO MMIO identity registers expected by xv6.
58
+ fn dump_virtio_id(bus: &mut SystemBus) {
59
+ const VIRTIO0_BASE: u64 = 0x1000_1000;
60
+ fn r32(bus: &mut SystemBus, off: u64) -> u32 {
61
+ bus.read32(VIRTIO0_BASE + off).unwrap_or(0)
62
+ }
63
+ let magic = r32(bus, 0x000);
64
+ let ver = r32(bus, 0x004);
65
+ let devid = r32(bus, 0x008);
66
+ let vendor = r32(bus, 0x00c);
67
+ eprintln!(
68
+ "VirtIO ID: MAGIC=0x{:08x} VERSION={} DEVICE_ID={} VENDOR=0x{:08x}",
69
+ magic, ver, devid, vendor
70
+ );
71
+ }
72
+
73
+ fn main() -> Result<(), Box<dyn std::error::Error>> {
74
+ env_logger::init();
75
+ let args = Args::parse();
76
+
77
+ let mut file = File::open(&args.kernel)?;
78
+ let mut buffer = Vec::new();
79
+ file.read_to_end(&mut buffer)?;
80
+
81
+ let dram_size_bytes = args
82
+ .mem_mib
83
+ .checked_mul(1024 * 1024)
84
+ .ok_or("Requested memory size is too large")?;
85
+
86
+ // Initialize DRAM at 0x8000_0000
87
+ let dram_base = 0x8000_0000;
88
+ let mut bus = SystemBus::new(dram_base, dram_size_bytes);
89
+
90
+ // If a disk image is provided, wire up VirtIO Block at 0x1000_1000
91
+ if let Some(disk_path) = &args.disk {
92
+ let mut disk_file = File::open(disk_path)?;
93
+ let mut disk_buf = Vec::new();
94
+ disk_file.read_to_end(&mut disk_buf)?;
95
+ let vblk = riscv_vm::virtio::VirtioBlock::new(disk_buf);
96
+ bus.virtio_devices.push(Box::new(vblk));
97
+ println!("VirtIO Block device attached at 0x1000_1000 (IRQ 1)");
98
+ }
99
+
100
+ // If a TAP interface is provided, wire up VirtIO Net with TAP backend
101
+ if let Some(tap_name) = &args.net_tap {
102
+ let tap_backend = riscv_vm::net_tap::TapBackend::new(tap_name);
103
+ let vnet = riscv_vm::virtio::VirtioNet::new(Box::new(tap_backend));
104
+ let device_idx = bus.virtio_devices.len();
105
+ let irq = 1 + device_idx; // IRQ 1 for first device, 2 for second, etc.
106
+ bus.virtio_devices.push(Box::new(vnet));
107
+ let base_addr = 0x1000_1000 + (device_idx as u64) * 0x1000;
108
+ println!("VirtIO Net device (TAP: {}) attached at 0x{:x} (IRQ {})", tap_name, base_addr, irq);
109
+ } else if let Some(ws_url) = &args.net_ws {
110
+ // Wire up VirtIO Net with WebSocket backend
111
+ let ws_backend = riscv_vm::net_ws::WsBackend::new(ws_url);
112
+ let vnet = riscv_vm::virtio::VirtioNet::new(Box::new(ws_backend));
113
+ let device_idx = bus.virtio_devices.len();
114
+ let irq = 1 + device_idx;
115
+ bus.virtio_devices.push(Box::new(vnet));
116
+ let base_addr = 0x1000_1000 + (device_idx as u64) * 0x1000;
117
+ println!("VirtIO Net device (WebSocket: {}) attached at 0x{:x} (IRQ {})", ws_url, base_addr, irq);
118
+ } else if let Some(wt_url) = &args.net_webtransport {
119
+ // Wire up VirtIO Net with WebTransport backend
120
+ let wt_backend = riscv_vm::net_webtransport::WebTransportBackend::new(wt_url, args.net_cert_hash.clone());
121
+ let vnet = riscv_vm::virtio::VirtioNet::new(Box::new(wt_backend));
122
+ let device_idx = bus.virtio_devices.len();
123
+ let irq = 1 + device_idx;
124
+ bus.virtio_devices.push(Box::new(vnet));
125
+ let base_addr = 0x1000_1000 + (device_idx as u64) * 0x1000;
126
+ println!("VirtIO Net device (WebTransport: {}) attached at 0x{:x} (IRQ {})", wt_url, base_addr, irq);
127
+ } else if args.net_dummy {
128
+ // Wire up VirtIO Net with dummy backend (for testing)
129
+ let dummy_backend = riscv_vm::net::DummyBackend::new();
130
+ let vnet = riscv_vm::virtio::VirtioNet::new(Box::new(dummy_backend));
131
+ let device_idx = bus.virtio_devices.len();
132
+ let irq = 1 + device_idx;
133
+ bus.virtio_devices.push(Box::new(vnet));
134
+ let base_addr = 0x1000_1000 + (device_idx as u64) * 0x1000;
135
+ println!("VirtIO Net device (Dummy) attached at 0x{:x} (IRQ {})", base_addr, irq);
136
+ }
137
+
138
+ let entry_pc = if buffer.starts_with(b"\x7FELF") {
139
+ println!("Detected ELF payload, loading program segments...");
140
+ load_elf_into_dram(&buffer, &mut bus)?
141
+ } else {
142
+ if args.load_addr < dram_base {
143
+ eprintln!("Load address must be >= 0x{:x}", dram_base);
144
+ return Ok(());
145
+ }
146
+ let offset = args.load_addr - dram_base;
147
+ println!(
148
+ "Loading raw binary ({} bytes) at 0x{:x}",
149
+ buffer.len(),
150
+ args.load_addr
151
+ );
152
+ bus.dram
153
+ .load(&buffer, offset)
154
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
155
+ args.load_addr
156
+ };
157
+
158
+ let mut cpu = Cpu::new(entry_pc);
159
+
160
+ println!("Starting execution at 0x{:x}", cpu.pc);
161
+ // Early probe dump (harmless if device absent): helps debug xv6 panic on probe.
162
+ dump_virtio_id(&mut bus);
163
+
164
+ let mut step_count = 0u64;
165
+ let mut last_report_step = 0u64;
166
+
167
+ // Initialize console for host input
168
+ let console = Console::new();
169
+ let mut escaped = false;
170
+
171
+ loop {
172
+ // Poll console input
173
+ if let Some(b) = console.poll() {
174
+ if escaped {
175
+ if b == b'x' {
176
+ println!("\nTerminated by user.");
177
+ break;
178
+ } else if b == 1 {
179
+ // Ctrl-A twice -> send Ctrl-A to guest
180
+ bus.uart.push_input(1);
181
+ } else {
182
+ // Ctrl-A then something else -> send that something else
183
+ // (Ctrl-A is swallowed)
184
+ bus.uart.push_input(b);
185
+ }
186
+ escaped = false;
187
+ } else {
188
+ if b == 1 { // Ctrl-A
189
+ escaped = true;
190
+ } else {
191
+ bus.uart.push_input(b);
192
+ }
193
+ }
194
+ }
195
+
196
+ let step_result = cpu.step(&mut bus);
197
+ step_count += 1;
198
+
199
+ // Poll VirtIO devices for incoming network packets every 100 instructions
200
+ // More frequent polling improves network responsiveness for interactive protocols
201
+ if step_count % 100 == 0 {
202
+ bus.poll_virtio();
203
+ }
204
+
205
+ // Progress report every 10M instructions (not every instruction!)
206
+ if step_count - last_report_step >= 10_000_000 {
207
+ // eprinteln!("[{} M insns] pc=0x{:x} mode={:?}", step_count / 1_000_000, cpu.pc, cpu.mode);
208
+ last_report_step = step_count;
209
+ }
210
+
211
+
212
+ if let Err(trap) = step_result {
213
+ match trap {
214
+ // Test finisher / explicit host stop requested by the guest.
215
+ Trap::RequestedTrap(code) => {
216
+ println!("Guest requested stop via test finisher: 0x{code:x}");
217
+ break;
218
+ }
219
+ // Non-recoverable emulator error: dump state and exit.
220
+ Trap::Fatal(msg) => {
221
+ eprintln!("Fatal emulator error: {msg}");
222
+ println!("PC: 0x{:x}", cpu.pc);
223
+ for i in 0..32 {
224
+ if i % 4 == 0 {
225
+ println!();
226
+ }
227
+ print!("x{:<2}: 0x{:<16x} ", i, cpu.regs[i]);
228
+ }
229
+ println!();
230
+ break;
231
+ }
232
+ // Architectural traps (interrupts, page faults, ecalls, etc.)
233
+ // are fully handled inside Cpu::handle_trap by updating CSRs
234
+ // and redirecting PC to mtvec/stvec. We simply continue
235
+ // stepping so that the guest handler can run.
236
+ _other => {
237
+ // Traps are handled inside cpu.step() - just continue execution.
238
+ // Use RUST_LOG=debug to see trap details.
239
+ if log::log_enabled!(log::Level::Debug) {
240
+ let mepc = cpu.read_csr(CSR_MEPC).unwrap_or(0);
241
+ let mcause = cpu.read_csr(CSR_MCAUSE).unwrap_or(0);
242
+ let mtval = cpu.read_csr(CSR_MTVAL).unwrap_or(0);
243
+ let mtvec = cpu.read_csr(CSR_MTVEC).unwrap_or(0);
244
+ log::debug!(
245
+ "Trap: {:?} pc=0x{:x} mepc=0x{:x} mcause=0x{:x} mtval=0x{:x} mtvec=0x{:x}",
246
+ _other, cpu.pc, mepc, mcause, mtval, mtvec
247
+ );
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ // Check UART output - handle raw mode by converting \n to \r\n
254
+ use std::io::Write;
255
+ let stdout = std::io::stdout();
256
+ let mut stdout_lock = stdout.lock();
257
+ while let Some(byte) = bus.uart.pop_output() {
258
+ // In raw terminal mode, \n alone doesn't return cursor to column 0.
259
+ // We need to emit \r\n for proper line breaks.
260
+ if byte == b'\n' {
261
+ let _ = stdout_lock.write_all(b"\r\n");
262
+ } else if byte == b'\r' {
263
+ // Carriage return - just emit it
264
+ let _ = stdout_lock.write_all(b"\r");
265
+ } else {
266
+ let _ = stdout_lock.write_all(&[byte]);
267
+ }
268
+ }
269
+ let _ = stdout_lock.flush();
270
+
271
+ // Stop if PC is 0 in Machine/Supervisor mode (likely trap to unmapped vector).
272
+ // User mode PC=0 is valid (xv6 initcode).
273
+ if cpu.pc == 0 && cpu.mode != riscv_vm::csr::Mode::User {
274
+ let mepc = cpu.read_csr(CSR_MEPC).unwrap_or(0);
275
+ let mcause = cpu.read_csr(CSR_MCAUSE).unwrap_or(0);
276
+ let mtval = cpu.read_csr(CSR_MTVAL).unwrap_or(0);
277
+ let mtvec = cpu.read_csr(CSR_MTVEC).unwrap_or(0);
278
+ let sepc = cpu.read_csr(CSR_SEPC).unwrap_or(0);
279
+ let scause = cpu.read_csr(CSR_SCAUSE).unwrap_or(0);
280
+ let stval = cpu.read_csr(CSR_STVAL).unwrap_or(0);
281
+ let stvec = cpu.read_csr(CSR_STVEC).unwrap_or(0);
282
+ println!("PC reached 0, stopping.");
283
+ println!(
284
+ "Final state:\n pc=0x{:016x} mode={:?}\n M: mepc=0x{:016x} mcause=0x{:016x} mtval=0x{:016x} mtvec=0x{:016x}\n S: sepc=0x{:016x} scause=0x{:016x} stval=0x{:016x} stvec=0x{:016x}",
285
+ cpu.pc, cpu.mode, mepc, mcause, mtval, mtvec, sepc, scause, stval, stvec
286
+ );
287
+ break;
288
+ }
289
+ }
290
+
291
+ Ok(())
292
+ }
293
+
294
+ fn load_elf_into_dram(
295
+ buffer: &[u8],
296
+ bus: &mut SystemBus,
297
+ ) -> Result<u64, Box<dyn std::error::Error>> {
298
+ let elf = Elf::parse(buffer)?;
299
+ let base = bus.dram_base();
300
+ let dram_end = base + bus.dram_size() as u64;
301
+
302
+ for ph in &elf.program_headers {
303
+ if ph.p_type != PT_LOAD || ph.p_memsz == 0 {
304
+ continue;
305
+ }
306
+
307
+ let file_size = ph.p_filesz as usize;
308
+ let mem_size = ph.p_memsz as usize;
309
+ let file_offset = ph.p_offset as usize;
310
+ if file_offset + file_size > buffer.len() {
311
+ return Err(format!(
312
+ "ELF segment exceeds file bounds (offset 0x{:x})",
313
+ file_offset
314
+ )
315
+ .into());
316
+ }
317
+
318
+ let target_addr = if ph.p_paddr != 0 {
319
+ ph.p_paddr
320
+ } else {
321
+ ph.p_vaddr
322
+ };
323
+ if target_addr < base {
324
+ return Err(format!(
325
+ "Segment start 0x{:x} lies below DRAM base 0x{:x}",
326
+ target_addr, base
327
+ )
328
+ .into());
329
+ }
330
+ let seg_end = target_addr
331
+ .checked_add(mem_size as u64)
332
+ .ok_or_else(|| "Segment end overflow".to_string())?;
333
+ if seg_end > dram_end {
334
+ return Err(format!(
335
+ "Segment 0x{:x}-0x{:x} exceeds DRAM (end 0x{:x})",
336
+ target_addr, seg_end, dram_end
337
+ )
338
+ .into());
339
+ }
340
+
341
+ let dram_offset = (target_addr - base) as u64;
342
+ if file_size > 0 {
343
+ let end = file_offset + file_size;
344
+ bus.dram
345
+ .load(&buffer[file_offset..end], dram_offset)
346
+ .map_err(|e| format!("Failed to load segment: {}", e))?;
347
+ }
348
+ if mem_size > file_size {
349
+ let zero_start = dram_offset as usize + file_size;
350
+ bus.dram
351
+ .zero_range(zero_start, mem_size - file_size)
352
+ .map_err(|e| format!("Failed to zero bss: {}", e))?;
353
+ }
354
+ log::debug!(
355
+ "Loaded segment: addr=0x{:x}, filesz=0x{:x}, memsz=0x{:x}",
356
+ target_addr,
357
+ file_size,
358
+ mem_size
359
+ );
360
+ }
361
+
362
+ Ok(elf.entry)
363
+ }