From ac0b9b71d142f381d39162a1ac52c7d143700a1b Mon Sep 17 00:00:00 2001 From: Matt Delco Date: Thu, 19 Sep 2019 10:30:41 -0700 Subject: crosvm: pre-cache answers to plugin get calls This change tries to improve the performance of a plugin-based VM by adding a hint API that allows crosvm to proactively push cpu state to the plugin when certain ports for hypercalls are accessed by the VM. BUG=None TEST=build and run. See performance increase significantly. Change-Id: I71af24ebc034095ffea42eedb9ffda0afc719cd6 Signed-off-by: Matt Delco Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1873005 Tested-by: kokoro Reviewed-by: Zach Reizner --- crosvm_plugin/crosvm.h | 75 +++++++++++- crosvm_plugin/src/lib.rs | 160 +++++++++++++++++++++++- protos/src/plugin.proto | 29 +++++ src/plugin/process.rs | 32 +++++ src/plugin/vcpu.rs | 87 ++++++++++++- tests/plugin.policy | 2 + tests/plugin_hint.c | 313 +++++++++++++++++++++++++++++++++++++++++++++++ tests/plugins.rs | 5 + 8 files changed, 692 insertions(+), 11 deletions(-) create mode 100644 tests/plugin_hint.c diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h index d7a036c..700ab0c 100644 --- a/crosvm_plugin/crosvm.h +++ b/crosvm_plugin/crosvm.h @@ -47,7 +47,7 @@ extern "C" { * do not indicate anything about what version of crosvm is running. */ #define CROSVM_API_MAJOR 0 -#define CROSVM_API_MINOR 17 +#define CROSVM_API_MINOR 18 #define CROSVM_API_PATCH 0 enum crosvm_address_space { @@ -229,6 +229,79 @@ static_assert(sizeof(struct crosvm_irq_route) == 24, int crosvm_set_irq_routing(struct crosvm*, uint32_t __route_count, const struct crosvm_irq_route* __routes); +/* Hint on what information is queried for a particular hypercall. */ +struct crosvm_hint_detail { + bool match_rax; + bool match_rbx; + bool match_rcx; + bool match_rdx; + uint8_t _reserved[4]; + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; + bool send_sregs; + bool send_debugregs; + uint8_t _reserved2[6]; +}; + +#ifdef static_assert +static_assert(sizeof(struct crosvm_hint_detail) == 48, + "extra padding in struct crosvm_hint_detail"); +#endif + +/* Maximum # of hints that can be passed to crosvm_set_hypercall_hint(). */ +#define CROSVM_MAX_HINT_COUNT 1 + +/* Maximum # of hint details that can be provided for a hint. */ +#define CROSVM_MAX_HINT_DETAIL_COUNT 32 + +#define CROSVM_HINT_ON_WRITE 0x1 + +/* Hint on what information is queried for a particular hypercall. */ +struct crosvm_hint { + uint32_t hint_version; /* For now only 0 is defined. */ + uint32_t _reserved; /* Must be zero. */ + uint32_t address_space; /* Value from crosvm_address_space. */ + uint16_t address_flags; /* 0: read/in; CROSVM_HINT_ON_WRITE: write/out. */ + uint16_t details_count; /* # of elements in |details|. */ + uint64_t address; + union { + struct crosvm_hint_detail *details; + uint64_t _reserved2; /* forcing pointer length to 64-bit */ + }; +}; + +#ifdef static_assert +static_assert(sizeof(struct crosvm_hint) == 32, + "extra padding in struct crosvm_hint"); +#endif + +/* + * Sets performance hint(s) for a hypercall port. + * + * If a VM does an io access the specified |address_space|, |address| + * (|address| must be non-zero), and direction (|address_flags|), then + * crosvm will assume the plugin is likely to call crosvm_vcpu_get_regs() + * (and thus utilize a cache to improve performance). + * + * Additional hints can be provided via |details| (the element length of + * |details| is limited to CROSVM_MAX_HINT_DETAIL_COUNT) on when to also cache + * information for crosvm_vcpu_get_sregs() and crosvm_vcpu_get_debugregs() + * based on values in the vcpu registers. |match_XXX| indicates which of + * 1 or more of |XXX| needs to be equal to the vcpu registers to be a match. + * On a match |send_sregs| and |send_debugregs| are used to determine what + * data to proactively cache for the plugin's use. Once a match is found + * the remaining hints are not consulted. + * + * To remove all hints, pass 0 for |__hint_count|. The value of + * |__hint_count| can be at most CROSVM_MAX_HINT_COUNT. Currently the API + * is limited to 1 hint (i.e., |__hint_count| must be 0 or 1). Each call + * to this API will replace the values specified by any prior call to this API. + */ +int crosvm_set_hypercall_hint(struct crosvm *, uint32_t __hints_count, + const struct crosvm_hint* __hints); + /* Gets the state of interrupt controller in a VM. */ int crosvm_get_pic_state(struct crosvm *, bool __primary, struct kvm_pic_state *__pic_state); diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs index 1eb22b5..fa6ce2f 100644 --- a/crosvm_plugin/src/lib.rs +++ b/crosvm_plugin/src/lib.rs @@ -96,6 +96,37 @@ pub struct crosvm_irq_route { route: anon_route, } +const CROSVM_MAX_HINT_COUNT: u32 = 1; +const CROSVM_MAX_HINT_DETAIL_COUNT: u32 = 32; +const CROSVM_HINT_ON_WRITE: u16 = 1; + +#[repr(C)] +pub struct crosvm_hint { + hint_version: u32, + reserved: u32, + address_space: u32, + address_flags: u16, + details_count: u16, + address: u64, + details: *const crosvm_hint_detail, +} + +#[repr(C)] +pub struct crosvm_hint_detail { + match_rax: bool, + match_rbx: bool, + match_rcx: bool, + match_rdx: bool, + reserved1: [u8; 4], + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + send_sregs: bool, + send_debugregs: bool, + reserved2: [u8; 6], +} + fn proto_error_to_int(e: protobuf::ProtobufError) -> c_int { match e { protobuf::ProtobufError::IoError(e) => e.raw_os_error().unwrap_or(EINVAL), @@ -174,6 +205,7 @@ enum Stat { VcpuGetVcpuEvents, VcpuSetVcpuEvents, NewConnection, + SetHypercallHint, Count, } @@ -543,6 +575,38 @@ impl crosvm { Ok(()) } + fn set_hint( + &mut self, + space: u32, + addr: u64, + on_write: bool, + hints: &[crosvm_hint_detail], + ) -> result::Result<(), c_int> { + let mut r = MainRequest::new(); + let req: &mut MainRequest_SetCallHint = r.mut_set_call_hint(); + let set_hints: &mut RepeatedField = req.mut_hints(); + for hint in hints { + let mut entry = MainRequest_SetCallHint_RegHint::new(); + entry.match_rax = hint.match_rax; + entry.match_rbx = hint.match_rbx; + entry.match_rcx = hint.match_rcx; + entry.match_rdx = hint.match_rdx; + entry.rax = hint.rax; + entry.rbx = hint.rbx; + entry.rcx = hint.rcx; + entry.rdx = hint.rdx; + entry.send_sregs = hint.send_sregs; + entry.send_debugregs = hint.send_debugregs; + set_hints.push(entry); + } + req.space = AddressSpace::from_i32(space as i32).ok_or(EINVAL)?; + req.address = addr; + req.on_write = on_write; + + self.main_transaction(&r, &[])?; + Ok(()) + } + fn get_state( &mut self, state_set: MainRequest_StateSet, @@ -922,7 +986,17 @@ pub struct crosvm_vcpu_event { event: anon_vcpu_event, } +// |get| tracks if the |cache| contains a cached value that can service get() +// requests. A set() call will populate |cache| and |set| to true to record +// that the next resume() should apply the state. We've got two choices on +// what to do about |get| on a set(): 1) leave it as true, or 2) clear it and +// have any call to get() first apply any pending set. Currently #2 is used +// to favor correctness over performance (it gives KVM a chance to +// modify/massage the values input to the set call). A plugin will rarely +// (if ever) issue a get() after a set() on the same vcpu exit, so opting for +// #1 is unlikely to provide a tangible performance gain. pub struct crosvm_vcpu_reg_cache { + get: bool, set: bool, cache: Vec, } @@ -950,14 +1024,17 @@ impl crosvm_vcpu { response_buffer: vec![0; MAX_DATAGRAM_SIZE], resume_data: Vec::new(), regs: crosvm_vcpu_reg_cache { + get: false, set: false, cache: vec![], }, sregs: crosvm_vcpu_reg_cache { + get: false, set: false, cache: vec![], }, debugregs: crosvm_vcpu_reg_cache { + get: false, set: false, cache: vec![], }, @@ -1008,6 +1085,9 @@ impl crosvm_vcpu { let wait: &mut VcpuResponse_Wait = response.mut_wait(); if wait.has_init() { event.kind = CROSVM_VCPU_EVENT_KIND_INIT; + self.regs.get = false; + self.sregs.get = false; + self.debugregs.get = false; Ok(()) } else if wait.has_io() { let mut io: VcpuResponse_Wait_Io = wait.take_io(); @@ -1022,11 +1102,26 @@ impl crosvm_vcpu { __reserved1: Default::default(), }; self.resume_data = io.data; + self.regs.get = !io.regs.is_empty(); + if self.regs.get { + swap(&mut self.regs.cache, &mut io.regs); + } + self.sregs.get = !io.sregs.is_empty(); + if self.sregs.get { + swap(&mut self.sregs.cache, &mut io.sregs); + } + self.debugregs.get = !io.debugregs.is_empty(); + if self.debugregs.get { + swap(&mut self.debugregs.cache, &mut io.debugregs); + } Ok(()) } else if wait.has_user() { let user: &VcpuResponse_Wait_User = wait.get_user(); event.kind = CROSVM_VCPU_EVENT_KIND_PAUSED; event.event.user = user.user as *mut c_void; + self.regs.get = false; + self.sregs.get = false; + self.debugregs.get = false; Ok(()) } else { Err(EPROTO) @@ -1346,6 +1441,41 @@ pub unsafe extern "C" fn crosvm_set_irq_routing( to_crosvm_rc(ret) } +#[no_mangle] +pub unsafe extern "C" fn crosvm_set_hypercall_hint( + self_: *mut crosvm, + hints_count: u32, + hints: *const crosvm_hint, +) -> c_int { + let _u = STATS.record(Stat::SetHypercallHint); + let self_ = &mut (*self_); + + if hints_count < 1 { + let ret = self_.set_hint(0, 0, false, &[]); + return to_crosvm_rc(ret); + } + if hints_count > CROSVM_MAX_HINT_COUNT { + return -EINVAL; + } + let hints = slice::from_raw_parts(hints, hints_count as usize); + let hint = &hints[0]; + if hint.hint_version != 0 + || hint.reserved != 0 + || hint.address == 0 + || (hint.address_flags != 0 && hint.address_flags != CROSVM_HINT_ON_WRITE) + || hint.details_count > CROSVM_MAX_HINT_DETAIL_COUNT as u16 + { + return -EINVAL; + } + let ret = self_.set_hint( + hint.address_space, + hint.address, + hint.address_flags == CROSVM_HINT_ON_WRITE, + slice::from_raw_parts(hint.details, hint.details_count as usize), + ); + return to_crosvm_rc(ret); +} + #[no_mangle] pub unsafe extern "C" fn crosvm_get_pic_state( this: *mut crosvm, @@ -1531,8 +1661,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_regs( } } let regs = from_raw_parts_mut(regs as *mut u8, size_of::()); - let ret = this.get_state(VcpuRequest_StateSet::REGS, regs); - to_crosvm_rc(ret) + if this.regs.get { + regs.copy_from_slice(&this.regs.cache); + 0 + } else { + let ret = this.get_state(VcpuRequest_StateSet::REGS, regs); + to_crosvm_rc(ret) + } } #[no_mangle] @@ -1542,6 +1677,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_regs( ) -> c_int { let _u = STATS.record(Stat::VcpuSetRegs); let this = &mut *this; + this.regs.get = false; let regs = from_raw_parts(regs as *mut u8, size_of::()); this.regs.set = true; this.regs.cache = regs.to_vec(); @@ -1561,8 +1697,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_sregs( } } let sregs = from_raw_parts_mut(sregs as *mut u8, size_of::()); - let ret = this.get_state(VcpuRequest_StateSet::SREGS, sregs); - to_crosvm_rc(ret) + if this.sregs.get { + sregs.copy_from_slice(&this.sregs.cache); + 0 + } else { + let ret = this.get_state(VcpuRequest_StateSet::SREGS, sregs); + to_crosvm_rc(ret) + } } #[no_mangle] @@ -1572,6 +1713,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_sregs( ) -> c_int { let _u = STATS.record(Stat::VcpuSetSregs); let this = &mut *this; + this.sregs.get = false; let sregs = from_raw_parts(sregs as *mut u8, size_of::()); this.sregs.set = true; this.sregs.cache = sregs.to_vec(); @@ -1609,8 +1751,13 @@ pub unsafe extern "C" fn crosvm_vcpu_get_debugregs( } } let dregs = from_raw_parts_mut(dregs as *mut u8, size_of::()); - let ret = this.get_state(VcpuRequest_StateSet::DEBUGREGS, dregs); - to_crosvm_rc(ret) + if this.debugregs.get { + dregs.copy_from_slice(&this.debugregs.cache); + 0 + } else { + let ret = this.get_state(VcpuRequest_StateSet::DEBUGREGS, dregs); + to_crosvm_rc(ret) + } } #[no_mangle] @@ -1620,6 +1767,7 @@ pub unsafe extern "C" fn crosvm_vcpu_set_debugregs( ) -> c_int { let _u = STATS.record(Stat::SetDebugRegs); let this = &mut *this; + this.debugregs.get = false; let dregs = from_raw_parts(dregs as *mut u8, size_of::()); this.debugregs.set = true; this.debugregs.cache = dregs.to_vec(); diff --git a/protos/src/plugin.proto b/protos/src/plugin.proto index b000bf4..7b0600d 100644 --- a/protos/src/plugin.proto +++ b/protos/src/plugin.proto @@ -151,6 +151,27 @@ message MainRequest { message GetVcpus {} message Start {} + message SetCallHint { + message RegHint { + bool match_rax = 1; + bool match_rbx = 2; + bool match_rcx = 3; + bool match_rdx = 4; + uint64 rax = 5; + uint64 rbx = 6; + uint64 rcx = 7; + uint64 rdx = 8; + bool send_sregs = 9; + bool send_debugregs = 10; + } + + AddressSpace space = 1; + uint64 address = 2; + bool on_write = 3; + + repeated RegHint hints = 4; + } + message MemoryDirtyLog { uint32 id = 1; } @@ -178,6 +199,7 @@ message MainRequest { PauseVcpus pause_vcpus = 16; GetVcpus get_vcpus = 17; Start start = 18; + SetCallHint set_call_hint = 19; // Method for a Memory type object for retrieving the dirty bitmap. Only valid if the memory // object was created with dirty_log set. MemoryDirtyLog dirty_log = 101; @@ -225,6 +247,7 @@ message MainResponse { // socket. The VcpuRequest/VcpuResponse protocol is run over each of the returned fds. message GetVcpus {} message Start {} + message SetCallHint {} message MemoryDirtyLog { bytes bitmap = 1; } @@ -252,6 +275,7 @@ message MainResponse { PauseVcpus pause_vcpus = 17; GetVcpus get_vcpus = 18; Start start = 19; + SetCallHint set_call_hint = 20; MemoryDirtyLog dirty_log = 101; } } @@ -361,6 +385,11 @@ message VcpuResponse { uint64 address = 2; bool is_write = 3; bytes data = 4; + + // The following can be eagerly provided. + bytes regs = 5; + bytes sregs = 6; + bytes debugregs = 7; } // This type of wait reason is only generated after a PuaseVcpus request on this VCPU. diff --git a/src/plugin/process.rs b/src/plugin/process.rs index 50b4465..2869847 100644 --- a/src/plugin/process.rs +++ b/src/plugin/process.rs @@ -418,6 +418,35 @@ impl Process { vm.set_gsi_routing(&routes[..]) } + fn handle_set_call_hint(&mut self, hints: &MainRequest_SetCallHint) -> SysResult<()> { + let mut regs: Vec = vec![]; + for hint in &hints.hints { + regs.push(CallHintDetails { + match_rax: hint.match_rax, + match_rbx: hint.match_rbx, + match_rcx: hint.match_rcx, + match_rdx: hint.match_rdx, + rax: hint.rax, + rbx: hint.rbx, + rcx: hint.rcx, + rdx: hint.rdx, + send_sregs: hint.send_sregs, + send_debugregs: hint.send_debugregs, + }); + } + match self.shared_vcpu_state.write() { + Ok(mut lock) => { + let space = match hints.space { + AddressSpace::IOPORT => IoSpace::Ioport, + AddressSpace::MMIO => IoSpace::Mmio, + }; + lock.set_hint(space, hints.address, hints.on_write, regs); + Ok(()) + } + Err(_) => Err(SysError::new(EDEADLK)), + } + } + fn handle_pause_vcpus(&self, vcpu_handles: &[JoinHandle<()>], cpu_mask: u64, user_data: u64) { for (cpu_id, (handle, per_cpu_state)) in vcpu_handles.iter().zip(&self.per_vcpu_states).enumerate() @@ -631,6 +660,9 @@ impl Process { } None => Err(SysError::new(ENODATA)), } + } else if request.has_set_call_hint() { + response.mut_set_call_hint(); + self.handle_set_call_hint(request.get_set_call_hint()) } else if request.has_dirty_log() { let dirty_log_response = response.mut_dirty_log(); match self.objects.get(&request.get_dirty_log().id) { diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs index 05970f7..03d63b4 100644 --- a/src/plugin/vcpu.rs +++ b/src/plugin/vcpu.rs @@ -30,7 +30,7 @@ use sys_util::{error, LayoutAllocation}; use super::*; /// Identifier for an address space in the VM. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq)] pub enum IoSpace { Ioport, Mmio, @@ -137,11 +137,32 @@ fn set_vcpu_state(vcpu: &Vcpu, state_set: VcpuRequest_StateSet, state: &[u8]) -> } } +pub struct CallHintDetails { + pub match_rax: bool, + pub match_rbx: bool, + pub match_rcx: bool, + pub match_rdx: bool, + pub rax: u64, + pub rbx: u64, + pub rcx: u64, + pub rdx: u64, + pub send_sregs: bool, + pub send_debugregs: bool, +} + +pub struct CallHint { + io_space: IoSpace, + addr: u64, + on_write: bool, + regs: Vec, +} + /// State shared by every VCPU, grouped together to make edits to the state coherent across VCPUs. #[derive(Default)] pub struct SharedVcpuState { ioport_regions: BTreeSet, mmio_regions: BTreeSet, + hint: Option, } impl SharedVcpuState { @@ -191,6 +212,26 @@ impl SharedVcpuState { } } + pub fn set_hint( + &mut self, + space: IoSpace, + addr: u64, + on_write: bool, + regs: Vec, + ) { + if addr == 0 { + self.hint = None; + } else { + let hint = CallHint { + io_space: space, + addr, + on_write, + regs, + }; + self.hint = Some(hint); + } + } + fn is_reserved(&self, space: IoSpace, addr: u64) -> bool { if let Some(Range(start, len)) = self.first_before(space, addr) { let offset = addr - start; @@ -212,6 +253,28 @@ impl SharedVcpuState { None => None, } } + + fn matches_hint(&self, io_space: IoSpace, addr: u64, is_write: bool) -> bool { + if let Some(hint) = &self.hint { + return io_space == hint.io_space && addr == hint.addr && is_write == hint.on_write; + } + false + } + + fn check_hint_details(&self, regs: &kvm_regs) -> (bool, bool) { + if let Some(hint) = &self.hint { + for entry in hint.regs.iter() { + if (!entry.match_rax || entry.rax == regs.rax) + && (!entry.match_rbx || entry.rbx == regs.rbx) + && (!entry.match_rcx || entry.rcx == regs.rcx) + && (!entry.match_rdx || entry.rdx == regs.rdx) + { + return (entry.send_sregs, entry.send_debugregs); + } + } + } + (false, false) + } } /// State specific to a VCPU, grouped so that each `PluginVcpu` object will share a canonical @@ -338,9 +401,6 @@ impl PluginVcpu { }; let first_before_addr = vcpu_state_lock.first_before(io_space, addr); - // Drops the read lock as soon as possible, to prevent holding lock while blocked in - // `handle_until_resume`. - drop(vcpu_state_lock); match first_before_addr { Some(Range(start, len)) => { @@ -358,6 +418,25 @@ impl PluginVcpu { io.address = addr; io.is_write = data.is_write(); io.data = data.as_slice().to_vec(); + if vcpu_state_lock.matches_hint(io_space, addr, io.is_write) { + if let Ok(regs) = vcpu.get_regs() { + let (has_sregs, has_debugregs) = vcpu_state_lock.check_hint_details(®s); + io.regs = VcpuRegs(regs).as_slice().to_vec(); + if has_sregs { + if let Ok(state) = get_vcpu_state(vcpu, VcpuRequest_StateSet::SREGS) { + io.sregs = state; + } + } + if has_debugregs { + if let Ok(state) = get_vcpu_state(vcpu, VcpuRequest_StateSet::DEBUGREGS) + { + io.debugregs = state; + } + } + } + } + // don't hold lock while blocked in `handle_until_resume`. + drop(vcpu_state_lock); self.wait_reason.set(Some(wait_reason)); match self.handle_until_resume(vcpu) { diff --git a/tests/plugin.policy b/tests/plugin.policy index a3ab2f7..19e7102 100644 --- a/tests/plugin.policy +++ b/tests/plugin.policy @@ -8,6 +8,7 @@ dup2: 1 execve: 1 exit_group: 1 futex: 1 +kill: 1 lseek: 1 mprotect: 1 munmap: 1 @@ -22,6 +23,7 @@ write: 1 eventfd2: 1 poll: 1 getpid: 1 +getppid: 1 # Allow PR_SET_NAME only. prctl: arg0 == 15 access: 1 diff --git a/tests/plugin_hint.c b/tests/plugin_hint.c new file mode 100644 index 0000000..f8d6310 --- /dev/null +++ b/tests/plugin_hint.c @@ -0,0 +1,313 @@ +/* + * Copyright 2019 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crosvm.h" + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#endif + +#ifndef F_SEAL_SHRINK +#define F_SEAL_SHRINK 0x0002 +#endif + +#define KILL_ADDRESS 0x3f9 +#define HINT_ADDRESS 0x500 +#define EAX_HINT_VALUE 0x77 + +int g_kill_evt; +int got_regs = 0; + +void *vcpu_thread(void *arg) { + struct crosvm_vcpu *vcpu = arg; + struct crosvm_vcpu_event evt; + while (crosvm_vcpu_wait(vcpu, &evt) == 0) { + if (evt.kind == CROSVM_VCPU_EVENT_KIND_INIT) { + struct kvm_sregs sregs; + crosvm_vcpu_get_sregs(vcpu, &sregs); + sregs.cs.base = 0; + sregs.cs.selector = 0; + sregs.es.base = KILL_ADDRESS; + sregs.es.selector = 0; + crosvm_vcpu_set_sregs(vcpu, &sregs); + + struct kvm_regs regs; + crosvm_vcpu_get_regs(vcpu, ®s); + regs.rip = 0x1000; + regs.rax = 2; + regs.rbx = 7; + regs.rflags = 2; + crosvm_vcpu_set_regs(vcpu, ®s); + } + if (evt.kind == CROSVM_VCPU_EVENT_KIND_IO_ACCESS) { + if (evt.io_access.address_space == CROSVM_ADDRESS_SPACE_IOPORT && + evt.io_access.address == HINT_ADDRESS && + evt.io_access.is_write && + evt.io_access.length == 1) { + struct kvm_regs regs = {0}; + struct kvm_sregs sregs = {0}; + struct kvm_debugregs debugregs = {0}; + + /* + * In a properly running test the following + * get and set calls will return success despite + * crosvm being halted. + */ + if (kill(getppid(), SIGSTOP)) { + fprintf(stderr, "failed to send stop to crosvm\n"); + exit(1); + } + + printf("get regs query on crosvm\n"); + if (crosvm_vcpu_get_regs(vcpu, ®s)) { + /* + * The failure mode for this test is that crosvm remains + * halted (since the plugin hasn't returned from + * crosvm_vcpu_[g|s]et_regs() to resume crosvm) and + * the test times out. + */ + fprintf(stderr, "failed to query regs on hint port\n"); + exit(1); + } + + printf("set regs query on crosvm\n"); + if (crosvm_vcpu_set_regs(vcpu, ®s)) { + fprintf(stderr, "failed to set regs on hint port\n"); + exit(1); + } + + printf("get sregs query on crosvm\n"); + if (crosvm_vcpu_get_sregs(vcpu, &sregs)) { + fprintf(stderr, "failed to query sregs on hint port\n"); + exit(1); + } + printf("set sregs query on crosvm\n"); + if (crosvm_vcpu_set_sregs(vcpu, &sregs)) { + fprintf(stderr, "failed to set sregs on hint port\n"); + exit(1); + } + + printf("get debugregs query on crosvm\n"); + if (crosvm_vcpu_get_debugregs(vcpu, &debugregs)) { + fprintf(stderr, "failed to query debugregs on hint port\n"); + exit(1); + } + printf("set debugregs query on crosvm\n"); + if (crosvm_vcpu_set_debugregs(vcpu, &debugregs)) { + fprintf(stderr, "failed to set debugregs on hint port\n"); + exit(1); + } + + got_regs = 1; + + if (kill(getppid(), SIGCONT)) { + fprintf(stderr, "failed to send continue to crosvm\n"); + exit(1); + } + } + if (evt.io_access.address_space == CROSVM_ADDRESS_SPACE_IOPORT && + evt.io_access.address == KILL_ADDRESS && + evt.io_access.is_write && + evt.io_access.length == 1 && + evt.io_access.data[0] == 1) + { + uint64_t dummy = 1; + write(g_kill_evt, &dummy, sizeof(dummy)); + return NULL; + } + } + + crosvm_vcpu_resume(vcpu); + } + + return NULL; +} + +int main(int argc, char** argv) { + const uint8_t code[] = { + /* + B007 mov al,0x7 + BA0005 mov dx,0x500 + EE out dx,al + BAF903 mov dx,0x3f9 + B001 mov al,0x1 + EE out dx,al + F4 hlt + */ + 0xb0, EAX_HINT_VALUE, + 0xba, (HINT_ADDRESS & 0xFF), ((HINT_ADDRESS >> 8) & 0xFF), + 0xee, + 0xba, (KILL_ADDRESS & 0xFF), ((KILL_ADDRESS >> 8) & 0xFF), + 0xb0, 0x01, + 0xee, + 0xf4 + }; + + struct crosvm *crosvm; + int ret = crosvm_connect(&crosvm); + if (ret) { + fprintf(stderr, "failed to connect to crosvm: %d\n", ret); + return 1; + } + + /* + * Not strictly necessary, but demonstrates we can have as many connections + * as we please. + */ + struct crosvm *extra_crosvm; + ret = crosvm_new_connection(crosvm, &extra_crosvm); + if (ret) { + fprintf(stderr, "failed to make new socket: %d\n", ret); + return 1; + } + + /* We needs this eventfd to know when to exit before being killed. */ + g_kill_evt = crosvm_get_shutdown_eventfd(crosvm); + if (g_kill_evt < 0) { + fprintf(stderr, "failed to get kill eventfd: %d\n", g_kill_evt); + return 1; + } + + ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT, + HINT_ADDRESS, 1); + if (ret) { + fprintf(stderr, "failed to reserve hint ioport range: %d\n", ret); + return 1; + } + + ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT, + KILL_ADDRESS, 1); + if (ret) { + fprintf(stderr, "failed to reserve kill ioport range: %d\n", ret); + return 1; + } + + struct crosvm_hint_detail details = {0}; + details.match_rax = 1; + details.rax = EAX_HINT_VALUE; + details.send_sregs = 1; + details.send_debugregs = 1; + + struct crosvm_hint hint = {0}; + hint.address_space = CROSVM_ADDRESS_SPACE_IOPORT; + hint.address = HINT_ADDRESS; + hint.address_flags = CROSVM_HINT_ON_WRITE; + hint.details_count = 1; + hint.details = &details; + + ret = crosvm_set_hypercall_hint(crosvm, 1, &hint); + if (ret) { + fprintf(stderr, "failed to set hypercall hint: %d\n", ret); + return 1; + } + + int mem_size = 0x2000; + int mem_fd = syscall(SYS_memfd_create, "guest_mem", + MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (mem_fd < 0) { + fprintf(stderr, "failed to create guest memfd: %d\n", errno); + return 1; + } + ret = ftruncate(mem_fd, mem_size); + if (ret) { + fprintf(stderr, "failed to set size of guest memory: %d\n", errno); + return 1; + } + uint8_t *mem = mmap(NULL, mem_size, PROT_READ | PROT_WRITE, MAP_SHARED, + mem_fd, 0x1000); + if (mem == MAP_FAILED) { + fprintf(stderr, "failed to mmap guest memory: %d\n", errno); + return 1; + } + fcntl(mem_fd, F_ADD_SEALS, F_SEAL_SHRINK); + memcpy(mem, code, sizeof(code)); + + struct crosvm_memory *mem_obj; + ret = crosvm_create_memory(crosvm, mem_fd, 0x1000, 0x1000, 0x1000, false, + false, &mem_obj); + if (ret) { + fprintf(stderr, "failed to create memory in crosvm: %d\n", ret); + return 1; + } + + /* get and creat a thread for each vcpu */ + struct crosvm_vcpu *vcpus[32]; + pthread_t vcpu_threads[32]; + uint32_t vcpu_count; + for (vcpu_count = 0; vcpu_count < 32; vcpu_count++) { + ret = crosvm_get_vcpu(crosvm, vcpu_count, &vcpus[vcpu_count]); + if (ret == -ENOENT) + break; + + if (ret) { + fprintf(stderr, "error while getting all vcpus: %d\n", ret); + return 1; + } + pthread_create(&vcpu_threads[vcpu_count], NULL, vcpu_thread, + vcpus[vcpu_count]); + } + + ret = crosvm_start(extra_crosvm); + if (ret) { + fprintf(stderr, "failed to tell crosvm to start: %d\n", ret); + return 1; + } + + /* Wait for crosvm to request that we exit otherwise we will be killed. */ + uint64_t dummy; + read(g_kill_evt, &dummy, 8); + + ret = crosvm_destroy_memory(crosvm, &mem_obj); + if (ret) { + fprintf(stderr, "failed to destroy memory in crosvm: %d\n", ret); + return 1; + } + + ret = crosvm_set_hypercall_hint(crosvm, 0, NULL); + if (ret) { + fprintf(stderr, "failed to clear hypercall hint: %d\n", ret); + return 1; + } + + ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT, + HINT_ADDRESS, 0); + if (ret) { + fprintf(stderr, "failed to unreserve hint ioport range: %d\n", ret); + return 1; + } + + ret = crosvm_reserve_range(crosvm, CROSVM_ADDRESS_SPACE_IOPORT, + KILL_ADDRESS, 0); + if (ret) { + fprintf(stderr, "failed to unreserve kill ioport range: %d\n", ret); + return 1; + } + + if (!got_regs) { + fprintf(stderr, "vm ran to completion without reg query\n"); + return 1; + } + + return 0; +} diff --git a/tests/plugins.rs b/tests/plugins.rs index 50abea8..47b0ca3 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -225,6 +225,11 @@ fn test_adder() { test_plugin(include_str!("plugin_adder.c")); } +#[test] +fn test_hint() { + test_plugin(include_str!("plugin_hint.c")); +} + #[test] fn test_dirty_log() { test_plugin(include_str!("plugin_dirty_log.c")); -- cgit 1.4.1