Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked uhyve output #796

Merged
merged 6 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
21 changes: 14 additions & 7 deletions src/bin/uhyve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -38,9 +38,11 @@ fn setup_trace() {
#[derive(Parser, Debug)]
#[clap(version, author, about)]
struct Args {
/// Print kernel messages
#[clap(short, long)]
verbose: bool,
/// Kernel output redirection.
///
/// None discards all output, Omit for stdout
#[clap(short, long, value_name = "FILE")]
output: Option<String>,

#[clap(flatten, next_help_heading = "MEMORY")]
memory_args: MemoryArgs,
Expand Down Expand Up @@ -225,7 +227,6 @@ impl CpuArgs {
impl From<Args> for Params {
fn from(args: Args) -> Self {
let Args {
verbose,
memory_args:
MemoryArgs {
memory_size,
Expand All @@ -245,9 +246,9 @@ impl From<Args> for Params {
gdb_port,
kernel: _,
kernel_args,
output,
} = args;
Self {
verbose,
memory_size,
#[cfg(target_os = "linux")]
thp,
Expand All @@ -261,6 +262,12 @@ impl From<Args> for Params {
#[cfg(target_os = "macos")]
gdb_port: None,
kernel_args,
// TODO
output: if let Some(outp) = output {
Output::from_str(&outp).unwrap()
} else {
Output::StdIo
},
}
}
}
Expand All @@ -280,7 +287,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() {
Expand Down
67 changes: 40 additions & 27 deletions src/hypercall.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
ffi::OsStr,
io::{self, Error, ErrorKind, Write},
io::{self, Error, ErrorKind},
os::unix::ffi::OsStrExt,
};

Expand All @@ -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
Expand Down Expand Up @@ -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<B: VirtualizationBackend>(
parent_vm: &UhyveVm<B>,
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 {
Expand All @@ -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
Expand Down
33 changes: 24 additions & 9 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::{
x86_64::kvm_cpu::KvmVm,
},
vcpu::VirtualCPU,
vm::{UhyveVm, VirtualizationBackend},
vm::{Output, UhyveVm, VirtualizationBackend, VmResult},
};

static KVM: LazyLock<Kvm> = LazyLock::new(|| Kvm::new().unwrap());
Expand Down Expand Up @@ -71,7 +71,7 @@ impl UhyveVm<KvmVm> {
/// Runs the VM.
///
/// Blocks until the VM has finished execution.
pub fn run(mut self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
pub fn run(mut self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
KickSignal::register_handler().unwrap();

self.load_kernel().expect("Unabled to load the kernel");
Expand All @@ -83,7 +83,7 @@ impl UhyveVm<KvmVm> {
}
}

fn run_no_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
fn run_no_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
// After spinning up all vCPU threads, the main thread waits for any vCPU to end execution.
let barrier = Arc::new(Barrier::new(2));

Expand Down Expand Up @@ -143,14 +143,21 @@ impl UhyveVm<KvmVm> {
.into_iter()
.filter_map(|thread| thread.join().unwrap())
.collect::<Vec<_>>();
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:?})"),
}
};
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<Vec<CoreId>>) -> i32 {
fn run_gdb(self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
let cpu_id = 0;

let local_cpu_affinity = cpu_affinity
Expand All @@ -170,9 +177,9 @@ impl UhyveVm<KvmVm> {

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::<UhyveGdbEventLoop>(&mut debuggable_vcpu)
.unwrap()
{
Expand All @@ -186,7 +193,15 @@ impl UhyveVm<KvmVm> {
eprintln!("Kill command received.");
0
}
}
};

let output = if let Output::Buffer(b) = &this.output {
Some(b.lock().unwrap().clone())
} else {
None
};

VmResult { code, output }
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/linux/x86_64/kvm_cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UhyveVm<KvmVm>>) -> HypervisorResult<KvmCpu> {
let vcpu = self.vm_fd.create_vcpu(id as u64)?;
let mut kvcpu = KvmCpu {
Expand Down Expand Up @@ -424,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),
};
Expand Down
19 changes: 15 additions & 4 deletions src/macos/aarch64/vcpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UhyveVm<XhyveVm>>) -> HypervisorResult<XhyveCpu> {
let mut vcpu = XhyveCpu {
id,
Expand Down Expand Up @@ -186,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));
Expand All @@ -215,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)
Expand Down
9 changes: 6 additions & 3 deletions src/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,7 +26,7 @@ impl UhyveVm<XhyveVm> {
/// Runs the VM.
///
/// Blocks until the VM has finished execution.
pub fn run(mut self, cpu_affinity: Option<Vec<CoreId>>) -> i32 {
pub fn run(mut self, cpu_affinity: Option<Vec<CoreId>>) -> VmResult {
self.load_kernel().expect("Unabled to load the kernel");

// For communication of the exit code from one vcpu to this thread as return
Expand Down Expand Up @@ -75,6 +75,9 @@ impl UhyveVm<XhyveVm> {
// 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,
}
}
}
Loading