1use std::{
2 env,
3 fmt::{self, Debug},
4 fs, io,
5 mem::{drop, take},
6 num::NonZero,
7 ops::Add,
8 path::PathBuf,
9 sync::{Arc, Barrier, Mutex},
10 thread,
11 time::SystemTime,
12};
13
14use align_address::Align;
15use core_affinity::CoreId;
16use gdbstub::{arch::Arch, target::Target};
17use hermit_entry::{
18 Format, HermitVersion, UhyveIfVersion,
19 boot_info::{BootInfo, HardwareInfo, LoadInfo, PlatformInfo, RawBootInfo, SerialPortBase},
20 config, detect_format,
21 elf::{KernelObject, LoadedKernel, ParseKernelError},
22};
23use log::error;
24use nix::sys::pthread::{Pthread, pthread_self};
25use thiserror::Error;
26use uhyve_interface::GuestPhysAddr;
27
28use crate::{
29 HypervisorError, PAGE_SIZE, V1_ADDR_RANGE, V2_ADDR_RANGE,
30 fdt::Fdt,
31 gdb::GdbVcpuManager,
32 isolation::filemap::{UhyveFileMap, UhyveMapLeaf},
33 mem::MmapMemory,
34 net::NetworkBackend,
35 params::{EnvVars, HermitImageMode, NetworkMode, Params},
36 parking::Parker,
37 serial::{Destination, UhyveSerial},
38 stats::{CpuStats, VmStats},
39 vcpu::VirtualCPU,
40};
41#[cfg(target_os = "linux")]
42use crate::{
43 isolation::landlock::initialize,
44 params::{FileSandboxMode, Output},
45};
46
47pub type HypervisorResult<T> = Result<T, HypervisorError>;
48
49#[derive(Error, Debug)]
50pub enum LoadKernelError {
51 #[error(transparent)]
52 Io(#[from] io::Error),
53 #[error("{0}")]
54 ParseKernelError(ParseKernelError),
55 #[error("guest memory size is not large enough")]
56 InsufficientMemory,
57}
58
59type LoadKernelResult<T> = Result<T, LoadKernelError>;
60
61#[cfg(target_os = "linux")]
62pub type DefaultBackend = crate::os::x86_64::kvm_cpu::KvmVm;
63#[cfg(target_os = "macos")]
64pub type DefaultBackend = crate::os::XhyveVm;
65
66pub(crate) trait VirtualizationBackendInternal: Sized {
68 type VCPU: 'static + VirtualCPU;
69 type VirtioNetImpl: NetworkBackend;
70 const NAME: &str;
71
72 fn new_cpu(
74 &self,
75 id: usize,
76 kernel_info: Arc<KernelInfo>,
77 enable_stats: bool,
78 ) -> HypervisorResult<Self::VCPU>;
79
80 fn new(
81 peripherals: Arc<VmPeripherals<Self::VirtioNetImpl>>,
82 params: &Params,
83 ) -> HypervisorResult<Self>;
84
85 fn virtio_net_device(mode: NetworkMode, mmap: Arc<MmapMemory>) -> Self::VirtioNetImpl;
86}
87
88#[derive(Debug, Clone)]
89pub struct VmResult {
90 pub code: i32,
91 pub output: Option<String>,
92 pub stats: Option<VmStats>,
93}
94
95#[derive(Debug)]
97pub(crate) struct VmPeripherals<VirtioNetImpl: NetworkBackend> {
98 pub file_mapping: Mutex<UhyveFileMap>,
99 pub mem: Arc<MmapMemory>,
100 pub(crate) serial: UhyveSerial,
101 pub virtio_device: Option<Mutex<VirtioNetImpl>>,
102}
103
104#[allow(private_bounds)]
106pub trait VirtualizationBackend: Sized + VirtualizationBackendInternal {}
107
108unsafe impl<N: NetworkBackend> Send for VmPeripherals<N> {}
111
112unsafe impl<N: NetworkBackend> Sync for VmPeripherals<N> {}
113
114#[derive(Debug)]
116pub(crate) struct KernelInfo {
117 pub entry_point: GuestPhysAddr,
119 pub kernel_address: GuestPhysAddr,
122 pub params: Params,
123 pub path: PathBuf,
124 pub stack_address: GuestPhysAddr,
125 pub guest_address: GuestPhysAddr,
127}
128
129pub(crate) const BOOT_INFO_OFFSET: u64 = 0x9000;
131const FDT_OFFSET: u64 = 0x5000;
132pub(crate) const KERNEL_OFFSET: u64 = 0x40000;
133const KERNEL_STACK_SIZE: u64 = 0x8000;
134pub(crate) struct KickSignal;
138
139pub(crate) fn generate_guest_start_address(
156 interface_version: UhyveIfVersion,
157 aslr: bool,
158 object_mem_size: usize,
159 object_start_addr: Option<u64>,
160 mem_size: usize,
161) -> (GuestPhysAddr, GuestPhysAddr) {
162 let (guest_address_lb, guest_address_ub): (u64, u64) = {
166 let range = match interface_version.0 {
167 1 => V1_ADDR_RANGE,
168 2 | 3 => V2_ADDR_RANGE,
169 _ => unimplemented!(),
170 };
171 let mem_size = (object_mem_size + mem_size) as u64 + KERNEL_OFFSET;
173 (
174 range.0,
175 range.1.checked_sub(mem_size).unwrap_or_else(|| {
176 let (lb, ub) = range;
177 let hint = match interface_version.0 {
178 1 => " (More than 3GiB memory is not supported for Hermit <= 0.13.2)",
179 2 | 3 => "",
180 _ => unimplemented!(),
181 };
182 panic!("Out of range [{lb:#x}, {ub:#x}) due to memory size {mem_size:#x}.{hint}")
183 }),
184 )
185 };
186
187 match (aslr, object_start_addr) {
188 (true, None) => {
189 let mut rng = rand::rng();
190 let guest_address = GuestPhysAddr::new(
191 rand::RngExt::random_range(&mut rng, guest_address_lb..=guest_address_ub)
192 .align_down(0x20_0000),
193 );
194 (guest_address, guest_address.add(KERNEL_OFFSET))
195 }
196 (false, None) => {
197 let guest_address = GuestPhysAddr::new(guest_address_lb);
198 (guest_address, guest_address.add(KERNEL_OFFSET))
199 }
200 (_, Some(predefined_start_address)) => {
201 assert!(
202 (guest_address_lb..=guest_address_ub).contains(&predefined_start_address),
203 "Predefined address {predefined_start_address:#x} out of range of possible
204 guest addresses: [{guest_address_lb:#x}, {guest_address_ub:#x}]."
205 );
206 if aslr {
207 warn!("ASLR is enabled but kernel is not relocatable - disabling ASLR");
208 }
209 (
210 GuestPhysAddr::new(guest_address_lb),
211 GuestPhysAddr::new(predefined_start_address),
212 )
213 }
214 }
215}
216
217pub struct UhyveVm<VirtBackend: VirtualizationBackend> {
218 pub(crate) vcpus: Vec<<VirtBackend as VirtualizationBackendInternal>::VCPU>,
219 pub(crate) peripherals: Arc<VmPeripherals<VirtBackend::VirtioNetImpl>>,
220 pub(crate) kernel_info: Arc<KernelInfo>,
221 _virt_backend: VirtBackend,
222}
223#[allow(private_bounds)]
224impl<VirtBackend: VirtualizationBackend<VirtioNetImpl: NetworkBackend>> UhyveVm<VirtBackend> {
225 pub fn new(kernel_path: PathBuf, mut params: Params) -> HypervisorResult<UhyveVm<VirtBackend>> {
226 let memory_size = params.memory_size.get();
227
228 let kernel_data = fs::read(&kernel_path)
229 .map_err(|_e| HypervisorError::InvalidKernelPath(kernel_path.clone()))?;
230
231 let mut file_mapping = UhyveFileMap::new(
233 ¶ms.file_mapping,
234 params.tempdir.clone(),
235 #[cfg(target_os = "linux")]
236 params.io_mode,
237 );
238
239 let mut hermit_image = None;
240
241 let elf = match detect_format(&kernel_data[..]) {
243 None => return Err(HypervisorError::InvalidKernelPath(kernel_path.clone())),
244 Some(Format::Elf) => kernel_data,
245 Some(Format::Gzip) => {
246 let buf_decompressed = {
247 use io::Read;
248
249 let mut buf_decompressed = Vec::new();
251 flate2::bufread::GzDecoder::new(&kernel_data[..])
252 .read_to_end(&mut buf_decompressed)?;
253 drop(kernel_data);
254 buf_decompressed
255 };
256
257 let keep_config_data;
258 let keep_kernel_data;
259
260 let handle = match params.hermit_image_mode {
261 HermitImageMode::External => {
262 file_mapping.add_hermit_image(&buf_decompressed[..])?;
264 drop(buf_decompressed);
265
266 let config_data = if let Some(UhyveMapLeaf::Virtual(data)) = file_mapping
267 .get_host_path(&("/".to_string() + config::Config::DEFAULT_PATH), true)
268 {
269 keep_config_data = data;
270 &keep_config_data[..]
271 } else {
272 return Err(HypervisorError::HermitImageConfigNotFound);
273 };
274
275 let konfig: config::Config<'_> = toml::from_slice(config_data)?;
276
277 let inner_kernel_path = match &konfig {
279 config::Config::V1 { kernel, .. } => kernel,
280 };
281 let raw_kernel = if let Some(UhyveMapLeaf::Virtual(data)) =
282 file_mapping.get_host_path(inner_kernel_path, true)
283 {
284 keep_kernel_data = data;
285 &keep_kernel_data[..]
286 } else {
287 error!("Unable to find kernel in Hermit image");
288 return Err(HypervisorError::InvalidKernelPath(kernel_path.clone()));
289 };
290
291 config::ConfigHandle {
292 config: konfig,
293 raw_kernel,
294 }
295 }
296 HermitImageMode::Internal => {
297 match config::parse_tar(hermit_image.insert(buf_decompressed)) {
298 Ok(handle) => handle,
299 Err(e) => {
300 error!("Error during Hermit image parsing: {e}");
301 return Err(HypervisorError::InvalidKernelPath(
302 kernel_path.clone(),
303 ));
304 }
305 }
306 }
307 };
308
309 match handle.config {
311 config::Config::V1 {
312 mut input,
313 requirements,
314 kernel: _,
315 } => {
316 if params.kernel_args.is_empty() {
318 params.kernel_args.append(
319 &mut take(&mut input.kernel_args)
320 .into_iter()
321 .map(|i| i.into_owned())
322 .collect(),
323 );
324 if !input.app_args.is_empty() {
325 params.kernel_args.push("--".to_string());
326 params.kernel_args.append(
327 &mut take(&mut input.app_args)
328 .into_iter()
329 .map(|i| i.into_owned())
330 .collect(),
331 )
332 }
333 }
334 debug!("Passing kernel arguments: {:?}", params.kernel_args);
335
336 input.env_vars.retain(|i| i.contains('='));
338
339 if let EnvVars::Set(env) = &mut params.env {
340 if let Ok(EnvVars::Set(prev_env_vars)) =
341 EnvVars::try_from(&input.env_vars[..])
342 {
343 let new_env_vars = take(env);
345 *env = prev_env_vars.into_iter().chain(new_env_vars).collect();
346 } else {
347 warn!("Unable to parse env vars from Hermit image configuration");
348 }
349 } else if input.env_vars.is_empty() {
350 info!("Ignoring Hermit image env vars due to `-e host`");
351 }
352
353 if let Some(required_memory_size) = requirements.memory
357 && params.memory_size.0 < required_memory_size
358 {
359 return Err(HypervisorError::InsufficientGuestMemorySize {
360 got: params.memory_size.0,
361 wanted: required_memory_size,
362 });
363 }
364
365 if params.cpu_count.get() < requirements.cpus {
366 return Err(HypervisorError::InsufficientGuestCPUs {
367 got: params.cpu_count.get(),
368 wanted: requirements.cpus,
369 });
370 }
371 }
372 }
373
374 handle.raw_kernel.to_vec()
375 }
376 };
377
378 let object: KernelObject<'_> =
379 KernelObject::parse(&elf).map_err(LoadKernelError::ParseKernelError)?;
380
381 let hermit_version = object.hermit_version();
382 if let Some(version) = hermit_version {
383 info!("Loading a Hermit v{version} kernel");
384 } else {
385 info!("Loading a pre Hermit v0.10.0 kernel");
386 }
387
388 let uhyve_interface_version = object
389 .uhyve_interface_version()
390 .unwrap_or(UhyveIfVersion(1));
391
392 let (guest_address, kernel_address) = generate_guest_start_address(
421 uhyve_interface_version,
422 params.aslr,
423 object.mem_size(),
424 object.start_addr(),
425 memory_size,
426 );
427
428 debug!("Guest starts at {guest_address:#x}");
429 debug!("Kernel gets loaded to {kernel_address:#x}");
430
431 #[cfg(target_os = "linux")]
432 let mut mem = MmapMemory::new(memory_size, guest_address, params.thp, params.ksm);
433
434 #[cfg(not(target_os = "linux"))]
435 let mut mem = MmapMemory::new(memory_size, guest_address, false, false);
436
437 let mounts: Vec<_> = file_mapping.get_all_guest_dirs().collect();
438
439 let serial = UhyveSerial::from_params(¶ms.output)?;
440
441 #[cfg(target_os = "linux")]
443 Self::landlock_init(
444 ¶ms.file_isolation,
445 &file_mapping,
446 &kernel_path,
447 ¶ms.output,
448 #[cfg(feature = "instrument")]
449 ¶ms.trace_dir,
450 );
451
452 assert!(
453 kernel_address.as_u64() > KERNEL_STACK_SIZE,
454 "there should be enough space for the boot stack before the kernel start address",
455 );
456 let stack_address = kernel_address - KERNEL_STACK_SIZE;
457 debug!("Stack starts at {stack_address:#x}");
458
459 let (
460 LoadedKernel {
461 load_info,
462 entry_point,
463 },
464 kernel_end_address,
465 ) = load_kernel_to_mem(&object, &mut mem, kernel_address - guest_address)
466 .expect("Unable to load Kernel {kernel_path}");
467
468 let hermit_image = hermit_image.map(|hermit_image| {
470 load_hermit_image_to_mem(
471 &hermit_image[..],
472 &mut mem,
473 (kernel_end_address - guest_address).align_up(PAGE_SIZE as u64),
474 )
475 .expect("Unable to load Hermit image {kernel_path}")
476 });
477
478 let kernel_info = Arc::new(KernelInfo {
479 entry_point: entry_point.into(),
480 kernel_address,
481 guest_address: mem.guest_addr(),
482 path: kernel_path,
483 params,
484 stack_address,
485 });
486
487 let mem = Arc::new(mem);
489 if let Some(version) = hermit_version
490 && kernel_info.params.network.is_some()
491 && (version
492 < HermitVersion {
493 major: 0,
494 minor: 13,
495 patch: 2,
496 }) {
497 return Err(HypervisorError::FeatureMismatch(
498 "Network requires Kernel 0.13.2 or newer",
499 ));
500 }
501 let virtio_device = kernel_info
502 .params
503 .network
504 .as_ref()
505 .map(|mode| Mutex::new(VirtBackend::virtio_net_device(mode.clone(), mem.clone())));
506
507 let peripherals = Arc::new(VmPeripherals {
508 mem,
509 virtio_device,
511 file_mapping: Mutex::new(file_mapping),
513 serial,
514 });
515
516 let virt_backend = VirtBackend::new(peripherals.clone(), &kernel_info.params)?;
517
518 let cpu_count = kernel_info.params.cpu_count.get();
519
520 let vcpus: Vec<_> = (0..cpu_count as usize)
521 .map(|cpu_id| {
522 virt_backend
523 .new_cpu(cpu_id, kernel_info.clone(), kernel_info.params.stats)
524 .unwrap()
525 })
526 .collect();
527
528 let freq = vcpus[0].get_cpu_frequency();
529
530 let serial_port = SerialPortBase::new(match uhyve_interface_version.0 {
531 1 => uhyve_interface::v1::HypercallAddress::Uart as _,
532 2 | 3 => uhyve_interface::v2::HypercallAddress::SerialWriteBuffer as _,
533 uhifv => {
534 unimplemented!(
535 "Kernel uses unsupported uhyve-interface version {}. Is Uhyve too old?",
536 uhifv
537 )
538 }
539 });
540
541 write_fdt_into_mem(
542 &peripherals.mem,
543 &kernel_info.params,
544 hermit_image.clone(),
545 freq,
546 mounts,
547 );
548 write_boot_info_to_mem(
549 &peripherals.mem,
550 load_info,
551 cpu_count as u64,
552 freq,
553 serial_port,
554 );
555
556 let legacy_mapping = if let Some(version) = hermit_version {
557 version
559 < HermitVersion {
560 major: 0,
561 minor: 10,
562 patch: 0,
563 }
564 } else {
565 true
566 };
567 init_guest_mem(
568 unsafe { peripherals.mem.as_slice_mut() }, guest_address,
570 hermit_image.map(|i| i.end).unwrap_or(kernel_end_address) - guest_address,
571 legacy_mapping,
572 );
573 trace!("VM initialization complete");
574
575 Ok(Self {
576 peripherals,
577 kernel_info,
578 vcpus,
579 _virt_backend: virt_backend,
580 })
581 }
582
583 #[cfg(target_os = "linux")]
584 pub fn landlock_init(
585 file_sandbox_mode: &FileSandboxMode,
586 file_map: &UhyveFileMap,
587 kernel_path: &std::path::Path,
588 output: &Output,
589 #[cfg(feature = "instrument")] trace: &Option<PathBuf>,
590 ) {
591 if file_sandbox_mode != &FileSandboxMode::None {
592 debug!("Attempting to initialize Landlock...");
593 let host_paths = file_map.get_all_host_paths();
594 let temp_dir = file_map.get_temp_dir().to_owned();
595 let landlock = initialize(
596 file_sandbox_mode,
597 kernel_path.into(),
598 output,
599 host_paths.map(|i| i.as_os_str()),
600 temp_dir,
601 #[cfg(feature = "instrument")]
602 trace,
603 );
604 landlock.apply_landlock_restrictions();
605 }
606 }
607
608 pub fn run(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult
612 where
613 GdbVcpuManager<VirtBackend>: Target,
614 <GdbVcpuManager<VirtBackend> as Target>::Error: fmt::Debug,
615 <GdbVcpuManager<VirtBackend> as Target>::Arch: Arch<Usize = u64>,
616 {
617 KickSignal::register_handler().unwrap();
618
619 if self.kernel_info.params.gdb_port.is_none() {
620 self.run_no_gdb(cpu_affinity)
621 } else {
622 if cfg!(target_os = "macos") {
623 warn!("This mode is experimental and doesn't yet work correctly.");
624 }
625 self.run_gdb(cpu_affinity)
626 }
627 }
628
629 fn run_no_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
630 let main_parker = Parker::current();
632
633 let num_vcpus = self.vcpus.len();
634
635 let pthreads: Mutex<Vec<Pthread>> = Mutex::new(Vec::with_capacity(num_vcpus));
636 let pthreads_published = Barrier::new(num_vcpus + 1);
637
638 let cpu_results = thread::scope(|s| {
639 trace!("Starting vCPUs");
640 let cpu_handles = self
641 .vcpus
642 .into_iter()
643 .enumerate()
644 .map(|(cpu_id, mut cpu)| {
645 let main_parker = main_parker.clone();
646 let local_cpu_affinity = cpu_affinity
647 .as_ref()
648 .and_then(|core_ids| core_ids.get(cpu_id).copied());
649 let pthreads = &pthreads;
650 let pthreads_published = &pthreads_published;
651
652 s.spawn(move || {
653 {
654 pthreads.lock().unwrap().push(pthread_self());
655 }
656 pthreads_published.wait();
657
658 trace!("Create thread for CPU {cpu_id}");
659 match local_cpu_affinity {
660 Some(core_id) => {
661 debug!("Trying to pin thread {} to CPU {}", cpu_id, core_id.id);
662 core_affinity::set_for_current(core_id); }
664 None => debug!("No affinity specified, not binding thread"),
665 }
666
667 cpu.thread_local_init().expect("Unable to initialize vCPU");
668
669 struct UnparkOnDrop(Parker);
670
671 impl Drop for UnparkOnDrop {
672 fn drop(&mut self) {
673 self.0.unpark();
674 }
675 }
676
677 let _unpark_on_drop = UnparkOnDrop(main_parker);
678
679 match cpu.run() {
681 Ok((code, stats)) => (Ok(code), stats),
682 Err(err) => {
683 error!("CPU {cpu_id} crashed with {err:?}");
684 (Err(err), None)
685 }
686 }
687 })
688 })
689 .collect::<Vec<_>>();
690
691 pthreads_published.wait();
692
693 trace!("Waiting for first CPU to finish");
694 main_parker.park();
695
696 trace!("Killing all threads");
697 for &tid in pthreads.lock().unwrap().iter() {
698 let _ = KickSignal::pthread_kill(tid);
702 }
703
704 cpu_handles
705 .into_iter()
706 .map(|h| h.join().unwrap())
707 .collect::<Vec<_>>()
708 });
709
710 let code = cpu_results
711 .iter()
712 .find_map(|(ret, _stats)| ret.as_ref().ok().copied().flatten())
713 .unwrap();
714
715 let stats: Vec<CpuStats> = cpu_results
716 .iter()
717 .filter_map(|(_ret, stats)| stats.clone())
718 .collect();
719 let output = if let Destination::Buffer(b) = &self.peripherals.serial.destination {
720 Some(String::from_utf8_lossy(&b.lock().unwrap()).into_owned())
721 } else {
722 None
723 };
724
725 VmResult {
726 code,
727 output,
728 stats: Some(VmStats::new(&stats)),
729 }
730 }
731}
732
733impl<VirtIf: VirtualizationBackend + Debug> fmt::Debug for UhyveVm<VirtIf> {
734 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
735 f.debug_struct(&format!("UhyveVm<{}>", VirtIf::NAME))
736 .field("entry_point", &self.kernel_info.entry_point)
737 .field("stack_address", &self.kernel_info.stack_address)
738 .field("guest_address", &self.kernel_info.guest_address)
739 .field("mem", &self.peripherals.mem)
740 .field("path", &self.kernel_info.path)
741 .field("virtio_device", &self.peripherals.virtio_device)
742 .field("params", &self.kernel_info.params)
743 .field("file_mapping", &self.peripherals.file_mapping)
744 .finish()
745 }
746}
747
748fn init_guest_mem(
752 mem: &mut [u8],
753 guest_addr: GuestPhysAddr,
754 memory_size: u64,
755 legacy_mapping: bool,
756) {
757 trace!("Initialize guest memory");
758 crate::arch::init_guest_mem(mem, guest_addr, memory_size, legacy_mapping);
759}
760
761fn write_fdt_into_mem(
762 mem: &MmapMemory,
763 params: &Params,
764 hermit_image: Option<core::ops::Range<GuestPhysAddr>>,
765 cpu_freq: Option<NonZero<u32>>,
766 mounts: Vec<String>,
767) {
768 trace!("Writing FDT in memory");
769
770 let sep = params
771 .kernel_args
772 .iter()
773 .take_while(|arg| *arg != "--")
774 .count();
775
776 let mut fdt = Fdt::new(hermit_image)
777 .unwrap()
778 .memory(mem.address_range())
779 .unwrap()
780 .kernel_args(¶ms.kernel_args[..sep])
781 .app_args(params.kernel_args.get(sep + 1..).unwrap_or_default());
782
783 fdt = match ¶ms.env {
784 EnvVars::Host => fdt.envs(env::vars()),
785 EnvVars::Set(map) => fdt.envs(map.iter().map(|(a, b)| (a.as_str(), b.as_str()))),
786 };
787
788 if !mounts.is_empty() {
789 fdt = fdt.mounts(mounts).unwrap();
790 }
791
792 #[cfg(target_arch = "aarch64")]
793 {
794 fdt = fdt.gic().unwrap();
795 fdt = fdt.cpus(params.cpu_count).unwrap();
796 fdt = fdt.timer().unwrap();
797 }
798
799 if let Some(tsc_khz) = cpu_freq {
800 fdt = fdt.tsc_khz(tsc_khz.into()).unwrap();
801 }
802 let fdt = fdt.finish().unwrap();
803
804 debug!("fdt.len() = {}", fdt.len());
805 assert!(fdt.len() < (BOOT_INFO_OFFSET - FDT_OFFSET) as usize);
806 unsafe {
807 let fdt_ptr = mem.host_start().add(FDT_OFFSET as usize);
808 fdt_ptr.copy_from_nonoverlapping(fdt.as_ptr(), fdt.len());
809 }
810}
811
812fn write_boot_info_to_mem(
813 mem: &MmapMemory,
814 load_info: LoadInfo,
815 num_cpus: u64,
816 cpu_freq: Option<NonZero<u32>>,
817 #[cfg(target_arch = "x86_64")] serial_port: Option<NonZero<u16>>,
818 #[cfg(target_arch = "aarch64")] serial_port: Option<NonZero<u64>>,
819) {
820 debug!(
821 "Writing BootInfo to {:?}",
822 mem.guest_addr() + BOOT_INFO_OFFSET
823 );
824 let boot_info = BootInfo {
825 hardware_info: HardwareInfo {
826 phys_addr_range: mem.address_range_u64(),
827 serial_port_base: serial_port,
828 device_tree: Some((mem.guest_addr().as_u64() + FDT_OFFSET).try_into().unwrap()),
829 },
830 load_info,
831 platform_info: PlatformInfo::Uhyve {
832 has_pci: cfg!(target_os = "linux"),
833 num_cpus: num_cpus.try_into().unwrap(),
834 cpu_freq,
835 boot_time: SystemTime::now().into(),
836 },
837 };
838 unsafe {
839 let raw_boot_info_ptr = mem.host_start().add(BOOT_INFO_OFFSET as usize) as *mut RawBootInfo;
840 *raw_boot_info_ptr = RawBootInfo::from(boot_info);
841 }
842}
843
844fn load_kernel_to_mem(
847 object: &KernelObject<'_>,
848 mem: &mut MmapMemory,
849 relative_offset: u64,
850) -> LoadKernelResult<(LoadedKernel, GuestPhysAddr)> {
851 let kernel_end_address = mem.guest_addr() + relative_offset + object.mem_size();
852
853 if kernel_end_address > mem.guest_addr() + mem.size() {
854 return Err(LoadKernelError::InsufficientMemory);
855 }
856
857 Ok((
858 object.load_kernel(
859 &mut unsafe { mem.as_slice_uninit_mut() }
861 [relative_offset as usize..relative_offset as usize + object.mem_size()],
862 relative_offset + mem.guest_addr().as_u64(),
863 ),
864 kernel_end_address,
865 ))
866}
867
868fn load_hermit_image_to_mem(
871 hermit_image: &[u8],
872 mem: &mut MmapMemory,
873 relative_offset: u64,
874) -> Option<core::ops::Range<GuestPhysAddr>> {
875 assert!(!hermit_image.is_empty());
876
877 let aligned_image_len = hermit_image.len().align_up(PAGE_SIZE);
878 let image_start_address = mem.guest_addr() + relative_offset;
879 let image_end_address = image_start_address + aligned_image_len as u64;
880
881 if image_end_address > mem.guest_addr() + mem.size() {
882 return None;
883 }
884
885 let mem_slice =
891 unsafe { &mut mem.as_slice_mut()[relative_offset as usize..][..aligned_image_len] };
892 mem_slice[..hermit_image.len()].copy_from_slice(hermit_image);
893 mem_slice[hermit_image.len()..].fill(0);
894 debug!(
895 "Hermit image: start={relative_offset:#x}, length={:#x}",
896 hermit_image.len()
897 );
898 if unsafe {
900 libc::mprotect(
901 &mut mem_slice[0] as *mut u8 as *mut libc::c_void,
902 aligned_image_len,
903 libc::PROT_READ | libc::PROT_EXEC,
904 )
905 } == -1
906 {
907 let e = std::io::Error::last_os_error();
908 warn!("Hermit image mprotect(2) failed: {e}");
909 }
910
911 Some(image_start_address..image_end_address)
912}
913
914#[cfg(test)]
915mod tests {
916 use std::ops::Add;
917
918 use hermit_entry::UhyveIfVersion;
919
920 use crate::{
921 RAM_START, V1_MAX_ADDR,
922 vm::{KERNEL_OFFSET, generate_guest_start_address},
923 };
924
925 #[test]
926 fn test_generate_guest_start_address() {
927 let mem_size: usize = 0xBE20_0000; let if_v1 = UhyveIfVersion(1);
929 let if_v2 = UhyveIfVersion(2);
930 let object_mem_size: usize = 0x0009_C400;
931 let object_no_start_addr: Option<u64> = None;
932 #[cfg(target_arch = "x86_64")]
933 let object_start_addr: u64 = 0x0002_0000;
934 #[cfg(target_arch = "aarch64")]
935 let object_start_addr: u64 = 0x1002_0000;
936
937 let (mut guest_address, mut start_address) = generate_guest_start_address(
941 if_v1,
942 false,
943 object_mem_size,
944 object_no_start_addr,
945 mem_size,
946 );
947 assert_eq!(guest_address, RAM_START);
948 assert_eq!(start_address, guest_address.add(KERNEL_OFFSET));
949
950 (guest_address, start_address) = generate_guest_start_address(
952 if_v1,
953 true,
954 object_mem_size,
955 object_no_start_addr,
956 mem_size,
957 );
958 assert_eq!(start_address, guest_address.add(KERNEL_OFFSET));
959 assert!(start_address.as_u64() <= V1_MAX_ADDR);
960
961 (guest_address, start_address) = generate_guest_start_address(
963 if_v1,
964 true,
965 object_mem_size,
966 object_start_addr.into(),
967 mem_size,
968 );
969 assert_eq!(guest_address, RAM_START);
970 assert_eq!(start_address.as_u64(), object_start_addr);
971 assert_eq!(start_address, guest_address.add(0x0002_0000usize));
973 assert!(start_address.as_u64() <= V1_MAX_ADDR);
974
975 (guest_address, start_address) = generate_guest_start_address(
979 if_v2,
980 false,
981 object_mem_size,
982 object_no_start_addr,
983 mem_size,
984 );
985 assert_eq!(guest_address.as_u64(), 0x0001_0000_0000u64);
986 assert_eq!(start_address, guest_address.add(KERNEL_OFFSET));
987 #[cfg(target_arch = "x86_64")]
988 assert!(start_address.as_u64() >= V1_MAX_ADDR);
989
990 (guest_address, start_address) = generate_guest_start_address(
992 if_v2,
993 true,
994 object_mem_size,
995 object_no_start_addr,
996 mem_size,
997 );
998 assert_eq!(start_address, guest_address.add(KERNEL_OFFSET));
999 #[cfg(target_arch = "x86_64")]
1000 assert!(start_address.as_u64() >= V1_MAX_ADDR);
1001
1002 (guest_address, start_address) = generate_guest_start_address(
1008 if_v2,
1009 true,
1010 object_mem_size,
1011 object_no_start_addr,
1012 0x0010_0000_0000 - object_mem_size - KERNEL_OFFSET as usize - 0x0001_0000_0000,
1014 );
1015 assert_eq!(guest_address.as_u64(), 0x0001_0000_0000);
1016 assert_eq!(start_address, guest_address.add(KERNEL_OFFSET));
1017 #[cfg(target_arch = "x86_64")]
1018 assert!(start_address.as_u64() >= V1_MAX_ADDR);
1019 }
1020}