Skip to main content

x86/
native_backend.rs

1use crate::error::{Result, X86Error};
2use crate::machine::{ExecutionBackend, MachineConfig};
3use crate::state::SavedState;
4use native_v86_core::native_runtime::NativeCpu;
5use std::path::{Path, PathBuf};
6
7/// Native x86 interpreter backend backed by the Rust port of v86's CPU core.
8///
9/// The current adapter is deliberately single-machine: the v86 CPU core uses
10/// process-global pointers, so two NativeBackend instances must not execute at
11/// the same time in one process.
12pub struct NativeBackend {
13    cpu: Option<NativeCpu>,
14    instructions_per_step: u32,
15    ninep_root: Option<PathBuf>,
16}
17
18impl NativeBackend {
19    pub fn new() -> Self {
20        Self {
21            cpu: None,
22            instructions_per_step: 10_000,
23            ninep_root: None,
24        }
25    }
26
27    pub fn with_instructions_per_step(mut self, value: u32) -> Self {
28        self.instructions_per_step = value.max(1);
29        self
30    }
31
32    pub fn cpu(&self) -> Option<&NativeCpu> {
33        self.cpu.as_ref()
34    }
35
36    pub fn cpu_mut(&mut self) -> Option<&mut NativeCpu> {
37        self.cpu.as_mut()
38    }
39
40    pub fn with_9p_root(mut self, path: impl AsRef<Path>) -> Self {
41        self.ninep_root = Some(path.as_ref().to_path_buf());
42        self
43    }
44
45    pub fn set_9p_root(&mut self, path: impl AsRef<Path>) -> Result<()> {
46        let path = path.as_ref();
47        if let Some(cpu) = self.cpu.as_mut() {
48            cpu.set_9p_root(path)
49                .map_err(X86Error::BackendUnavailable)?;
50        }
51        self.ninep_root = Some(path.to_path_buf());
52        Ok(())
53    }
54}
55
56impl Default for NativeBackend {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62impl ExecutionBackend for NativeBackend {
63    fn reset(&mut self, config: &MachineConfig) -> Result<()> {
64        let ram = u32::try_from(config.ram_bytes).map_err(|_| {
65            X86Error::BackendUnavailable(format!(
66                "native v86 core supports guest RAM up to 4 GiB; requested {} bytes",
67                config.ram_bytes
68            ))
69        })?;
70        let vga = u32::try_from(config.vga_memory_bytes).map_err(|_| {
71            X86Error::BackendUnavailable(format!(
72                "native v86 core supports VGA memory up to 4 GiB; requested {} bytes",
73                config.vga_memory_bytes
74            ))
75        })?;
76        let mut cpu = NativeCpu::new(ram, vga);
77        if let Some(path) = &self.ninep_root {
78            cpu.set_9p_root(path)
79                .map_err(X86Error::BackendUnavailable)?;
80        }
81        self.cpu = Some(cpu);
82        Ok(())
83    }
84
85    fn restore_state(&mut self, state: &SavedState) -> Result<()> {
86        let cpu = self.cpu.as_mut().ok_or_else(|| {
87            X86Error::BackendUnavailable("native backend must be reset before restore".to_owned())
88        })?;
89        let (state_object, buffers) = state.cpu_state_and_buffers()?;
90        cpu.restore_v86_state(&state_object, &buffers)
91            .map_err(X86Error::InvalidState)
92    }
93
94    fn step(&mut self) -> Result<bool> {
95        let cpu = self.cpu.as_mut().ok_or_else(|| {
96            X86Error::BackendUnavailable("native backend is not prepared".to_owned())
97        })?;
98        let _executed = cpu.step(self.instructions_per_step);
99        // HLT is an interruptible guest idle state. It is not a terminal
100        // machine condition, so the outer run loop must keep polling timers.
101        Ok(false)
102    }
103
104    fn read_memory(&self, address: u64, buffer: &mut [u8]) -> Result<()> {
105        let cpu = self.cpu.as_ref().ok_or_else(|| {
106            X86Error::BackendUnavailable("native backend is not prepared".to_owned())
107        })?;
108        let address = u32::try_from(address).map_err(|_| {
109            X86Error::InvalidImage("guest memory address exceeds 32-bit x86 range".to_owned())
110        })?;
111        if cpu.read_memory(address, buffer) {
112            Ok(())
113        } else {
114            Err(X86Error::InvalidImage(
115                "guest memory read is out of bounds".to_owned(),
116            ))
117        }
118    }
119
120    fn write_memory(&mut self, address: u64, data: &[u8]) -> Result<()> {
121        let cpu = self.cpu.as_mut().ok_or_else(|| {
122            X86Error::BackendUnavailable("native backend is not prepared".to_owned())
123        })?;
124        let address = u32::try_from(address).map_err(|_| {
125            X86Error::InvalidImage("guest memory address exceeds 32-bit x86 range".to_owned())
126        })?;
127        if cpu.write_memory(address, data) {
128            Ok(())
129        } else {
130            Err(X86Error::InvalidImage(
131                "guest memory write is out of bounds".to_owned(),
132            ))
133        }
134    }
135
136    fn vga_framebuffer_rgb(&self) -> Option<(u32, u32, Vec<u8>)> {
137        self.cpu.as_ref()?.vga_framebuffer_rgb()
138    }
139
140    fn inject_text(&mut self, text: &str) -> Result<usize> {
141        if self.cpu.is_none() {
142            return Err(X86Error::BackendUnavailable(
143                "native backend is not prepared".to_owned(),
144            ));
145        }
146        Ok(native_v86_core::native_runtime::inject_keyboard_text(text))
147    }
148}