From 9e1cf38e35cc956d6c9823dc973a851ef2f41e00 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 12:17:21 +0100 Subject: [PATCH 1/6] removed the verbose parameter. Fixes #496 --- .github/workflows/ci.yml | 6 +++--- README.md | 7 +++---- src/bin/uhyve.rs | 6 ------ src/params.rs | 4 ---- src/vm.rs | 14 +++----------- tests/common.rs | 1 - tests/gdb.rs | 1 - 7 files changed, 9 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30784b3c..aef32205 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,9 +60,9 @@ jobs: - name: Run images timeout-minutes: 1 run: | - cargo run -- -v data/x86_64/hello_world - cargo run -- -v data/x86_64/rusty_demo - cargo run -- -v data/x86_64/hello_c + cargo run -- data/x86_64/hello_world + cargo run -- data/x86_64/rusty_demo + cargo run -- data/x86_64/hello_c fmt: name: Format diff --git a/README.md b/README.md index 7edb9c6c..ad97529f 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,6 @@ The following variables are supported: - `HERMIT_CPUS`: specifies the number of cores the virtual machine may use. - `HERMIT_MEM`: defines the memory size of the virtual machine. The suffixes *M* and *G* can be used to specify a value in megabytes or gigabytes, respectively. -- setting `HERMIT_VERBOSE` to `1` makes the hypervisor print kernel log messages to the terminal. - `HERMIT_GDB_PORT=port` activate a gdb server for the application running inside Uhyve. _See below_ By default, the loader initializes a system with one core and 512 MiB RAM. @@ -158,9 +157,9 @@ Unless you explicitly state otherwise, any contribution intentionally submitted As mentioned above, the Uhyve repository ships some binaries that can be used for testing purposes. ```sh -cargo run -- -v data/x86_64/rusty_demo -cargo run -- -v data/x86_64/hello_world -cargo run -- -v data/x86_64/hello_c +cargo run -- data/x86_64/rusty_demo +cargo run -- data/x86_64/hello_world +cargo run -- data/x86_64/hello_c ``` ### Debugging Hermit apps diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index 0bd1868c..0e989320 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -38,10 +38,6 @@ fn setup_trace() { #[derive(Parser, Debug)] #[clap(version, author, about)] struct Args { - /// Print kernel messages - #[clap(short, long)] - verbose: bool, - #[clap(flatten, next_help_heading = "MEMORY")] memory_args: MemoryArgs, @@ -225,7 +221,6 @@ impl CpuArgs { impl From for Params { fn from(args: Args) -> Self { let Args { - verbose, memory_args: MemoryArgs { memory_size, @@ -247,7 +242,6 @@ impl From for Params { kernel_args, } = args; Self { - verbose, memory_size, #[cfg(target_os = "linux")] thp, diff --git a/src/params.rs b/src/params.rs index 15cf68b4..ed242b3b 100644 --- a/src/params.rs +++ b/src/params.rs @@ -9,9 +9,6 @@ use thiserror::Error; #[derive(Debug, Clone)] pub struct Params { - /// Print kernel messages - pub verbose: bool, - /// Guest RAM size pub memory_size: GuestMemorySize, @@ -41,7 +38,6 @@ pub struct Params { impl Default for Params { fn default() -> Self { Self { - verbose: Default::default(), memory_size: Default::default(), #[cfg(target_os = "linux")] thp: false, diff --git a/src/vm.rs b/src/vm.rs index f3b9e65b..e87c3961 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -120,7 +120,6 @@ pub struct UhyveVm { path: PathBuf, args: Vec, boot_info: *const RawBootInfo, - verbose: bool, pub virtio_device: Arc>, #[allow(dead_code)] // gdb is not supported on macos pub(super) gdb_port: Option, @@ -163,7 +162,6 @@ impl UhyveVm { path: kernel_path, args: params.kernel_args, boot_info: ptr::null(), - verbose: params.verbose, virtio_device, gdb_port: params.gdb_port, virt_backend, @@ -174,10 +172,6 @@ impl UhyveVm { Ok(vm) } - fn verbose(&self) -> bool { - self.verbose - } - /// Returns the section offsets relative to their base addresses pub fn get_offset(&self) -> u64 { self.offset @@ -267,10 +261,9 @@ impl UhyveVm { hardware_info: HardwareInfo { phys_addr_range: self.mem.guest_address.as_u64() ..self.mem.guest_address.as_u64() + self.mem.memory_size as u64, - serial_port_base: self.verbose().then(|| { - SerialPortBase::new((uhyve_interface::HypercallAddress::Uart as u16).into()) - .unwrap() - }), + serial_port_base: SerialPortBase::new( + (uhyve_interface::HypercallAddress::Uart as u16).into(), + ), device_tree: Some(FDT_ADDR.as_u64().try_into().unwrap()), }, load_info, @@ -307,7 +300,6 @@ impl fmt::Debug for UhyveVm { .field("num_cpus", &self.num_cpus) .field("path", &self.path) .field("boot_info", &self.boot_info) - .field("verbose", &self.verbose) .field("virtio_device", &self.virtio_device) .finish() } diff --git a/tests/common.rs b/tests/common.rs index e28abe29..bdf60ea0 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -44,7 +44,6 @@ pub fn run_simple_vm(kernel_path: PathBuf) { env_logger::try_init().ok(); println!("Launching kernel {}", kernel_path.display()); let params = Params { - verbose: true, cpu_count: 2.try_into().unwrap(), memory_size: Byte::from_u64_with_unit(32, Unit::MiB) .unwrap() diff --git a/tests/gdb.rs b/tests/gdb.rs index a2dbd33f..c9ff439c 100644 --- a/tests/gdb.rs +++ b/tests/gdb.rs @@ -25,7 +25,6 @@ fn gdb() -> io::Result<()> { let vm = UhyveVm::new( bin_path, Params { - verbose: true, gdb_port: Some(port), ..Default::default() }, From 91f334a033b64b657ca6686c1681486dfbe735c4 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 14:00:04 +0100 Subject: [PATCH 2/6] Print the Virtualization backend in UhyveVm debug print --- src/linux/x86_64/kvm_cpu.rs | 2 ++ src/macos/aarch64/vcpu.rs | 2 ++ src/macos/x86_64/vcpu.rs | 1 + src/vm.rs | 3 ++- 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/linux/x86_64/kvm_cpu.rs b/src/linux/x86_64/kvm_cpu.rs index ea7c3fd2..dd14ff74 100644 --- a/src/linux/x86_64/kvm_cpu.rs +++ b/src/linux/x86_64/kvm_cpu.rs @@ -34,6 +34,8 @@ pub struct KvmVm { } impl VirtualizationBackend for KvmVm { type VCPU = KvmCpu; + const NAME: &str = "KvmVm"; + fn new_cpu(&self, id: u32, parent_vm: Arc>) -> HypervisorResult { let vcpu = self.vm_fd.create_vcpu(id as u64)?; let mut kvcpu = KvmCpu { diff --git a/src/macos/aarch64/vcpu.rs b/src/macos/aarch64/vcpu.rs index bdfbb614..c8abbf1b 100644 --- a/src/macos/aarch64/vcpu.rs +++ b/src/macos/aarch64/vcpu.rs @@ -26,6 +26,8 @@ use crate::{ pub struct XhyveVm {} impl VirtualizationBackend for XhyveVm { type VCPU = XhyveCpu; + const NAME: &str = "XhyveVm"; + fn new_cpu(&self, id: u32, parent_vm: Arc>) -> HypervisorResult { let mut vcpu = XhyveCpu { id, diff --git a/src/macos/x86_64/vcpu.rs b/src/macos/x86_64/vcpu.rs index 7a155942..de4c56b4 100644 --- a/src/macos/x86_64/vcpu.rs +++ b/src/macos/x86_64/vcpu.rs @@ -158,6 +158,7 @@ static CAP_EXIT: LazyLock = LazyLock::new(|| { pub struct XhyveVm {} impl VirtualizationBackend for XhyveVm { type VCPU = XhyveCpu; + const NAME: &str = "XhyveVm"; fn new_cpu(&self, id: u32, parent_vm: Arc>) -> HypervisorResult { let mut vcpu = XhyveCpu { diff --git a/src/vm.rs b/src/vm.rs index e87c3961..964547a2 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -103,6 +103,7 @@ pub type DefaultBackend = crate::macos::XhyveVm; /// Trait marking a interface for creating (accelerated) VMs. pub trait VirtualizationBackend: Sized { type VCPU; + const NAME: &str; /// Create a new CPU object fn new_cpu(&self, id: u32, parent_vm: Arc>) -> HypervisorResult; @@ -293,7 +294,7 @@ impl UhyveVm { impl fmt::Debug for UhyveVm { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("UhyveVm") + f.debug_struct(&format!("UhyveVm<{}>", VirtIf::NAME)) .field("entry_point", &self.entry_point) .field("stack_address", &self.stack_address) .field("mem", &self.mem) From 53b3ec93d577e43d10e5b3ed26b4901866533174 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 19:20:41 +0100 Subject: [PATCH 3/6] Introduced VmResult for more elaborate return values of UhyveVm::run --- src/bin/uhyve.rs | 2 +- src/linux/mod.rs | 21 ++++++++++++--------- src/macos/mod.rs | 9 ++++++--- src/vm.rs | 6 ++++++ tests/common.rs | 7 +++---- tests/gdb.rs | 4 ++-- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index 0e989320..53acee2a 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -274,7 +274,7 @@ fn run_uhyve() -> i32 { let vm = UhyveVm::new(kernel, params) .expect("Unable to create VM! Is the hypervisor interface (e.g. KVM) activated?"); - vm.run(affinity) + vm.run(affinity).code } fn main() { diff --git a/src/linux/mod.rs b/src/linux/mod.rs index e72f23d7..90583c66 100755 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -29,7 +29,7 @@ use crate::{ x86_64::kvm_cpu::KvmVm, }, vcpu::VirtualCPU, - vm::{UhyveVm, VirtualizationBackend}, + vm::{UhyveVm, VirtualizationBackend, VmResult}, }; static KVM: LazyLock = LazyLock::new(|| Kvm::new().unwrap()); @@ -71,7 +71,7 @@ impl UhyveVm { /// Runs the VM. /// /// Blocks until the VM has finished execution. - pub fn run(mut self, cpu_affinity: Option>) -> i32 { + pub fn run(mut self, cpu_affinity: Option>) -> VmResult { KickSignal::register_handler().unwrap(); self.load_kernel().expect("Unabled to load the kernel"); @@ -83,7 +83,7 @@ impl UhyveVm { } } - fn run_no_gdb(self, cpu_affinity: Option>) -> i32 { + fn run_no_gdb(self, cpu_affinity: Option>) -> VmResult { // After spinning up all vCPU threads, the main thread waits for any vCPU to end execution. let barrier = Arc::new(Barrier::new(2)); @@ -143,14 +143,15 @@ impl UhyveVm { .into_iter() .filter_map(|thread| thread.join().unwrap()) .collect::>(); - match code.len() { + let code = match code.len() { 0 => panic!("No return code from any CPU? Maybe all have been kicked?"), 1 => code[0], _ => panic!("more than one thread finished with an exit code (codes: {code:?})"), - } + }; + VmResult { code, None } } - fn run_gdb(self, cpu_affinity: Option>) -> i32 { + fn run_gdb(self, cpu_affinity: Option>) -> VmResult { let cpu_id = 0; let local_cpu_affinity = cpu_affinity @@ -170,9 +171,9 @@ impl UhyveVm { let connection = wait_for_gdb_connection(this.gdb_port.unwrap()).unwrap(); let debugger = GdbStub::new(connection); - let mut debuggable_vcpu = GdbUhyve::new(this, cpu); + let mut debuggable_vcpu = GdbUhyve::new(this.clone(), cpu); - match debugger + let code = match debugger .run_blocking::(&mut debuggable_vcpu) .unwrap() { @@ -186,7 +187,9 @@ impl UhyveVm { eprintln!("Kill command received."); 0 } - } + }; + + VmResult { code, None } } } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index c3987f6d..fc169c2e 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -16,7 +16,7 @@ pub use crate::macos::aarch64::vcpu::{XhyveCpu, XhyveVm}; pub use crate::macos::x86_64::vcpu::{XhyveCpu, XhyveVm}; use crate::{ vcpu::VirtualCPU, - vm::{UhyveVm, VirtualizationBackend}, + vm::{UhyveVm, VirtualizationBackend, VmResult}, }; pub type HypervisorError = xhypervisor::Error; @@ -26,7 +26,7 @@ impl UhyveVm { /// Runs the VM. /// /// Blocks until the VM has finished execution. - pub fn run(mut self, cpu_affinity: Option>) -> i32 { + pub fn run(mut self, cpu_affinity: Option>) -> VmResult { self.load_kernel().expect("Unabled to load the kernel"); // For communication of the exit code from one vcpu to this thread as return @@ -75,6 +75,9 @@ impl UhyveVm { // ignore the remaining running threads. A better design would be to force // the VCPUs externally to stop, so that the other threads don't block and // can be terminated correctly. - exit_rx.recv().unwrap() + VmResult { + code: exit_rx.recv().unwrap(), + output: None, + } } } diff --git a/src/vm.rs b/src/vm.rs index 964547a2..6c8e670a 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -111,6 +111,12 @@ pub trait VirtualizationBackend: Sized { fn new(memory: &MmapMemory, params: &Params) -> HypervisorResult; } +#[derive(Debug, Clone)] +pub struct VmResult { + pub code: i32, + pub output: Option, +} + pub struct UhyveVm { /// The starting position of the image in physical memory offset: u64, diff --git a/tests/common.rs b/tests/common.rs index bdf60ea0..7d449455 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -5,7 +5,7 @@ use std::{ }; use byte_unit::{Byte, Unit}; -use uhyvelib::{params::Params, vm::UhyveVm}; +use uhyvelib::{params::Params, vm::{UhyveVm, VmResult}}; /// Uses Cargo to build a kernel in the `tests/test-kernels` directory. /// Returns a path to the build binary. @@ -40,7 +40,7 @@ pub fn build_hermit_bin(kernel: impl AsRef) -> PathBuf { /// Small wrapper around [`Uhyve::run`] with default parameters for a small and /// simple Uhyve vm -pub fn run_simple_vm(kernel_path: PathBuf) { +pub fn run_simple_vm(kernel_path: PathBuf) -> VmResult { env_logger::try_init().ok(); println!("Launching kernel {}", kernel_path.display()); let params = Params { @@ -51,6 +51,5 @@ pub fn run_simple_vm(kernel_path: PathBuf) { .unwrap(), ..Default::default() }; - let code = UhyveVm::new(kernel_path, params).unwrap().run(None); - assert_eq!(0, code); + UhyveVm::new(kernel_path, params).unwrap().run(None) } diff --git a/tests/gdb.rs b/tests/gdb.rs index c9ff439c..861d0dae 100644 --- a/tests/gdb.rs +++ b/tests/gdb.rs @@ -30,8 +30,8 @@ fn gdb() -> io::Result<()> { }, ) .unwrap(); - let code = vm.run(None); - assert_eq!(0, code); + let res = vm.run(None); + assert_eq!(0, res.code); }); let temp = TempDir::new().unwrap(); From 4893b27216355d7cc2c8fe1fcbaeac4c85bd2c8b Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 19:26:31 +0100 Subject: [PATCH 4/6] UhyveVm: store params and remove separate num_cpu and path fields --- src/vm.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vm.rs b/src/vm.rs index 6c8e670a..e44ed780 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -123,14 +123,13 @@ pub struct UhyveVm { entry_point: u64, stack_address: u64, pub mem: Arc, - num_cpus: u32, path: PathBuf, - args: Vec, boot_info: *const RawBootInfo, pub virtio_device: Arc>, #[allow(dead_code)] // gdb is not supported on macos pub(super) gdb_port: Option, pub(crate) virt_backend: VirtBackend, + params: Params, } impl UhyveVm { pub fn new(kernel_path: PathBuf, params: Params) -> HypervisorResult> { @@ -165,13 +164,12 @@ impl UhyveVm { entry_point: 0, stack_address: 0, mem: mem.into(), - num_cpus: cpu_count, path: kernel_path, - args: params.kernel_args, boot_info: ptr::null(), virtio_device, gdb_port: params.gdb_port, virt_backend, + params, }; vm.init_guest_mem(); @@ -194,7 +192,7 @@ impl UhyveVm { /// Returns the number of cores for the vm. pub fn num_cpus(&self) -> u32 { - self.num_cpus + self.params.cpu_count.get() } pub fn kernel_path(&self) -> &PathBuf { @@ -202,7 +200,7 @@ impl UhyveVm { } pub fn args(&self) -> &Vec { - &self.args + &self.params.kernel_args } /// Initialize the page tables for the guest @@ -240,19 +238,19 @@ impl UhyveVm { self.entry_point = entry_point; let sep = self - .args + .args() .iter() .enumerate() .find(|(_i, arg)| *arg == "--") .map(|(i, _arg)| i) - .unwrap_or_else(|| self.args.len()); + .unwrap_or_else(|| self.args().len()); let fdt = Fdt::new() .unwrap() .memory(self.mem.guest_address..self.mem.guest_address + self.mem.memory_size as u64) .unwrap() - .kernel_args(&self.args[..sep]) - .app_args(self.args.get(sep + 1..).unwrap_or_default()) + .kernel_args(&self.args()[..sep]) + .app_args(self.args().get(sep + 1..).unwrap_or_default()) .envs(env::vars()) .finish() .unwrap(); @@ -304,10 +302,10 @@ impl fmt::Debug for UhyveVm { .field("entry_point", &self.entry_point) .field("stack_address", &self.stack_address) .field("mem", &self.mem) - .field("num_cpus", &self.num_cpus) .field("path", &self.path) .field("boot_info", &self.boot_info) .field("virtio_device", &self.virtio_device) + .field("params", &self.params) .finish() } } From 2bca77a964fe18a2bc714bba917e95d6bc30cb08 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 19:42:15 +0100 Subject: [PATCH 5/6] Introduced output capture for UhyveVms. Fixes #528 --- src/bin/uhyve.rs | 2 ++ src/hypercall.rs | 67 ++++++++++++++++++++++--------------- src/linux/mod.rs | 18 ++++++++-- src/linux/x86_64/kvm_cpu.rs | 17 ++++++++-- src/macos/aarch64/vcpu.rs | 17 +++++++--- src/macos/x86_64/vcpu.rs | 20 +++++++++-- src/params.rs | 18 ++++++++++ src/vm.rs | 64 +++++++++++++++++++++++++++++++++-- tests/common.rs | 6 +++- tests/gdb.rs | 6 +++- tests/serial.rs | 48 ++++++++++++++++++++++++-- 11 files changed, 236 insertions(+), 47 deletions(-) diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index 53acee2a..fd78cf4f 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -255,6 +255,8 @@ impl From for Params { #[cfg(target_os = "macos")] gdb_port: None, kernel_args, + // TODO + output: Default::default(), } } } diff --git a/src/hypercall.rs b/src/hypercall.rs index 9e667f9d..47ba5f5d 100644 --- a/src/hypercall.rs +++ b/src/hypercall.rs @@ -1,6 +1,6 @@ use std::{ ffi::OsStr, - io::{self, Error, ErrorKind, Write}, + io::{self, Error, ErrorKind}, os::unix::ffi::OsStrExt, }; @@ -10,6 +10,7 @@ use crate::{ consts::BOOT_PML4, mem::{MemoryError, MmapMemory}, virt_to_phys, + vm::{UhyveVm, VirtualizationBackend}, }; /// `addr` is the address of the hypercall parameter in the guest's memory space. `data` is the @@ -119,23 +120,49 @@ pub fn read(mem: &MmapMemory, sysread: &mut ReadParams) { } /// Handles an write syscall on the host. -pub fn write(mem: &MmapMemory, syswrite: &WriteParams) -> io::Result<()> { +pub fn write( + parent_vm: &UhyveVm, + syswrite: &WriteParams, +) -> io::Result<()> { let mut bytes_written: usize = 0; while bytes_written != syswrite.len { + let guest_phys_addr = virt_to_phys( + syswrite.buf + bytes_written as u64, + &parent_vm.mem, + BOOT_PML4, + ) + .unwrap(); + + if syswrite.fd == 1 { + // fd 0 is stdout + let bytes = unsafe { + parent_vm + .mem + .slice_at(guest_phys_addr, syswrite.len) + .map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("invalid syswrite buffer: {e:?}"), + ) + })? + }; + return parent_vm.serial_output(bytes); + } + unsafe { let step = libc::write( syswrite.fd, - mem.host_address( - virt_to_phys(syswrite.buf + bytes_written as u64, mem, BOOT_PML4).unwrap(), - ) - .map_err(|e| match e { - MemoryError::BoundsViolation => { - unreachable!("Bounds violation after host_address function") - } - MemoryError::WrongMemoryError => { - Error::new(ErrorKind::AddrNotAvailable, e.to_string()) - } - })? as *const libc::c_void, + parent_vm + .mem + .host_address(guest_phys_addr) + .map_err(|e| match e { + MemoryError::BoundsViolation => { + unreachable!("Bounds violation after host_address function") + } + MemoryError::WrongMemoryError => { + Error::new(ErrorKind::AddrNotAvailable, e.to_string()) + } + })? as *const libc::c_void, syswrite.len - bytes_written, ); if step >= 0 { @@ -157,20 +184,6 @@ pub fn lseek(syslseek: &mut LseekParams) { } } -/// Handles an UART syscall by writing to stdout. -pub fn uart(buf: &[u8]) -> io::Result<()> { - io::stdout().write_all(buf) -} - -/// Handles a UART syscall by contructing a buffer from parameter -pub fn uart_buffer(sysuart: &SerialWriteBufferParams, mem: &MmapMemory) { - let buf = unsafe { - mem.slice_at(sysuart.buf, sysuart.len) - .expect("Systemcall parameters for SerialWriteBuffer are invalid") - }; - io::stdout().write_all(buf).unwrap() -} - /// Copies the arguments of the application into the VM's memory to the destinations specified in `syscmdval`. pub fn copy_argv(path: &OsStr, argv: &[String], syscmdval: &CmdvalParams, mem: &MmapMemory) { // copy kernel path as first argument diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 90583c66..5326c177 100755 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -29,7 +29,7 @@ use crate::{ x86_64::kvm_cpu::KvmVm, }, vcpu::VirtualCPU, - vm::{UhyveVm, VirtualizationBackend, VmResult}, + vm::{Output, UhyveVm, VirtualizationBackend, VmResult}, }; static KVM: LazyLock = LazyLock::new(|| Kvm::new().unwrap()); @@ -148,7 +148,13 @@ impl UhyveVm { 1 => code[0], _ => panic!("more than one thread finished with an exit code (codes: {code:?})"), }; - VmResult { code, None } + let output = if let Output::Buffer(b) = &this.output { + Some(b.lock().unwrap().clone()) + } else { + None + }; + + VmResult { code, output } } fn run_gdb(self, cpu_affinity: Option>) -> VmResult { @@ -189,7 +195,13 @@ impl UhyveVm { } }; - VmResult { code, None } + let output = if let Output::Buffer(b) = &this.output { + Some(b.lock().unwrap().clone()) + } else { + None + }; + + VmResult { code, output } } } diff --git a/src/linux/x86_64/kvm_cpu.rs b/src/linux/x86_64/kvm_cpu.rs index dd14ff74..158f0644 100644 --- a/src/linux/x86_64/kvm_cpu.rs +++ b/src/linux/x86_64/kvm_cpu.rs @@ -426,15 +426,26 @@ impl VirtualCPU for KvmCpu { hypercall::read(&self.parent_vm.mem, sysread) } Hypercall::FileWrite(syswrite) => { - hypercall::write(&self.parent_vm.mem, syswrite) + hypercall::write(&self.parent_vm, syswrite) .map_err(|_e| HypervisorError::new(libc::EFAULT))? } Hypercall::FileUnlink(sysunlink) => { hypercall::unlink(&self.parent_vm.mem, sysunlink) } - Hypercall::SerialWriteByte(buf) => hypercall::uart(&[buf])?, + Hypercall::SerialWriteByte(buf) => self + .parent_vm + .serial_output(&[buf]) + .unwrap_or_else(|e| error!("{e:?}")), Hypercall::SerialWriteBuffer(sysserialwrite) => { - hypercall::uart_buffer(sysserialwrite, &self.parent_vm.mem) + // safety: as this buffer is only read and not used afterwards, we don't create multiple aliasing + let buf = unsafe { + self.parent_vm.mem.slice_at(sysserialwrite.buf, sysserialwrite.len) + .expect("Systemcall parameters for SerialWriteBuffer are invalid") + }; + + self.parent_vm + .serial_output(buf) + .unwrap_or_else(|e| error!("{e:?}")) } _ => panic!("Got unknown hypercall {:?}", hypercall), }; diff --git a/src/macos/aarch64/vcpu.rs b/src/macos/aarch64/vcpu.rs index c8abbf1b..44e06a7f 100644 --- a/src/macos/aarch64/vcpu.rs +++ b/src/macos/aarch64/vcpu.rs @@ -188,11 +188,20 @@ impl VirtualCPU for XhyveCpu { match hypercall { Hypercall::SerialWriteByte(_char) => { let x8 = (self.vcpu.read_register(Register::X8)? & 0xFF) as u8; - - hypercall::uart(&[x8]).unwrap(); + self.parent_vm + .serial_output(&[x8]) + .unwrap_or_else(|e| error!("{e:?}")); } Hypercall::SerialWriteBuffer(sysserialwrite) => { - hypercall::uart_buffer(sysserialwrite, &self.parent_vm.mem) + // safety: as this buffer is only read and not used afterwards, we don't create multiple aliasing + let buf = unsafe { + self.parent_vm.mem.slice_at(sysserialwrite.buf, sysserialwrite.len) + .expect("Systemcall parameters for SerialWriteBuffer are invalid") + }; + + self.parent_vm + .serial_output(buf) + .unwrap_or_else(|e| error!("{e:?}")) } Hypercall::Exit(sysexit) => { return Ok(VcpuStopReason::Exit(sysexit.arg)); @@ -217,7 +226,7 @@ impl VirtualCPU for XhyveCpu { hypercall::read(&self.parent_vm.mem, sysread) } Hypercall::FileWrite(syswrite) => { - hypercall::write(&self.parent_vm.mem, syswrite).unwrap() + hypercall::write(&self.parent_vm, syswrite).unwrap() } Hypercall::FileUnlink(sysunlink) => { hypercall::unlink(&self.parent_vm.mem, sysunlink) diff --git a/src/macos/x86_64/vcpu.rs b/src/macos/x86_64/vcpu.rs index de4c56b4..04ca2b1c 100644 --- a/src/macos/x86_64/vcpu.rs +++ b/src/macos/x86_64/vcpu.rs @@ -762,15 +762,29 @@ impl VirtualCPU for XhyveCpu { hypercall::read(&self.parent_vm.mem, sysread) } Hypercall::FileWrite(syswrite) => { - hypercall::write(&self.parent_vm.mem, syswrite).unwrap() + hypercall::write(&self.parent_vm, syswrite).unwrap() } Hypercall::FileUnlink(sysunlink) => { hypercall::unlink(&self.parent_vm.mem, sysunlink) } - Hypercall::SerialWriteByte(buf) => hypercall::uart(&[buf]).unwrap(), + + Hypercall::SerialWriteByte(buf) => { + self.parent_vm + .serial_output(&[buf]) + .unwrap_or_else(|e| error!("{e:?}")); + } Hypercall::SerialWriteBuffer(sysserialwrite) => { - hypercall::uart_buffer(sysserialwrite, &self.parent_vm.mem) + // safety: as this buffer is only read and not used afterwards, we don't create multiple aliasing + let buf = unsafe { + self.parent_vm.mem.slice_at(sysserialwrite.buf, sysserialwrite.len) + .expect("Systemcall parameters for SerialWriteBuffer are invalid") + }; + + self.parent_vm + .serial_output(buf) + .unwrap_or_else(|e| error!("{e:?}")) } + _ => panic!("Got unknown hypercall {:?}", hypercall), } self.vcpu.write_register(&Register::RIP, rip + len)?; diff --git a/src/params.rs b/src/params.rs index ed242b3b..a62da5e8 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,6 +1,7 @@ use std::{ fmt, num::{NonZeroU32, ParseIntError, TryFromIntError}, + path::PathBuf, str::FromStr, }; @@ -32,6 +33,9 @@ pub struct Params { /// Arguments to forward to the kernel pub kernel_args: Vec, + + /// Kernel output handling + pub output: Output, } #[allow(clippy::derivable_impls)] @@ -48,6 +52,7 @@ impl Default for Params { cpu_count: Default::default(), gdb_port: Default::default(), kernel_args: Default::default(), + output: Default::default(), } } } @@ -119,6 +124,19 @@ impl fmt::Display for GuestMemorySize { } } +#[derive(Debug, Clone)] +pub enum Output { + StdIo, + File(PathBuf), + Buffer, + None, +} +impl Default for Output { + fn default() -> Self { + Self::StdIo + } +} + #[derive(Error, Debug)] pub enum InvalidGuestMemorySizeError { #[error( diff --git a/src/vm.rs b/src/vm.rs index e44ed780..511a5468 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,8 +1,10 @@ use std::{ - env, fmt, fs, io, + env, fmt, + fs::{self, File, OpenOptions}, + io::{self, Write}, num::NonZeroU32, path::PathBuf, - ptr, + ptr, str, sync::{Arc, Mutex}, time::SystemTime, }; @@ -25,7 +27,7 @@ use crate::{ fdt::Fdt, mem::MmapMemory, os::HypervisorError, - params::Params, + params::{self, Params}, virtio::*, }; @@ -117,6 +119,19 @@ pub struct VmResult { pub output: Option, } +#[derive(Debug)] +pub enum Output { + StdIo, + File(Arc>), + Buffer(Arc>), + None, +} +impl Default for Output { + fn default() -> Self { + Self::StdIo + } +} + pub struct UhyveVm { /// The starting position of the image in physical memory offset: u64, @@ -130,6 +145,7 @@ pub struct UhyveVm { pub(super) gdb_port: Option, pub(crate) virt_backend: VirtBackend, params: Params, + pub output: Output, } impl UhyveVm { pub fn new(kernel_path: PathBuf, params: Params) -> HypervisorResult> { @@ -159,6 +175,30 @@ impl UhyveVm { "gdbstub is only supported with one CPU" ); + let output = match params.output { + params::Output::None => Output::None, + params::Output::StdIo => Output::StdIo, + params::Output::Buffer => { + Output::Buffer(Arc::new(Mutex::new(String::with_capacity(8096)))) + } + params::Output::File(ref path) => { + let f = OpenOptions::new() + .read(false) + .write(true) + .create_new(true) + .open(path) + .map_err(|e| { + error!("Cant create kernel output file: {e}"); + // TODO: proper error handling + #[cfg(target_os = "macos")] + panic!(); + #[cfg(not(target_os = "macos"))] + e + })?; + Output::File(Arc::new(Mutex::new(f))) + } + }; + let mut vm = Self { offset: 0, entry_point: 0, @@ -170,6 +210,7 @@ impl UhyveVm { gdb_port: params.gdb_port, virt_backend, params, + output, }; vm.init_guest_mem(); @@ -177,6 +218,23 @@ impl UhyveVm { Ok(vm) } + pub fn serial_output(&self, buf: &[u8]) -> io::Result<()> { + match &self.output { + Output::StdIo => io::stdout().write_all(buf), + Output::None => Ok(()), + Output::Buffer(b) => { + b.lock().unwrap().push_str(str::from_utf8(buf).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("invalid UTF-8 bytes in output: {e:?}"), + ) + })?); + Ok(()) + } + Output::File(f) => f.lock().unwrap().write_all(buf), + } + } + /// Returns the section offsets relative to their base addresses pub fn get_offset(&self) -> u64 { self.offset diff --git a/tests/common.rs b/tests/common.rs index 7d449455..edabb7fe 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -5,7 +5,10 @@ use std::{ }; use byte_unit::{Byte, Unit}; -use uhyvelib::{params::Params, vm::{UhyveVm, VmResult}}; +use uhyvelib::{ + params::{Output, Params}, + vm::{UhyveVm, VmResult}, +}; /// Uses Cargo to build a kernel in the `tests/test-kernels` directory. /// Returns a path to the build binary. @@ -49,6 +52,7 @@ pub fn run_simple_vm(kernel_path: PathBuf) -> VmResult { .unwrap() .try_into() .unwrap(), + output: Output::Buffer, ..Default::default() }; UhyveVm::new(kernel_path, params).unwrap().run(None) diff --git a/tests/gdb.rs b/tests/gdb.rs index 861d0dae..d547619b 100644 --- a/tests/gdb.rs +++ b/tests/gdb.rs @@ -12,7 +12,10 @@ use std::{ use assert_fs::{assert::PathAssert, fixture::PathChild, TempDir}; use common::build_hermit_bin; -use uhyvelib::{params::Params, vm::UhyveVm}; +use uhyvelib::{ + params::{Output, Params}, + vm::UhyveVm, +}; #[test] fn gdb() -> io::Result<()> { @@ -26,6 +29,7 @@ fn gdb() -> io::Result<()> { bin_path, Params { gdb_port: Some(port), + output: Output::Buffer, ..Default::default() }, ) diff --git a/tests/serial.rs b/tests/serial.rs index b764fdea..1f86ff16 100644 --- a/tests/serial.rs +++ b/tests/serial.rs @@ -1,10 +1,54 @@ mod common; +use std::{ + fs::{read_to_string, remove_file}, + path::PathBuf, +}; + +use byte_unit::{Byte, Unit}; use common::{build_hermit_bin, run_simple_vm}; +use uhyvelib::{ + params::{Output, Params}, + vm::UhyveVm, +}; #[test] fn serial_buffer_test() { - // TODO: Check the output once https://github.com/hermit-os/uhyve/issues/528 is resolved let bin_path = build_hermit_bin("serial"); - run_simple_vm(bin_path); + let res = run_simple_vm(bin_path); + assert_eq!(res.code, 0); + println!("Kernel output: {:?}", res); + assert!(res + .output + .as_ref() + .unwrap() + .contains("Hello from serial!\nABCD\n1234ASDF!@#$\n")); +} + +#[test] +fn serial_file_output_test() { + env_logger::try_init().ok(); + let bin_path = build_hermit_bin("serial"); + let output_path: PathBuf = "testserialout.txt".into(); + + println!("Launching kernel {}", bin_path.display()); + let params = Params { + cpu_count: 2.try_into().unwrap(), + memory_size: Byte::from_u64_with_unit(32, Unit::MiB) + .unwrap() + .try_into() + .unwrap(), + output: Output::File(output_path.clone()), + ..Default::default() + }; + let vm = UhyveVm::new(bin_path, params).unwrap(); + let res = vm.run(None); + println!("Kernel output: {:?}", res); + assert_eq!(res.code, 0); + + assert!(output_path.exists()); + let file_content = read_to_string(&output_path).unwrap(); + assert!(file_content.contains("Hello from serial!\nABCD\n1234ASDF!@#$\n")); + + remove_file(&output_path).unwrap_or_else(|_| panic!("Can't remove {}", output_path.display())); } From 6111373735782365fc7e7ac7a03428d8b1ca5730 Mon Sep 17 00:00:00 2001 From: Jonathan Klimt Date: Wed, 20 Nov 2024 20:25:37 +0100 Subject: [PATCH 6/6] Added output redirection to uhyve's binary arguments --- src/bin/uhyve.rs | 15 +++++++++++++-- src/params.rs | 11 +++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/bin/uhyve.rs b/src/bin/uhyve.rs index fd78cf4f..3b59332a 100644 --- a/src/bin/uhyve.rs +++ b/src/bin/uhyve.rs @@ -7,7 +7,7 @@ use core_affinity::CoreId; use either::Either; use thiserror::Error; use uhyvelib::{ - params::{CpuCount, GuestMemorySize, Params}, + params::{CpuCount, GuestMemorySize, Output, Params}, vm::UhyveVm, }; @@ -38,6 +38,12 @@ fn setup_trace() { #[derive(Parser, Debug)] #[clap(version, author, about)] struct Args { + /// Kernel output redirection. + /// + /// None discards all output, Omit for stdout + #[clap(short, long, value_name = "FILE")] + output: Option, + #[clap(flatten, next_help_heading = "MEMORY")] memory_args: MemoryArgs, @@ -240,6 +246,7 @@ impl From for Params { gdb_port, kernel: _, kernel_args, + output, } = args; Self { memory_size, @@ -256,7 +263,11 @@ impl From for Params { gdb_port: None, kernel_args, // TODO - output: Default::default(), + output: if let Some(outp) = output { + Output::from_str(&outp).unwrap() + } else { + Output::StdIo + }, } } } diff --git a/src/params.rs b/src/params.rs index a62da5e8..1563cb5b 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,4 +1,5 @@ use std::{ + convert::Infallible, fmt, num::{NonZeroU32, ParseIntError, TryFromIntError}, path::PathBuf, @@ -136,6 +137,16 @@ impl Default for Output { Self::StdIo } } +impl FromStr for Output { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + match s { + "none" | "None" => Ok(Self::None), + p => Ok(Self::File(p.into())), + } + } +} #[derive(Error, Debug)] pub enum InvalidGuestMemorySizeError {