use std::convert::TryFrom;
use std::fmt::Debug;
#[cfg(gdb)]
use std::sync::{Arc, Mutex};
use kvm_bindings::{kvm_fpu, kvm_regs, kvm_userspace_memory_region, KVM_MEM_READONLY};
use kvm_ioctls::Cap::UserMemory;
use kvm_ioctls::{Kvm, VcpuExit, VcpuFd, VmFd};
use log::LevelFilter;
use tracing::{instrument, Span};
use super::fpu::{FP_CONTROL_WORD_DEFAULT, FP_TAG_WORD_DEFAULT, MXCSR_DEFAULT};
#[cfg(gdb)]
use super::gdb::{DebugCommChannel, DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason};
#[cfg(gdb)]
use super::handlers::DbgMemAccessHandlerWrapper;
use super::handlers::{MemAccessHandlerWrapper, OutBHandlerWrapper};
use super::{
HyperlightExit, Hypervisor, VirtualCPU, CR0_AM, CR0_ET, CR0_MP, CR0_NE, CR0_PE, CR0_PG, CR0_WP,
CR4_OSFXSR, CR4_OSXMMEXCPT, CR4_PAE, EFER_LMA, EFER_LME, EFER_NX, EFER_SCE,
};
use crate::hypervisor::hypervisor_handler::HypervisorHandler;
use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
use crate::mem::ptr::{GuestPtr, RawPtr};
#[cfg(gdb)]
use crate::HyperlightError;
use crate::{log_then_return, new_error, Result};
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
pub(crate) fn is_hypervisor_present() -> bool {
if let Ok(kvm) = Kvm::new() {
let api_version = kvm.get_api_version();
match api_version {
version if version == 12 && kvm.check_extension(UserMemory) => true,
12 => {
log::info!("KVM does not have KVM_CAP_USER_MEMORY capability");
false
}
version => {
log::info!("KVM GET_API_VERSION returned {}, expected 12", version);
false
}
}
} else {
log::info!("KVM is not available on this system");
false
}
}
#[cfg(gdb)]
mod debug {
use std::sync::{Arc, Mutex};
use kvm_bindings::kvm_debug_exit_arch;
use super::KVMDriver;
use crate::hypervisor::gdb::{
DebugMsg, DebugResponse, GuestDebug, KvmDebug, VcpuStopReason, X86_64Regs,
};
use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
use crate::{new_error, Result};
impl KVMDriver {
fn disable_debug(&mut self) -> Result<()> {
let mut debug = KvmDebug::default();
debug.set_single_step(&self.vcpu_fd, false)?;
self.debug = Some(debug);
Ok(())
}
pub(crate) fn get_stop_reason(
&mut self,
debug_exit: kvm_debug_exit_arch,
) -> Result<VcpuStopReason> {
let debug = self
.debug
.as_mut()
.ok_or_else(|| new_error!("Debug is not enabled"))?;
debug.get_stop_reason(&self.vcpu_fd, debug_exit, self.entrypoint)
}
pub(crate) fn process_dbg_request(
&mut self,
req: DebugMsg,
dbg_mem_access_fn: Arc<Mutex<dyn DbgMemAccessHandlerCaller>>,
) -> Result<DebugResponse> {
if let Some(debug) = self.debug.as_mut() {
match req {
DebugMsg::AddHwBreakpoint(addr) => Ok(DebugResponse::AddHwBreakpoint(
debug
.add_hw_breakpoint(&self.vcpu_fd, addr)
.map_err(|e| {
log::error!("Failed to add hw breakpoint: {:?}", e);
e
})
.is_ok(),
)),
DebugMsg::AddSwBreakpoint(addr) => Ok(DebugResponse::AddSwBreakpoint(
debug
.add_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
.map_err(|e| {
log::error!("Failed to add sw breakpoint: {:?}", e);
e
})
.is_ok(),
)),
DebugMsg::Continue => {
debug.set_single_step(&self.vcpu_fd, false).map_err(|e| {
log::error!("Failed to continue execution: {:?}", e);
e
})?;
Ok(DebugResponse::Continue)
}
DebugMsg::DisableDebug => {
self.disable_debug().map_err(|e| {
log::error!("Failed to disable debugging: {:?}", e);
e
})?;
Ok(DebugResponse::DisableDebug)
}
DebugMsg::GetCodeSectionOffset => {
let offset = dbg_mem_access_fn
.try_lock()
.map_err(|e| {
new_error!("Error locking at {}:{}: {}", file!(), line!(), e)
})?
.get_code_offset()
.map_err(|e| {
log::error!("Failed to get code offset: {:?}", e);
e
})?;
Ok(DebugResponse::GetCodeSectionOffset(offset as u64))
}
DebugMsg::ReadAddr(addr, len) => {
let mut data = vec![0u8; len];
debug
.read_addrs(&self.vcpu_fd, addr, &mut data, dbg_mem_access_fn)
.map_err(|e| {
log::error!("Failed to read from address: {:?}", e);
e
})?;
Ok(DebugResponse::ReadAddr(data))
}
DebugMsg::ReadRegisters => {
let mut regs = X86_64Regs::default();
debug
.read_regs(&self.vcpu_fd, &mut regs)
.map_err(|e| {
log::error!("Failed to read registers: {:?}", e);
e
})
.map(|_| DebugResponse::ReadRegisters(regs))
}
DebugMsg::RemoveHwBreakpoint(addr) => Ok(DebugResponse::RemoveHwBreakpoint(
debug
.remove_hw_breakpoint(&self.vcpu_fd, addr)
.map_err(|e| {
log::error!("Failed to remove hw breakpoint: {:?}", e);
e
})
.is_ok(),
)),
DebugMsg::RemoveSwBreakpoint(addr) => Ok(DebugResponse::RemoveSwBreakpoint(
debug
.remove_sw_breakpoint(&self.vcpu_fd, addr, dbg_mem_access_fn)
.map_err(|e| {
log::error!("Failed to remove sw breakpoint: {:?}", e);
e
})
.is_ok(),
)),
DebugMsg::Step => {
debug.set_single_step(&self.vcpu_fd, true).map_err(|e| {
log::error!("Failed to enable step instruction: {:?}", e);
e
})?;
Ok(DebugResponse::Step)
}
DebugMsg::WriteAddr(addr, data) => {
debug
.write_addrs(&self.vcpu_fd, addr, &data, dbg_mem_access_fn)
.map_err(|e| {
log::error!("Failed to write to address: {:?}", e);
e
})?;
Ok(DebugResponse::WriteAddr)
}
DebugMsg::WriteRegisters(regs) => debug
.write_regs(&self.vcpu_fd, ®s)
.map_err(|e| {
log::error!("Failed to write registers: {:?}", e);
e
})
.map(|_| DebugResponse::WriteRegisters),
}
} else {
Err(new_error!("Debugging is not enabled"))
}
}
pub(crate) fn recv_dbg_msg(&mut self) -> Result<DebugMsg> {
let gdb_conn = self
.gdb_conn
.as_mut()
.ok_or_else(|| new_error!("Debug is not enabled"))?;
gdb_conn.recv().map_err(|e| {
new_error!(
"Got an error while waiting to receive a message from the gdb thread: {:?}",
e
)
})
}
pub(crate) fn send_dbg_msg(&mut self, cmd: DebugResponse) -> Result<()> {
log::debug!("Sending {:?}", cmd);
let gdb_conn = self
.gdb_conn
.as_mut()
.ok_or_else(|| new_error!("Debug is not enabled"))?;
gdb_conn.send(cmd).map_err(|e| {
new_error!(
"Got an error while sending a response message to the gdb thread: {:?}",
e
)
})
}
}
}
pub(super) struct KVMDriver {
_kvm: Kvm,
_vm_fd: VmFd,
vcpu_fd: VcpuFd,
entrypoint: u64,
orig_rsp: GuestPtr,
mem_regions: Vec<MemoryRegion>,
#[cfg(gdb)]
debug: Option<KvmDebug>,
#[cfg(gdb)]
gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
}
impl KVMDriver {
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
pub(super) fn new(
mem_regions: Vec<MemoryRegion>,
pml4_addr: u64,
entrypoint: u64,
rsp: u64,
#[cfg(gdb)] gdb_conn: Option<DebugCommChannel<DebugResponse, DebugMsg>>,
) -> Result<Self> {
let kvm = Kvm::new()?;
let vm_fd = kvm.create_vm_with_type(0)?;
let perm_flags =
MemoryRegionFlags::READ | MemoryRegionFlags::WRITE | MemoryRegionFlags::EXECUTE;
mem_regions.iter().enumerate().try_for_each(|(i, region)| {
let perm_flags = perm_flags.intersection(region.flags);
let kvm_region = kvm_userspace_memory_region {
slot: i as u32,
guest_phys_addr: region.guest_region.start as u64,
memory_size: (region.guest_region.end - region.guest_region.start) as u64,
userspace_addr: region.host_region.start as u64,
flags: match perm_flags {
MemoryRegionFlags::READ => KVM_MEM_READONLY,
_ => 0, },
};
unsafe { vm_fd.set_user_memory_region(kvm_region) }
})?;
let mut vcpu_fd = vm_fd.create_vcpu(0)?;
Self::setup_initial_sregs(&mut vcpu_fd, pml4_addr)?;
#[cfg(gdb)]
let (debug, gdb_conn) = if let Some(gdb_conn) = gdb_conn {
let mut debug = KvmDebug::new();
debug.add_hw_breakpoint(&vcpu_fd, entrypoint)?;
(Some(debug), Some(gdb_conn))
} else {
(None, None)
};
let rsp_gp = GuestPtr::try_from(RawPtr::from(rsp))?;
let ret = Self {
_kvm: kvm,
_vm_fd: vm_fd,
vcpu_fd,
entrypoint,
orig_rsp: rsp_gp,
mem_regions,
#[cfg(gdb)]
debug,
#[cfg(gdb)]
gdb_conn,
};
Ok(ret)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn setup_initial_sregs(vcpu_fd: &mut VcpuFd, pml4_addr: u64) -> Result<()> {
let mut sregs = vcpu_fd.get_sregs()?;
sregs.cr3 = pml4_addr;
sregs.cr4 = CR4_PAE | CR4_OSFXSR | CR4_OSXMMEXCPT;
sregs.cr0 = CR0_PE | CR0_MP | CR0_ET | CR0_NE | CR0_AM | CR0_PG | CR0_WP;
sregs.efer = EFER_LME | EFER_LMA | EFER_SCE | EFER_NX;
sregs.cs.l = 1; vcpu_fd.set_sregs(&sregs)?;
Ok(())
}
}
impl Debug for KVMDriver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut f = f.debug_struct("KVM Driver");
for region in &self.mem_regions {
f.field("Memory Region", ®ion);
}
let regs = self.vcpu_fd.get_regs();
if let Ok(regs) = regs {
f.field("Registers", ®s);
}
let sregs = self.vcpu_fd.get_sregs();
if let Ok(sregs) = sregs {
f.field("Special Registers", &sregs);
}
f.finish()
}
}
impl Hypervisor for KVMDriver {
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn initialise(
&mut self,
peb_addr: RawPtr,
seed: u64,
page_size: u32,
outb_hdl: OutBHandlerWrapper,
mem_access_hdl: MemAccessHandlerWrapper,
hv_handler: Option<HypervisorHandler>,
max_guest_log_level: Option<LevelFilter>,
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
) -> Result<()> {
let max_guest_log_level: u64 = match max_guest_log_level {
Some(level) => level as u64,
None => self.get_max_log_level().into(),
};
let regs = kvm_regs {
rip: self.entrypoint,
rsp: self.orig_rsp.absolute()?,
rcx: peb_addr.into(),
rdx: seed,
r8: page_size.into(),
r9: max_guest_log_level,
..Default::default()
};
self.vcpu_fd.set_regs(®s)?;
VirtualCPU::run(
self.as_mut_hypervisor(),
hv_handler,
outb_hdl,
mem_access_hdl,
#[cfg(gdb)]
dbg_mem_access_fn,
)?;
Ok(())
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn dispatch_call_from_host(
&mut self,
dispatch_func_addr: RawPtr,
outb_handle_fn: OutBHandlerWrapper,
mem_access_fn: MemAccessHandlerWrapper,
hv_handler: Option<HypervisorHandler>,
#[cfg(gdb)] dbg_mem_access_fn: DbgMemAccessHandlerWrapper,
) -> Result<()> {
let regs = kvm_regs {
rip: dispatch_func_addr.into(),
rsp: self.orig_rsp.absolute()?,
..Default::default()
};
self.vcpu_fd.set_regs(®s)?;
let fpu = kvm_fpu {
fcw: FP_CONTROL_WORD_DEFAULT,
ftwx: FP_TAG_WORD_DEFAULT,
mxcsr: MXCSR_DEFAULT,
..Default::default() };
self.vcpu_fd.set_fpu(&fpu)?;
VirtualCPU::run(
self.as_mut_hypervisor(),
hv_handler,
outb_handle_fn,
mem_access_fn,
#[cfg(gdb)]
dbg_mem_access_fn,
)?;
Ok(())
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn handle_io(
&mut self,
port: u16,
data: Vec<u8>,
_rip: u64,
_instruction_length: u64,
outb_handle_fn: OutBHandlerWrapper,
) -> Result<()> {
if data.is_empty() {
log_then_return!("no data was given in IO interrupt");
} else {
let payload_u64 = u64::from(data[0]);
outb_handle_fn
.try_lock()
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?
.call(port, payload_u64)?;
}
Ok(())
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level = "Trace")]
fn run(&mut self) -> Result<HyperlightExit> {
let exit_reason = self.vcpu_fd.run();
let result = match exit_reason {
Ok(VcpuExit::Hlt) => {
crate::debug!("KVM - Halt Details : {:#?}", &self);
HyperlightExit::Halt()
}
Ok(VcpuExit::IoOut(port, data)) => {
crate::debug!("KVM IO Details : \nPort : {}\nData : {:?}", port, data);
HyperlightExit::IoOut(port, data.to_vec(), 0, 0)
}
Ok(VcpuExit::MmioRead(addr, _)) => {
crate::debug!("KVM MMIO Read -Details: Address: {} \n {:#?}", addr, &self);
match self.get_memory_access_violation(
addr as usize,
&self.mem_regions,
MemoryRegionFlags::READ,
) {
Some(access_violation_exit) => access_violation_exit,
None => HyperlightExit::Mmio(addr),
}
}
Ok(VcpuExit::MmioWrite(addr, _)) => {
crate::debug!("KVM MMIO Write -Details: Address: {} \n {:#?}", addr, &self);
match self.get_memory_access_violation(
addr as usize,
&self.mem_regions,
MemoryRegionFlags::WRITE,
) {
Some(access_violation_exit) => access_violation_exit,
None => HyperlightExit::Mmio(addr),
}
}
#[cfg(gdb)]
Ok(VcpuExit::Debug(debug_exit)) => match self.get_stop_reason(debug_exit) {
Ok(reason) => HyperlightExit::Debug(reason),
Err(e) => {
log_then_return!("Error getting stop reason: {:?}", e);
}
},
Err(e) => match e.errno() {
#[cfg(gdb)]
libc::EINTR => HyperlightExit::Debug(VcpuStopReason::Interrupt),
#[cfg(not(gdb))]
libc::EINTR => HyperlightExit::Cancelled(),
libc::EAGAIN => HyperlightExit::Retry(),
_ => {
crate::debug!("KVM Error -Details: Address: {} \n {:#?}", e, &self);
log_then_return!("Error running VCPU {:?}", e);
}
},
Ok(other) => {
crate::debug!("KVM Other Exit {:?}", other);
HyperlightExit::Unknown(format!("Unexpected KVM Exit {:?}", other))
}
};
Ok(result)
}
#[instrument(skip_all, parent = Span::current(), level = "Trace")]
fn as_mut_hypervisor(&mut self) -> &mut dyn Hypervisor {
self as &mut dyn Hypervisor
}
#[cfg(crashdump)]
fn get_memory_regions(&self) -> &[MemoryRegion] {
&self.mem_regions
}
#[cfg(gdb)]
fn handle_debug(
&mut self,
dbg_mem_access_fn: Arc<Mutex<dyn super::handlers::DbgMemAccessHandlerCaller>>,
stop_reason: VcpuStopReason,
) -> Result<()> {
self.send_dbg_msg(DebugResponse::VcpuStopped(stop_reason))
.map_err(|e| new_error!("Couldn't signal vCPU stopped event to GDB thread: {:?}", e))?;
loop {
log::debug!("Debug wait for event to resume vCPU");
let req = self.recv_dbg_msg()?;
let result = self.process_dbg_request(req, dbg_mem_access_fn.clone());
let response = match result {
Ok(response) => response,
Err(HyperlightError::TranslateGuestAddress(_)) => DebugResponse::ErrorOccurred,
Err(e) => {
return Err(e);
}
};
let cont = matches!(
response,
DebugResponse::Step | DebugResponse::Continue | DebugResponse::DisableDebug
);
self.send_dbg_msg(response)
.map_err(|e| new_error!("Couldn't send response to gdb: {:?}", e))?;
if cont {
break;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
#[cfg(gdb)]
use crate::hypervisor::handlers::DbgMemAccessHandlerCaller;
use crate::hypervisor::handlers::{MemAccessHandler, OutBHandler};
use crate::hypervisor::tests::test_initialise;
use crate::Result;
#[cfg(gdb)]
struct DbgMemAccessHandler {}
#[cfg(gdb)]
impl DbgMemAccessHandlerCaller for DbgMemAccessHandler {
fn read(&mut self, _offset: usize, _data: &mut [u8]) -> Result<()> {
Ok(())
}
fn write(&mut self, _offset: usize, _data: &[u8]) -> Result<()> {
Ok(())
}
fn get_code_offset(&mut self) -> Result<usize> {
Ok(0)
}
}
#[test]
fn test_init() {
if !super::is_hypervisor_present() {
return;
}
let outb_handler: Arc<Mutex<OutBHandler>> = {
let func: Box<dyn FnMut(u16, u64) -> Result<()> + Send> =
Box::new(|_, _| -> Result<()> { Ok(()) });
Arc::new(Mutex::new(OutBHandler::from(func)))
};
let mem_access_handler = {
let func: Box<dyn FnMut() -> Result<()> + Send> = Box::new(|| -> Result<()> { Ok(()) });
Arc::new(Mutex::new(MemAccessHandler::from(func)))
};
#[cfg(gdb)]
let dbg_mem_access_handler = Arc::new(Mutex::new(DbgMemAccessHandler {}));
test_initialise(
outb_handler,
mem_access_handler,
#[cfg(gdb)]
dbg_mem_access_handler,
)
.unwrap();
}
}