From cf2dc266b68e3f2674cdc807f334f0647e23ff9f Mon Sep 17 00:00:00 2001 From: Greg Colombo Date: Tue, 22 Oct 2024 10:27:57 -0700 Subject: [PATCH] lib: log vCPU diagnostics on triple fault and for some unhandled exit types (#795) Add a `propolis::vcpu::Diagnostics` type that captures and pretty-prints the register state of a vCPU. Log this state of a vCPU triple-faults or (in propolis-server) if it raises a `Paging` or `InstEmul` exit that the binary does not handle. Formatting and logging register state increases the risk that a Propolis log will contain sensitive guest application data that happens to have been loaded into a register at the time the state was read. To help mitigate this, introduce a `GuestData` wrapper type that identifies this sort of guest application data. `GuestData`'s `Display` and `Debug` impls can be configured (using a library-level flag) to redact this data when it is formatted. `GuestData` is displayed by default in propolis-standalone and development builds of propolis-server and redacted in Omicron zone builds of propolis-server. --- bin/propolis-server/src/lib/migrate/source.rs | 13 ++- bin/propolis-server/src/lib/vcpu_tasks.rs | 79 ++++++++++++- bin/propolis-server/src/main.rs | 7 ++ bin/propolis-standalone/src/main.rs | 3 + lib/propolis/src/common.rs | 79 +++++++++++++ lib/propolis/src/exits.rs | 8 +- lib/propolis/src/hw/nvme/cmds.rs | 24 ++-- lib/propolis/src/hw/nvme/queue.rs | 2 +- lib/propolis/src/hw/qemu/fwcfg.rs | 12 +- lib/propolis/src/hw/virtio/p9fs.rs | 6 +- lib/propolis/src/hw/virtio/queue.rs | 12 +- lib/propolis/src/hw/virtio/softnpu.rs | 4 +- lib/propolis/src/vcpu.rs | 109 ++++++++++++++++++ lib/propolis/src/vmm/mem.rs | 41 ++++--- 14 files changed, 349 insertions(+), 50 deletions(-) diff --git a/bin/propolis-server/src/lib/migrate/source.rs b/bin/propolis-server/src/lib/migrate/source.rs index 5abc41aa1..1b2900500 100644 --- a/bin/propolis-server/src/lib/migrate/source.rs +++ b/bin/propolis-server/src/lib/migrate/source.rs @@ -4,7 +4,7 @@ use bitvec::prelude::{BitSlice, Lsb0}; use futures::{SinkExt, StreamExt}; -use propolis::common::{GuestAddr, PAGE_SIZE}; +use propolis::common::{GuestAddr, GuestData, PAGE_SIZE}; use propolis::migrate::{ MigrateCtx, MigrateStateError, Migrator, PayloadOutputs, }; @@ -657,9 +657,12 @@ impl<'vm, T: MigrateConn> RonV0Runner<'vm, T> { info!(self.log(), "ram_push: xfer RAM between {start:#x} and {end:#x}",); self.send_msg(memx::make_mem_xfer(start, end, bits)).await?; for addr in PageIter::new(start, end, bits) { - let mut bytes = [0u8; PAGE_SIZE]; - self.read_guest_mem(GuestAddr(addr), &mut bytes).await?; - self.send_msg(codec::Message::Page(bytes.into())).await?; + let mut byte_buffer = [0u8; PAGE_SIZE]; + { + let mut bytes = GuestData::from(byte_buffer.as_mut_slice()); + self.read_guest_mem(GuestAddr(addr), &mut bytes).await?; + } + self.send_msg(codec::Message::Page(byte_buffer.into())).await?; probes::migrate_xfer_ram_page!(|| (addr, PAGE_SIZE as u64)); } Ok(()) @@ -919,7 +922,7 @@ impl<'vm, T: MigrateConn> RonV0Runner<'vm, T> { async fn read_guest_mem( &mut self, addr: GuestAddr, - buf: &mut [u8], + buf: &mut GuestData<&mut [u8]>, ) -> Result<(), MigrateError> { let objects = self.vm.lock_shared().await; let memctx = objects.access_mem().unwrap(); diff --git a/bin/propolis-server/src/lib/vcpu_tasks.rs b/bin/propolis-server/src/lib/vcpu_tasks.rs index be2d37f35..e74074e3e 100644 --- a/bin/propolis-server/src/lib/vcpu_tasks.rs +++ b/bin/propolis-server/src/lib/vcpu_tasks.rs @@ -159,6 +159,7 @@ impl VcpuTasks { VmEntry::Run } VmExitKind::Suspended(SuspendDetail { kind, when }) => { + use propolis::vcpu::Diagnostics; match kind { exits::Suspend::Halt => { event_handler.suspend_halt_event(when); @@ -167,6 +168,13 @@ impl VcpuTasks { event_handler.suspend_reset_event(when); } exits::Suspend::TripleFault(vcpuid) => { + slog::info!( + &log, + "triple fault on vcpu {}", + vcpu.id; + "state" => %Diagnostics::capture(vcpu) + ); + if vcpuid == -1 || vcpuid == vcpu.id { event_handler .suspend_triple_fault_event(vcpu.id, when); @@ -187,7 +195,76 @@ impl VcpuTasks { task.force_hold(); VmEntry::Run } - _ => { + VmExitKind::InstEmul(inst) => { + let diag = propolis::vcpu::Diagnostics::capture(vcpu); + error!(log, + "instruction emulation exit on vCPU {}", + vcpu.id; + "context" => ?inst, + "vcpu_state" => %diag); + + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); + VmEntry::Run + } + VmExitKind::Unknown(code) => { + error!(log, + "unrecognized exit code on vCPU {}", + vcpu.id; + "code" => code); + + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); + VmEntry::Run + } + // Bhyve emits the `Bogus` exit kind when there is no actual + // guest exit for user space to handle, but circumstances + // nevertheless dictate that the kernel VMM should exit to + // user space (e.g. a caller requested that all vCPUs be + // forced to exit to user space so their threads can + // rendezvous there). + // + // `process_vmexit` should always successfully handle this + // exit, since it never entails any work that could fail to + // be completed. + VmExitKind::Bogus => { + unreachable!( + "propolis-lib always handles VmExitKind::Bogus" + ); + } + VmExitKind::Debug => { + error!(log, + "lib returned debug exit from vCPU {}", + vcpu.id); + + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); + VmEntry::Run + } + VmExitKind::VmxError(detail) => { + error!(log, + "unclassified VMX exit on vCPU {}", + vcpu.id; + "detail" => ?detail); + + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); + VmEntry::Run + } + VmExitKind::SvmError(detail) => { + error!(log, + "unclassified SVM exit on vCPU {}", + vcpu.id; + "detail" => ?detail); + + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); + VmEntry::Run + } + VmExitKind::Paging(gpa, fault_type) => { + let diag = propolis::vcpu::Diagnostics::capture(vcpu); + error!(log, + "unhandled paging exit on vCPU {}", + vcpu.id; + "gpa" => gpa, + "fault_type" => fault_type, + "vcpu_state" => %diag); + event_handler.unhandled_vm_exit(vcpu.id, exit.kind); VmEntry::Run } diff --git a/bin/propolis-server/src/main.rs b/bin/propolis-server/src/main.rs index 5b5054e92..b4127ec31 100644 --- a/bin/propolis-server/src/main.rs +++ b/bin/propolis-server/src/main.rs @@ -136,6 +136,13 @@ fn run_server( Err(e).context("API version checks")?; } + // If this is a development image being run outside of an Omicron zone, + // enable the display (in logs, panic messages, and the like) of diagnostic + // data that may have originated in the guest. + #[cfg(not(feature = "omicron-build"))] + propolis::common::DISPLAY_GUEST_DATA + .store(true, std::sync::atomic::Ordering::SeqCst); + let use_reservoir = config::reservoir_decide(&log); let context = server::DropshotEndpointContext::new( diff --git a/bin/propolis-standalone/src/main.rs b/bin/propolis-standalone/src/main.rs index 10fa8968e..1a0b23b8f 100644 --- a/bin/propolis-standalone/src/main.rs +++ b/bin/propolis-standalone/src/main.rs @@ -1421,6 +1421,9 @@ fn main() -> anyhow::Result { // Check that vmm and viona device version match what we expect api_version_checks(&log).context("API version checks")?; + propolis::common::DISPLAY_GUEST_DATA + .store(true, std::sync::atomic::Ordering::SeqCst); + // Load/parse the config first, since it's required to size the tokio runtime // used to run the instance. let config = if restore { diff --git a/lib/propolis/src/common.rs b/lib/propolis/src/common.rs index df22322c3..4256db730 100644 --- a/lib/propolis/src/common.rs +++ b/lib/propolis/src/common.rs @@ -5,9 +5,88 @@ use std::ops::{Add, BitAnd}; use std::ops::{Bound::*, RangeBounds}; use std::slice::SliceIndex; +use std::sync::atomic::{AtomicBool, Ordering}; use crate::vmm::SubMapping; +/// Controls whether items wrapped in a [`GuestData`] are displayed or redacted +/// when the wrappers are printed via their `Display` or `Debug` impls. +// +// The Propolis server binary should only link the Propolis lib once (any +// structure that links the lib multiple times means something is very odd about +// its dependency graph), so there should never be any ambiguity about what +// `DISPLAY_GUEST_DATA` refers to when linking. But to be maximally cautious, +// label this static as `no_mangle` so that pulling in multiple Propolis +// libraries will break the build instead of possibly resolving ambiguously. +#[no_mangle] +pub static DISPLAY_GUEST_DATA: AtomicBool = AtomicBool::new(false); + +/// A wrapper type denoting that the contained `T` was obtained from the guest +/// (e.g. by reading the guest's memory). This type implements various traits +/// (`Deref`, `DerefMut`, and `Borrow`) that allow it to be treated in most +/// cases as just another instance of a `T`. The main difference is that this +/// wrapper has custom `Display` and `Debug` implementations that redact the +/// wrapped value unless the program has set the [`DISPLAY_GUEST_DATA`] flag. +/// +/// NOTE: This wrapper type is not airtight: owners of a wrapper can always +/// dereference it and invoke the Display/Debug impls directly on the resulting +/// reference to the wrapped value. If `T` is `Clone`, they can also clone the +/// dereferenced value and display the clone. (This comes with the territory +/// here: users need to be able to get at the wrapped value to be able to do +/// anything useful with it!) +/// +/// NOTE: This type does not provide any other security guarantees (e.g. it does +/// not ensure that the wrapped memory will be zeroed on drop). +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct GuestData(T); + +impl std::fmt::Display for GuestData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if DISPLAY_GUEST_DATA.load(Ordering::Relaxed) { + write!(f, "{}", self.0) + } else { + write!(f, "") + } + } +} + +impl std::fmt::Debug for GuestData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if DISPLAY_GUEST_DATA.load(Ordering::Relaxed) { + write!(f, "{:?}", self.0) + } else { + write!(f, "") + } + } +} + +impl std::ops::Deref for GuestData { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for GuestData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From for GuestData { + fn from(value: T) -> Self { + Self(value) + } +} + +impl std::borrow::Borrow for GuestData { + fn borrow(&self) -> &T { + &self.0 + } +} + fn numeric_bounds( bound: impl RangeBounds, len: usize, diff --git a/lib/propolis/src/exits.rs b/lib/propolis/src/exits.rs index 53df50e78..21da5ac4f 100644 --- a/lib/propolis/src/exits.rs +++ b/lib/propolis/src/exits.rs @@ -12,6 +12,8 @@ use bhyve_api::{ vm_suspend_how, }; +use crate::common::GuestData; + /// Describes the reason for exiting execution of a vCPU. pub struct VmExit { /// The instruction pointer of the guest at the time of exit. @@ -95,9 +97,10 @@ impl From<&bhyve_api::vm_exit_vmx> for VmxDetail { #[derive(Copy, Clone, Debug)] pub struct InstEmul { - pub inst_data: [u8; 15], + pub inst_data: GuestData<[u8; 15]>, pub len: u8, } + impl InstEmul { pub fn bytes(&self) -> &[u8] { &self.inst_data[..usize::min(self.inst_data.len(), self.len as usize)] @@ -105,7 +108,8 @@ impl InstEmul { } impl From<&bhyve_api::vm_inst_emul> for InstEmul { fn from(raw: &bhyve_api::vm_inst_emul) -> Self { - let mut res = Self { inst_data: [0u8; 15], len: raw.num_valid }; + let mut res = + Self { inst_data: GuestData::from([0u8; 15]), len: raw.num_valid }; assert!(res.len as usize <= res.inst_data.len()); res.inst_data.copy_from_slice(&raw.inst[..]); diff --git a/lib/propolis/src/hw/nvme/cmds.rs b/lib/propolis/src/hw/nvme/cmds.rs index 7b3ffd9db..c4b9bf23b 100644 --- a/lib/propolis/src/hw/nvme/cmds.rs +++ b/lib/propolis/src/hw/nvme/cmds.rs @@ -54,12 +54,14 @@ pub enum AdminCmd { /// Asynchronous Event Request Command AsyncEventReq, /// An unknown admin command - Unknown(#[allow(dead_code)] SubmissionQueueEntry), + Unknown(#[allow(dead_code)] GuestData), } impl AdminCmd { /// Try to parse an `AdminCmd` out of a raw Submission Entry. - pub fn parse(raw: SubmissionQueueEntry) -> Result { + pub fn parse( + raw: GuestData, + ) -> Result { let cmd = match raw.opcode() { bits::ADMIN_OPC_DELETE_IO_SQ => { AdminCmd::DeleteIOSubQ(raw.cdw10 as u16) @@ -645,12 +647,14 @@ pub enum NvmCmd { /// Read data and metadata Read(ReadCmd), /// An unknown NVM command - Unknown(SubmissionQueueEntry), + Unknown(GuestData), } impl NvmCmd { /// Try to parse an `NvmCmd` out of a raw Submission Entry. - pub fn parse(raw: SubmissionQueueEntry) -> Result { + pub fn parse( + raw: GuestData, + ) -> Result { let _fuse = match (raw.cdw0 >> 8) & 0b11 { 0b00 => Ok(()), // Normal (non-fused) operation 0b01 => Err(ParseErr::Fused), // First fused op @@ -882,7 +886,7 @@ impl PrpIter<'_> { PrpNext::List(base, idx) => { assert!(idx <= PRP_LIST_MAX); let entry_addr = base + u64::from(idx) * 8; - let entry: u64 = self + let entry: GuestData = self .mem .read(GuestAddr(entry_addr)) .ok_or_else(|| "Unable to read PRP list entry")?; @@ -890,21 +894,21 @@ impl PrpIter<'_> { || (self as *const Self as u64, entry,) ); - if entry & PAGE_OFFSET as u64 != 0 { + if *entry & PAGE_OFFSET as u64 != 0 { return Err("Inappropriate PRP list entry offset"); } if self.remain <= PAGE_SIZE as u64 { - (entry, self.remain, PrpNext::Done) + (*entry, self.remain, PrpNext::Done) } else if idx != PRP_LIST_MAX { - (entry, PAGE_SIZE as u64, PrpNext::List(base, idx + 1)) + (*entry, PAGE_SIZE as u64, PrpNext::List(base, idx + 1)) } else { // The last PRP in this list chains to another // (page-aligned) list with the next PRP. - self.next = PrpNext::List(entry, 0); + self.next = PrpNext::List(*entry, 0); probes::nvme_prp_list!(|| ( self as *const Self as u64, - entry, + *entry, 0, )); return self.get_next(); diff --git a/lib/propolis/src/hw/nvme/queue.rs b/lib/propolis/src/hw/nvme/queue.rs index eb06efd52..8cb9d74e8 100644 --- a/lib/propolis/src/hw/nvme/queue.rs +++ b/lib/propolis/src/hw/nvme/queue.rs @@ -429,7 +429,7 @@ impl SubQueue { pub fn pop( self: &Arc, mem: &MemCtx, - ) -> Option<(SubmissionQueueEntry, Permit, u16)> { + ) -> Option<(GuestData, Permit, u16)> { // Attempt to reserve an entry on the Completion Queue let permit = self.cq.reserve_entry(&self)?; let mut state = self.state.lock(); diff --git a/lib/propolis/src/hw/qemu/fwcfg.rs b/lib/propolis/src/hw/qemu/fwcfg.rs index 2fb8b635f..9327cf811 100644 --- a/lib/propolis/src/hw/qemu/fwcfg.rs +++ b/lib/propolis/src/hw/qemu/fwcfg.rs @@ -599,7 +599,7 @@ impl FwCfg { let mem_guard = self.acc_mem.access().expect("usable mem accessor"); let mem = mem_guard.deref(); - let req: FwCfgDmaAccess = + let req: GuestData = mem.read(GuestAddr(addr)).ok_or(FwCfgErr::BadAddr)?; fn dma_write_result( @@ -632,7 +632,7 @@ impl FwCfg { fn dma_operation( &self, state: &mut MutexGuard, - req: FwCfgDmaAccess, + req: GuestData, mem: &MemCtx, ) -> Result<(), FwCfgErr> { let opts = FwCfgDmaCtrl::from_bits_truncate(req.ctrl.get()); @@ -995,9 +995,9 @@ mod test { submit_dma_req(&dev, req_addr); // DMA should have successfully completed now - assert_eq!(mem.read::(GuestAddr(req_addr)).unwrap(), 0); + assert_eq!(*mem.read::(GuestAddr(req_addr)).unwrap(), 0); let data = mem.read::<[u8; 4]>(GuestAddr(dma_addr)).unwrap(); - assert_eq!(&data, "QEMU".as_bytes()); + assert_eq!(&*data, "QEMU".as_bytes()); } #[test] @@ -1019,9 +1019,9 @@ mod test { submit_dma_req(&dev, req_addr); // DMA should have successfully completed now - assert_eq!(mem.read::(GuestAddr(req_addr)).unwrap(), 0); + assert_eq!(*mem.read::(GuestAddr(req_addr)).unwrap(), 0); let data = mem.read::<[u8; 4]>(GuestAddr(dma_addr)).unwrap(); - assert_eq!(data, [0u8; 4]); + assert_eq!(*data, [0u8; 4]); } #[test] diff --git a/lib/propolis/src/hw/virtio/p9fs.rs b/lib/propolis/src/hw/virtio/p9fs.rs index 807f63fd3..8cfa8cf65 100644 --- a/lib/propolis/src/hw/virtio/p9fs.rs +++ b/lib/propolis/src/hw/virtio/p9fs.rs @@ -223,15 +223,15 @@ pub trait P9Handler: Sync + Send + 'static { let mut data = Vec::new(); let msize = self.msize(); data.resize(msize as usize, 0); - let buf = data.as_mut_slice(); + let mut buf = GuestData::from(data.as_mut_slice()); // TODO copy pasta from tail end of Chain::read function. Seemingly // cannot use Chain::read as-is because it expects a statically sized // type. let mut done = 0; let _total = chain.for_remaining_type(true, |addr, len| { - let remain = &mut buf[done..]; - if let Some(copied) = mem.read_into(addr, remain, len) { + let mut remain = GuestData::from(&mut buf[done..]); + if let Some(copied) = mem.read_into(addr, &mut remain, len) { let need_more = copied != remain.len(); done += copied; (copied, need_more) diff --git a/lib/propolis/src/hw/virtio/queue.rs b/lib/propolis/src/hw/virtio/queue.rs index fdaa88d57..ef2ea78fb 100644 --- a/lib/propolis/src/hw/virtio/queue.rs +++ b/lib/propolis/src/hw/virtio/queue.rs @@ -59,7 +59,7 @@ impl VqAvail { return None; } if let Some(idx) = mem.read::(self.gpa_idx) { - let ndesc = Wrapping(idx) - self.cur_avail_idx; + let ndesc = Wrapping(*idx) - self.cur_avail_idx; if ndesc.0 != 0 && ndesc.0 < rsize { let avail_idx = self.cur_avail_idx.0 & (rsize - 1); self.cur_avail_idx += Wrapping(1); @@ -68,7 +68,7 @@ impl VqAvail { let addr = self.gpa_ring.offset::(avail_idx as usize); return mem .read(addr) - .map(|desc_idx| VqReq { desc_idx, avail_idx }); + .map(|desc_idx| VqReq { desc_idx: *desc_idx, avail_idx }); } } None @@ -78,7 +78,7 @@ impl VqAvail { id: u16, rsize: u16, mem: &MemCtx, - ) -> Option { + ) -> Option> { assert!(id < rsize); let addr = self.gpa_desc.offset::(id as usize); mem.read::(addr) @@ -127,7 +127,7 @@ impl VqUsed { mem.write(self.gpa_idx, &self.used_idx.0); } fn intr_supressed(&self, mem: &MemCtx) -> bool { - let flags: u16 = mem.read(self.gpa_flags).unwrap(); + let flags: u16 = *mem.read(self.gpa_flags).unwrap(); flags & VRING_AVAIL_F_NO_INTERRUPT != 0 } fn reset(&mut self) { @@ -504,8 +504,8 @@ impl Chain { }; let mut done = 0; let total = self.for_remaining_type(true, |addr, len| { - let remain = &mut raw[done..]; - if let Some(copied) = mem.read_into(addr, remain, len) { + let mut remain = GuestData::from(&mut raw[done..]); + if let Some(copied) = mem.read_into(addr, &mut remain, len) { let need_more = copied != remain.len(); done += copied; diff --git a/lib/propolis/src/hw/virtio/softnpu.rs b/lib/propolis/src/hw/virtio/softnpu.rs index cf1740fae..3c1152013 100644 --- a/lib/propolis/src/hw/virtio/softnpu.rs +++ b/lib/propolis/src/hw/virtio/softnpu.rs @@ -652,8 +652,8 @@ use bits::*; fn read_buf(mem: &MemCtx, chain: &mut Chain, buf: &mut [u8]) -> usize { let mut done = 0; chain.for_remaining_type(true, |addr, len| { - let remain = &mut buf[done..]; - if let Some(copied) = mem.read_into(addr, remain, len) { + let mut remain = GuestData::from(&mut buf[done..]); + if let Some(copied) = mem.read_into(addr, &mut remain, len) { let need_more = copied != remain.len(); done += copied; (copied, need_more) diff --git a/lib/propolis/src/vcpu.rs b/lib/propolis/src/vcpu.rs index 21fed44b5..5001952ef 100644 --- a/lib/propolis/src/vcpu.rs +++ b/lib/propolis/src/vcpu.rs @@ -7,6 +7,7 @@ use std::io::Result; use std::sync::Arc; +use crate::common::GuestData; use crate::common::Lifecycle; use crate::cpuid; use crate::exits::*; @@ -1341,3 +1342,111 @@ mod bits { pub const MSR_DEBUGCTL: u32 = 0x1d9; pub const MSR_EFER: u32 = 0xc0000080; } + +/// Pretty-printable diagnostic information about the state of a vCPU. +pub struct Diagnostics { + gp_regs: Result>, + seg_regs: Result>, + ctrl_regs: Result>, +} + +impl Diagnostics { + pub fn capture(vcpu: &Vcpu) -> Self { + Self { + gp_regs: migrate::VcpuGpRegsV1::read(vcpu).map(GuestData::from), + seg_regs: migrate::VcpuSegRegsV1::read(vcpu).map(GuestData::from), + ctrl_regs: migrate::VcpuCtrlRegsV1::read(vcpu).map(GuestData::from), + } + } +} + +impl std::fmt::Display for migrate::VcpuGpRegsV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "%rax = {:#018x}\t%r9 = {:#018x}", self.rax, self.r9)?; + writeln!(f, "%rbx = {:#018x}\t%r10 = {:#018x}", self.rbx, self.r10)?; + writeln!(f, "%rcx = {:#018x}\t%r11 = {:#018x}", self.rcx, self.r11)?; + writeln!(f, "%rdx = {:#018x}\t%r12 = {:#018x}", self.rdx, self.r12)?; + writeln!(f, "%rsi = {:#018x}\t%r13 = {:#018x}", self.rsi, self.r13)?; + writeln!(f, "%rdi = {:#018x}\t%r14 = {:#018x}", self.rdi, self.r14)?; + writeln!(f, "%r8 = {:#018x}\t%r15 = {:#018x}", self.r8, self.r15)?; + writeln!(f)?; + writeln!(f, "%rip = {:#018x}", self.rip)?; + writeln!(f, "%rbp = {:#018x}", self.rbp)?; + writeln!(f, "%rsp = {:#018x}", self.rsp)?; + writeln!(f, "%rflags = {:#018x}", self.rflags)?; + + Ok(()) + } +} + +impl std::fmt::Display for migrate::SegDesc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "sel = {:#06x}\tbase = {:#018x}", self.selector, self.base)?; + write!( + f, + "\tlimit = {:#010x}\taccess = {:#010x}", + self.limit, self.access + )?; + Ok(()) + } +} + +impl std::fmt::Display for migrate::VcpuSegRegsV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "%cs: {}", self.cs)?; + writeln!(f, "%ds: {}", self.ds)?; + writeln!(f, "%es: {}", self.es)?; + writeln!(f, "%fs: {}", self.fs)?; + writeln!(f, "%gs: {}", self.gs)?; + writeln!(f, "%ss: {}", self.ss)?; + writeln!(f, "%gdtr: {}", self.gdtr)?; + writeln!(f, "%idtr: {}", self.idtr)?; + writeln!(f, "%ldtr: {}", self.ldtr)?; + writeln!(f, "%tr: {}", self.tr)?; + Ok(()) + } +} + +impl std::fmt::Display for migrate::VcpuCtrlRegsV1 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "%cr0 = {:#018x}\t%cr2 = {:#018x}", self.cr0, self.cr2)?; + writeln!(f, "%cr3 = {:#018x}\t%cr4 = {:#018x}", self.cr3, self.cr4)?; + writeln!( + f, + "%xcr0 = {:#018x}\t%efer = {:#018x}", + self.xcr0, self.efer + )?; + Ok(()) + } +} + +impl std::fmt::Display for Diagnostics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f)?; + writeln!( + f, + "{}", + self.gp_regs.as_ref().map(|regs| regs.to_string()).unwrap_or_else( + |e| format!("error reading general-purpose registers: {e}") + ) + )?; + writeln!( + f, + "{}", + self.seg_regs.as_ref().map(|regs| regs.to_string()).unwrap_or_else( + |e| format!("error reading segment registers: {e}") + ) + )?; + writeln!( + f, + "{}", + self.ctrl_regs + .as_ref() + .map(|regs| regs.to_string()) + .unwrap_or_else(|e| format!( + "error reading control registers: {e}" + )) + )?; + Ok(()) + } +} diff --git a/lib/propolis/src/vmm/mem.rs b/lib/propolis/src/vmm/mem.rs index c54245e20..054f9e500 100644 --- a/lib/propolis/src/vmm/mem.rs +++ b/lib/propolis/src/vmm/mem.rs @@ -14,8 +14,7 @@ use std::sync::{Arc, Mutex}; use libc::iovec; -use crate::common::PAGE_SIZE; -use crate::common::{GuestAddr, GuestRegion}; +use crate::common::{GuestAddr, GuestData, GuestRegion, PAGE_SIZE}; use crate::util::aspace::ASpace; use crate::vmm::VmmHdl; @@ -798,11 +797,14 @@ pub struct MemCtx { } impl MemCtx { /// Reads a generic value from a specified guest address. - pub fn read(&self, addr: GuestAddr) -> Option { + pub fn read( + &self, + addr: GuestAddr, + ) -> Option> { if let Some(mapping) = self.region_covered(addr, size_of::(), Prot::READ) { - mapping.read().ok() + mapping.read::().ok().map(GuestData::from) } else { None } @@ -813,7 +815,7 @@ impl MemCtx { pub fn read_into( &self, addr: GuestAddr, - buf: &mut [u8], + buf: &mut GuestData<&mut [u8]>, len: usize, ) -> Option { let len = usize::min(buf.len(), len); @@ -828,7 +830,7 @@ impl MemCtx { pub fn direct_read_into( &self, addr: GuestAddr, - buf: &mut [u8], + buf: &mut GuestData<&mut [u8]>, len: usize, ) -> Option { let len = usize::min(buf.len(), len); @@ -842,9 +844,16 @@ impl MemCtx { &self, base: GuestAddr, count: usize, - ) -> Option> { + ) -> Option>> { self.region_covered(base, size_of::() * count, Prot::READ).map( - |mapping| MemMany { mapping, pos: 0, count, phantom: PhantomData }, + |mapping| { + GuestData::from(MemMany { + mapping, + pos: 0, + count, + phantom: PhantomData, + }) + }, ) } /// Writes a value to guest memory. @@ -1025,26 +1034,30 @@ pub struct MemMany<'a, T: Copy> { pos: usize, phantom: PhantomData, } -impl<'a, T: Copy + FromBytes> MemMany<'a, T> { +impl<'a, T: Copy + FromBytes> GuestData> { /// Gets the object at position `pos` within the memory region. /// /// Returns [`Option::None`] if out of range. - pub fn get(&self, pos: usize) -> Option { + pub fn get(&self, pos: usize) -> Option> { if pos < self.count { let sz = std::mem::size_of::(); - self.mapping.subregion(pos * sz, sz)?.read().ok() + self.mapping + .subregion(pos * sz, sz)? + .read::() + .ok() + .map(GuestData::from) } else { None } } } -impl<'a, T: Copy + FromBytes> Iterator for MemMany<'a, T> { - type Item = T; +impl<'a, T: Copy + FromBytes> Iterator for GuestData> { + type Item = GuestData; fn next(&mut self) -> Option { let res = self.get(self.pos); self.pos += 1; - res + res.map(GuestData::from) } }