// Copyright 2017 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. //! Track memory regions that are mapped to the guest VM. use std::convert::{AsRef, TryFrom}; use std::fs::File; use std::marker::{Send, Sync}; use std::mem::size_of; use std::result; use std::sync::Arc; use base::{pagesize, Error as SysError}; use base::{ AsRawDescriptor, AsRawDescriptors, MappedRegion, MemfdSeals, MemoryMapping, MemoryMappingBuilder, MemoryMappingUnix, MmapError, RawDescriptor, SharedMemory, SharedMemoryUnix, }; use bitflags::bitflags; use cros_async::{mem, BackingMemory}; use data_model::volatile_memory::*; use data_model::DataInit; use remain::sorted; use thiserror::Error; use crate::guest_address::GuestAddress; #[sorted] #[derive(Error, Debug)] pub enum Error { #[error("invalid guest address {0}")] InvalidGuestAddress(GuestAddress), #[error("invalid offset {0}")] InvalidOffset(u64), #[error("size {0} must not be zero")] InvalidSize(usize), #[error("invalid guest memory access at addr={0}: {1}")] MemoryAccess(GuestAddress, MmapError), #[error("failed to set seals on shm region: {0}")] MemoryAddSealsFailed(SysError), #[error("failed to create shm region")] MemoryCreationFailed(SysError), #[error("failed to lock {0} bytes of guest memory: {1}")] MemoryLockingFailed(usize, MmapError), #[error("failed to map guest memory: {0}")] MemoryMappingFailed(MmapError), #[error("shm regions must be page aligned")] MemoryNotAligned, #[error("memory regions overlap")] MemoryRegionOverlap, #[error("memory region size {0} is too large")] MemoryRegionTooLarge(u128), #[error("incomplete read of {completed} instead of {expected} bytes")] ShortRead { expected: usize, completed: usize }, #[error("incomplete write of {completed} instead of {expected} bytes")] ShortWrite { expected: usize, completed: usize }, #[error("DescriptorChain split is out of bounds: {0}")] SplitOutOfBounds(usize), #[error("{0}")] VolatileMemoryAccess(VolatileMemoryError), } pub type Result = result::Result; bitflags! { pub struct MemoryPolicy: u32 { const USE_HUGEPAGES = 1; const MLOCK_ON_FAULT = 2; } } /// A file-like object backing `MemoryRegion`. #[derive(Clone)] pub enum BackingObject { Shm(Arc), File(Arc), } impl AsRawDescriptor for BackingObject { fn as_raw_descriptor(&self) -> RawDescriptor { match self { Self::Shm(shm) => shm.as_raw_descriptor(), Self::File(f) => f.as_raw_descriptor(), } } } impl AsRef for BackingObject { fn as_ref(&self) -> &(dyn AsRawDescriptor + Sync + Send + 'static) { match self { BackingObject::Shm(shm) => shm.as_ref(), BackingObject::File(f) => f.as_ref(), } } } /// A regions of memory mapped memory. /// Holds the memory mapping with its offset in guest memory. /// Also holds the backing object for the mapping and the offset in that object of the mapping. pub struct MemoryRegion { mapping: MemoryMapping, guest_base: GuestAddress, shared_obj: BackingObject, obj_offset: u64, } impl MemoryRegion { /// Creates a new MemoryRegion using the given SharedMemory object to later be attached to a VM /// at `guest_base` address in the guest. pub fn new_from_shm( size: u64, guest_base: GuestAddress, offset: u64, shm: Arc, ) -> Result { let mapping = MemoryMappingBuilder::new(size as usize) .from_shared_memory(shm.as_ref()) .offset(offset) .build() .map_err(Error::MemoryMappingFailed)?; Ok(MemoryRegion { mapping, guest_base, shared_obj: BackingObject::Shm(shm), obj_offset: offset, }) } /// Creates a new MemoryRegion using the given file to get available later at `guest_base` /// address in the guest. pub fn new_from_file( size: u64, guest_base: GuestAddress, offset: u64, file: Arc, ) -> Result { let mapping = MemoryMappingBuilder::new(size as usize) .from_file(&file) .offset(offset) .build() .map_err(Error::MemoryMappingFailed)?; Ok(MemoryRegion { mapping, guest_base, shared_obj: BackingObject::File(file), obj_offset: offset, }) } fn start(&self) -> GuestAddress { self.guest_base } fn end(&self) -> GuestAddress { // unchecked_add is safe as the region bounds were checked when it was created. self.guest_base.unchecked_add(self.mapping.size() as u64) } fn contains(&self, addr: GuestAddress) -> bool { addr >= self.guest_base && addr < self.end() } } /// Tracks memory regions and where they are mapped in the guest, along with shm /// fds of the underlying memory regions. #[derive(Clone)] pub struct GuestMemory { regions: Arc<[MemoryRegion]>, } impl AsRawDescriptors for GuestMemory { fn as_raw_descriptors(&self) -> Vec { self.regions .iter() .map(|r| r.shared_obj.as_raw_descriptor()) .collect() } } impl GuestMemory { /// Creates backing shm for GuestMemory regions fn create_shm(ranges: &[(GuestAddress, u64)]) -> Result { let mut aligned_size = 0; let pg_size = pagesize(); for range in ranges { if range.1 % pg_size as u64 != 0 { return Err(Error::MemoryNotAligned); } aligned_size += range.1; } let mut seals = MemfdSeals::new(); seals.set_shrink_seal(); seals.set_grow_seal(); seals.set_seal_seal(); let mut shm = SharedMemory::named("crosvm_guest", aligned_size) .map_err(Error::MemoryCreationFailed)?; shm.add_seals(seals).map_err(Error::MemoryAddSealsFailed)?; Ok(shm) } /// Creates a container for guest memory regions. /// Valid memory regions are specified as a Vec of (Address, Size) tuples sorted by Address. pub fn new(ranges: &[(GuestAddress, u64)]) -> Result { // Create shm let shm = Arc::new(GuestMemory::create_shm(ranges)?); // Create memory regions let mut regions = Vec::::new(); let mut offset = 0; for range in ranges { if let Some(last) = regions.last() { if last .guest_base .checked_add(last.mapping.size() as u64) .map_or(true, |a| a > range.0) { return Err(Error::MemoryRegionOverlap); } } let size = usize::try_from(range.1) .map_err(|_| Error::MemoryRegionTooLarge(range.1 as u128))?; let mapping = MemoryMappingBuilder::new(size) .from_shared_memory(shm.as_ref()) .offset(offset) .build() .map_err(Error::MemoryMappingFailed)?; regions.push(MemoryRegion { mapping, guest_base: range.0, shared_obj: BackingObject::Shm(shm.clone()), obj_offset: offset, }); offset += size as u64; } Ok(GuestMemory { regions: Arc::from(regions), }) } /// Creates a `GuestMemory` from a collection of MemoryRegions. pub fn from_regions(mut regions: Vec) -> Result { // Sort the regions and ensure non overlap. regions.sort_by(|a, b| a.guest_base.cmp(&b.guest_base)); if regions.len() > 1 { let mut prev_end = regions[0] .guest_base .checked_add(regions[0].mapping.size() as u64) .ok_or(Error::MemoryRegionOverlap)?; for region in ®ions[1..] { if prev_end > region.guest_base { return Err(Error::MemoryRegionOverlap); } prev_end = region .guest_base .checked_add(region.mapping.size() as u64) .ok_or(Error::MemoryRegionTooLarge( region.guest_base.0 as u128 + region.mapping.size() as u128, ))?; } } Ok(GuestMemory { regions: Arc::from(regions), }) } /// Returns the end address of memory. /// /// # Examples /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_end_addr() -> Result<(), ()> { /// let start_addr = GuestAddress(0x1000); /// let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// assert_eq!(start_addr.checked_add(0x400), Some(gm.end_addr())); /// Ok(()) /// # } /// ``` pub fn end_addr(&self) -> GuestAddress { self.regions .iter() .max_by_key(|region| region.start()) .map_or(GuestAddress(0), MemoryRegion::end) } /// Returns the total size of memory in bytes. pub fn memory_size(&self) -> u64 { self.regions .iter() .map(|region| region.mapping.size() as u64) .sum() } /// Returns true if the given address is within the memory range available to the guest. pub fn address_in_range(&self, addr: GuestAddress) -> bool { self.regions.iter().any(|region| region.contains(addr)) } /// Returns true if the given range (start, end) is overlap with the memory range /// available to the guest. pub fn range_overlap(&self, start: GuestAddress, end: GuestAddress) -> bool { self.regions .iter() .any(|region| region.start() < end && start < region.end()) } /// Returns the address plus the offset if it is in range. pub fn checked_offset(&self, addr: GuestAddress, offset: u64) -> Option { addr.checked_add(offset).and_then(|a| { if self.address_in_range(a) { Some(a) } else { None } }) } /// Returns the size of the memory region in bytes. pub fn num_regions(&self) -> u64 { self.regions.len() as u64 } /// Madvise away the address range in the host that is associated with the given guest range. pub fn remove_range(&self, addr: GuestAddress, count: u64) -> Result<()> { self.do_in_region(addr, move |mapping, offset, _| { mapping .remove_range(offset, count as usize) .map_err(|e| Error::MemoryAccess(addr, e)) }) } /// Handles guest memory policy hints/advices. pub fn set_memory_policy(&self, mem_policy: MemoryPolicy) -> Result<()> { for (_, region) in self.regions.iter().enumerate() { if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) { let ret = region.mapping.use_hugepages(); if let Err(err) = ret { println!("Failed to enable HUGEPAGE for mapping {}", err); } } if mem_policy.contains(MemoryPolicy::MLOCK_ON_FAULT) { region .mapping .mlock_on_fault() .map_err(|e| Error::MemoryLockingFailed(region.mapping.size(), e))?; } } Ok(()) } /// Perform the specified action on each region's addresses. /// /// Callback is called with arguments: /// * index: usize /// * guest_addr : GuestAddress /// * size: usize /// * host_addr: usize /// * shm: Descriptor of the backing memory region /// * shm_offset: usize pub fn with_regions(&self, mut cb: F) -> result::Result<(), E> where F: FnMut(usize, GuestAddress, usize, usize, &BackingObject, u64) -> result::Result<(), E>, { for (index, region) in self.regions.iter().enumerate() { cb( index, region.start(), region.mapping.size(), region.mapping.as_ptr() as usize, ®ion.shared_obj, region.obj_offset, )?; } Ok(()) } /// Writes a slice to guest memory at the specified guest address. /// Returns the number of bytes written. The number of bytes written can /// be less than the length of the slice if there isn't enough room in the /// memory region. /// /// # Examples /// * Write a slice at guestaddress 0x200. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_write_u64() -> Result<(), ()> { /// # let start_addr = GuestAddress(0x1000); /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// let res = gm.write_at_addr(&[1,2,3,4,5], GuestAddress(0x200)).map_err(|_| ())?; /// assert_eq!(5, res); /// Ok(()) /// # } /// ``` pub fn write_at_addr(&self, buf: &[u8], guest_addr: GuestAddress) -> Result { self.do_in_region(guest_addr, move |mapping, offset, _| { mapping .write_slice(buf, offset) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Writes the entire contents of a slice to guest memory at the specified /// guest address. /// /// Returns an error if there isn't enough room in the memory region to /// complete the entire write. Part of the data may have been written /// nevertheless. /// /// # Examples /// /// ``` /// use vm_memory::{guest_memory, GuestAddress, GuestMemory}; /// /// fn test_write_all() -> guest_memory::Result<()> { /// let ranges = &[(GuestAddress(0x1000), 0x400)]; /// let gm = GuestMemory::new(ranges)?; /// gm.write_all_at_addr(b"zyxwvut", GuestAddress(0x1200)) /// } /// ``` pub fn write_all_at_addr(&self, buf: &[u8], guest_addr: GuestAddress) -> Result<()> { let expected = buf.len(); let completed = self.write_at_addr(buf, guest_addr)?; if expected == completed { Ok(()) } else { Err(Error::ShortWrite { expected, completed, }) } } /// Reads to a slice from guest memory at the specified guest address. /// Returns the number of bytes read. The number of bytes read can /// be less than the length of the slice if there isn't enough room in the /// memory region. /// /// # Examples /// * Read a slice of length 16 at guestaddress 0x200. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_write_u64() -> Result<(), ()> { /// # let start_addr = GuestAddress(0x1000); /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// let buf = &mut [0u8; 16]; /// let res = gm.read_at_addr(buf, GuestAddress(0x200)).map_err(|_| ())?; /// assert_eq!(16, res); /// Ok(()) /// # } /// ``` pub fn read_at_addr(&self, buf: &mut [u8], guest_addr: GuestAddress) -> Result { self.do_in_region(guest_addr, move |mapping, offset, _| { mapping .read_slice(buf, offset) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Reads from guest memory at the specified address to fill the entire /// buffer. /// /// Returns an error if there isn't enough room in the memory region to fill /// the entire buffer. Part of the buffer may have been filled nevertheless. /// /// # Examples /// /// ``` /// use vm_memory::{guest_memory, GuestAddress, GuestMemory}; /// /// fn test_read_exact() -> guest_memory::Result<()> { /// let ranges = &[(GuestAddress(0x1000), 0x400)]; /// let gm = GuestMemory::new(ranges)?; /// let mut buffer = [0u8; 0x200]; /// gm.read_exact_at_addr(&mut buffer, GuestAddress(0x1200)) /// } /// ``` pub fn read_exact_at_addr(&self, buf: &mut [u8], guest_addr: GuestAddress) -> Result<()> { let expected = buf.len(); let completed = self.read_at_addr(buf, guest_addr)?; if expected == completed { Ok(()) } else { Err(Error::ShortRead { expected, completed, }) } } /// Reads an object from guest memory at the given guest address. /// Reading from a volatile area isn't strictly safe as it could change /// mid-read. However, as long as the type T is plain old data and can /// handle random initialization, everything will be OK. /// /// # Examples /// * Read a u64 from two areas of guest memory backed by separate mappings. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_read_u64() -> Result { /// # let start_addr1 = GuestAddress(0x0); /// # let start_addr2 = GuestAddress(0x400); /// # let mut gm = GuestMemory::new(&vec![(start_addr1, 0x400), (start_addr2, 0x400)]) /// # .map_err(|_| ())?; /// let num1: u64 = gm.read_obj_from_addr(GuestAddress(32)).map_err(|_| ())?; /// let num2: u64 = gm.read_obj_from_addr(GuestAddress(0x400+32)).map_err(|_| ())?; /// # Ok(num1 + num2) /// # } /// ``` pub fn read_obj_from_addr(&self, guest_addr: GuestAddress) -> Result { self.do_in_region(guest_addr, |mapping, offset, _| { mapping .read_obj(offset) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Writes an object to the memory region at the specified guest address. /// Returns Ok(()) if the object fits, or Err if it extends past the end. /// /// # Examples /// * Write a u64 at guest address 0x1100. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_write_u64() -> Result<(), ()> { /// # let start_addr = GuestAddress(0x1000); /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// gm.write_obj_at_addr(55u64, GuestAddress(0x1100)) /// .map_err(|_| ()) /// # } /// ``` pub fn write_obj_at_addr(&self, val: T, guest_addr: GuestAddress) -> Result<()> { self.do_in_region(guest_addr, move |mapping, offset, _| { mapping .write_obj(val, offset) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Returns a `VolatileSlice` of `len` bytes starting at `addr`. Returns an error if the slice /// is not a subset of this `GuestMemory`. /// /// # Examples /// * Write `99` to 30 bytes starting at guest address 0x1010. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError}; /// # fn test_volatile_slice() -> Result<(), GuestMemoryError> { /// # let start_addr = GuestAddress(0x1000); /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?; /// let vslice = gm.get_slice_at_addr(GuestAddress(0x1010), 30)?; /// vslice.write_bytes(99); /// # Ok(()) /// # } /// ``` pub fn get_slice_at_addr(&self, addr: GuestAddress, len: usize) -> Result { self.regions .iter() .find(|region| region.contains(addr)) .ok_or(Error::InvalidGuestAddress(addr)) .and_then(|region| { // The cast to a usize is safe here because we know that `region.contains(addr)` and // it's not possible for a memory region to be larger than what fits in a usize. region .mapping .get_slice(addr.offset_from(region.start()) as usize, len) .map_err(Error::VolatileMemoryAccess) }) } /// Returns a `VolatileRef` to an object at `addr`. Returns Ok(()) if the object fits, or Err if /// it extends past the end. /// /// # Examples /// * Get a &u64 at offset 0x1010. /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError}; /// # fn test_ref_u64() -> Result<(), GuestMemoryError> { /// # let start_addr = GuestAddress(0x1000); /// # let mut gm = GuestMemory::new(&vec![(start_addr, 0x400)])?; /// gm.write_obj_at_addr(47u64, GuestAddress(0x1010))?; /// let vref = gm.get_ref_at_addr::(GuestAddress(0x1010))?; /// assert_eq!(vref.load(), 47u64); /// # Ok(()) /// # } /// ``` pub fn get_ref_at_addr(&self, addr: GuestAddress) -> Result> { let buf = self.get_slice_at_addr(addr, size_of::())?; // Safe because we have know that `buf` is at least `size_of::()` bytes and that the // returned reference will not outlive this `GuestMemory`. Ok(unsafe { VolatileRef::new(buf.as_mut_ptr() as *mut T) }) } /// Reads data from a file descriptor and writes it to guest memory. /// /// # Arguments /// * `guest_addr` - Begin writing memory at this offset. /// * `src` - Read from `src` to memory. /// * `count` - Read `count` bytes from `src` to memory. /// /// # Examples /// /// * Read bytes from /dev/urandom /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # use std::fs::File; /// # use std::path::Path; /// # fn test_read_random() -> Result { /// # let start_addr = GuestAddress(0x1000); /// # let gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// let mut file = File::open(Path::new("/dev/urandom")).map_err(|_| ())?; /// let addr = GuestAddress(0x1010); /// gm.read_to_memory(addr, &mut file, 128).map_err(|_| ())?; /// let read_addr = addr.checked_add(8).ok_or(())?; /// let rand_val: u32 = gm.read_obj_from_addr(read_addr).map_err(|_| ())?; /// # Ok(rand_val) /// # } /// ``` pub fn read_to_memory( &self, guest_addr: GuestAddress, src: &dyn AsRawDescriptor, count: usize, ) -> Result<()> { self.do_in_region(guest_addr, move |mapping, offset, _| { mapping .read_to_memory(offset, src, count) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Writes data from memory to a file descriptor. /// /// # Arguments /// * `guest_addr` - Begin reading memory from this offset. /// * `dst` - Write from memory to `dst`. /// * `count` - Read `count` bytes from memory to `src`. /// /// # Examples /// /// * Write 128 bytes to /dev/null /// /// ``` /// # use base::MemoryMapping; /// # use vm_memory::{GuestAddress, GuestMemory}; /// # use std::fs::File; /// # use std::path::Path; /// # fn test_write_null() -> Result<(), ()> { /// # let start_addr = GuestAddress(0x1000); /// # let gm = GuestMemory::new(&vec![(start_addr, 0x400)]).map_err(|_| ())?; /// let mut file = File::open(Path::new("/dev/null")).map_err(|_| ())?; /// let addr = GuestAddress(0x1010); /// gm.write_from_memory(addr, &mut file, 128).map_err(|_| ())?; /// # Ok(()) /// # } /// ``` pub fn write_from_memory( &self, guest_addr: GuestAddress, dst: &dyn AsRawDescriptor, count: usize, ) -> Result<()> { self.do_in_region(guest_addr, move |mapping, offset, _| { mapping .write_from_memory(offset, dst, count) .map_err(|e| Error::MemoryAccess(guest_addr, e)) }) } /// Convert a GuestAddress into a pointer in the address space of this /// process. This should only be necessary for giving addresses to the /// kernel, as with vhost ioctls. Normal reads/writes to guest memory should /// be done through `write_from_memory`, `read_obj_from_addr`, etc. /// /// # Arguments /// * `guest_addr` - Guest address to convert. /// /// # Examples /// /// ``` /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_host_addr() -> Result<(), ()> { /// let start_addr = GuestAddress(0x1000); /// let mut gm = GuestMemory::new(&vec![(start_addr, 0x500)]).map_err(|_| ())?; /// let addr = gm.get_host_address(GuestAddress(0x1200)).unwrap(); /// println!("Host address is {:p}", addr); /// Ok(()) /// # } /// ``` pub fn get_host_address(&self, guest_addr: GuestAddress) -> Result<*const u8> { self.do_in_region(guest_addr, |mapping, offset, _| { // This is safe; `do_in_region` already checks that offset is in // bounds. Ok(unsafe { mapping.as_ptr().add(offset) } as *const u8) }) } /// Convert a GuestAddress into a pointer in the address space of this /// process, and verify that the provided size define a valid range within /// a single memory region. Similar to get_host_address(), this should only /// be used for giving addresses to the kernel. /// /// # Arguments /// * `guest_addr` - Guest address to convert. /// * `size` - Size of the address range to be converted. /// /// # Examples /// /// ``` /// # use vm_memory::{GuestAddress, GuestMemory}; /// # fn test_host_addr() -> Result<(), ()> { /// let start_addr = GuestAddress(0x1000); /// let mut gm = GuestMemory::new(&vec![(start_addr, 0x500)]).map_err(|_| ())?; /// let addr = gm.get_host_address_range(GuestAddress(0x1200), 0x200).unwrap(); /// println!("Host address is {:p}", addr); /// Ok(()) /// # } /// ``` pub fn get_host_address_range( &self, guest_addr: GuestAddress, size: usize, ) -> Result<*const u8> { if size == 0 { return Err(Error::InvalidSize(size)); } // Assume no overlap among regions self.do_in_region(guest_addr, |mapping, offset, _| { if mapping .size() .checked_sub(offset) .map_or(true, |v| v < size) { return Err(Error::InvalidGuestAddress(guest_addr)); } // This is safe; `do_in_region` already checks that offset is in // bounds. Ok(unsafe { mapping.as_ptr().add(offset) } as *const u8) }) } /// Returns a reference to the region that backs the given address. pub fn shm_region( &self, guest_addr: GuestAddress, ) -> Result<&(dyn AsRawDescriptor + Send + Sync)> { self.regions .iter() .find(|region| region.contains(guest_addr)) .ok_or(Error::InvalidGuestAddress(guest_addr)) .map(|region| region.shared_obj.as_ref()) } /// Returns the region that contains the memory at `offset` from the base of guest memory. pub fn offset_region(&self, offset: u64) -> Result<&(dyn AsRawDescriptor + Send + Sync)> { self.shm_region( self.checked_offset(self.regions[0].guest_base, offset) .ok_or(Error::InvalidOffset(offset))?, ) } /// Loops over all guest memory regions of `self`, and performs the callback function `F` in /// the target region that contains `guest_addr`. The callback function `F` takes in: /// /// (i) the memory mapping associated with the target region. /// (ii) the relative offset from the start of the target region to `guest_addr`. /// (iii) the absolute offset from the start of the memory mapping to the target region. /// /// If no target region is found, an error is returned. The callback function `F` may return /// an Ok(`T`) on success or a `GuestMemoryError` on failure. pub fn do_in_region(&self, guest_addr: GuestAddress, cb: F) -> Result where F: FnOnce(&MemoryMapping, usize, u64) -> Result, { self.regions .iter() .find(|region| region.contains(guest_addr)) .ok_or(Error::InvalidGuestAddress(guest_addr)) .and_then(|region| { cb( ®ion.mapping, guest_addr.offset_from(region.start()) as usize, region.obj_offset, ) }) } /// Convert a GuestAddress into an offset within the associated shm region. /// /// Due to potential gaps within GuestMemory, it is helpful to know the /// offset within the shm where a given address is found. This offset /// can then be passed to another process mapping the shm to read data /// starting at that address. /// /// # Arguments /// * `guest_addr` - Guest address to convert. /// /// # Examples /// /// ``` /// # use vm_memory::{GuestAddress, GuestMemory}; /// let addr_a = GuestAddress(0x1000); /// let addr_b = GuestAddress(0x8000); /// let mut gm = GuestMemory::new(&vec![ /// (addr_a, 0x2000), /// (addr_b, 0x3000)]).expect("failed to create GuestMemory"); /// let offset = gm.offset_from_base(GuestAddress(0x9500)) /// .expect("failed to get offset"); /// assert_eq!(offset, 0x3500); /// ``` pub fn offset_from_base(&self, guest_addr: GuestAddress) -> Result { self.regions .iter() .find(|region| region.contains(guest_addr)) .ok_or(Error::InvalidGuestAddress(guest_addr)) .map(|region| region.obj_offset + guest_addr.offset_from(region.start())) } } // It is safe to implement BackingMemory because GuestMemory can be mutated any time already. unsafe impl BackingMemory for GuestMemory { fn get_volatile_slice( &self, mem_range: cros_async::MemRegion, ) -> mem::Result> { self.get_slice_at_addr(GuestAddress(mem_range.offset as u64), mem_range.len) .map_err(|_| mem::Error::InvalidOffset(mem_range.offset, mem_range.len)) } } #[cfg(test)] mod tests { use super::*; use base::kernel_has_memfd; #[test] fn test_alignment() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); assert!(GuestMemory::new(&[(start_addr1, 0x100), (start_addr2, 0x400)]).is_err()); assert!(GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).is_ok()); } #[test] fn two_regions() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x4000); assert!(GuestMemory::new(&[(start_addr1, 0x4000), (start_addr2, 0x4000)]).is_ok()); } #[test] fn overlap_memory() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); assert!(GuestMemory::new(&[(start_addr1, 0x2000), (start_addr2, 0x2000)]).is_err()); } #[test] fn region_hole() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x4000); let gm = GuestMemory::new(&[(start_addr1, 0x2000), (start_addr2, 0x2000)]).unwrap(); assert!(gm.address_in_range(GuestAddress(0x1000))); assert!(!gm.address_in_range(GuestAddress(0x3000))); assert!(gm.address_in_range(GuestAddress(0x5000))); assert!(!gm.address_in_range(GuestAddress(0x6000))); assert!(!gm.address_in_range(GuestAddress(0x6000))); assert!(gm.range_overlap(GuestAddress(0x1000), GuestAddress(0x3000))); assert!(!gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x4000))); assert!(gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x7000))); assert!(gm.checked_offset(GuestAddress(0x1000), 0x1000).is_none()); assert!(gm.checked_offset(GuestAddress(0x5000), 0x800).is_some()); assert!(gm.checked_offset(GuestAddress(0x5000), 0x1000).is_none()); } #[test] fn test_read_u64() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); let gm = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap(); let val1: u64 = 0xaa55aa55aa55aa55; let val2: u64 = 0x55aa55aa55aa55aa; gm.write_obj_at_addr(val1, GuestAddress(0x500)).unwrap(); gm.write_obj_at_addr(val2, GuestAddress(0x1000 + 32)) .unwrap(); let num1: u64 = gm.read_obj_from_addr(GuestAddress(0x500)).unwrap(); let num2: u64 = gm.read_obj_from_addr(GuestAddress(0x1000 + 32)).unwrap(); assert_eq!(val1, num1); assert_eq!(val2, num2); } #[test] fn test_ref_load_u64() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); let gm = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap(); let val1: u64 = 0xaa55aa55aa55aa55; let val2: u64 = 0x55aa55aa55aa55aa; gm.write_obj_at_addr(val1, GuestAddress(0x500)).unwrap(); gm.write_obj_at_addr(val2, GuestAddress(0x1000 + 32)) .unwrap(); let num1: u64 = gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().load(); let num2: u64 = gm .get_ref_at_addr(GuestAddress(0x1000 + 32)) .unwrap() .load(); assert_eq!(val1, num1); assert_eq!(val2, num2); } #[test] fn test_ref_store_u64() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); let gm = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x1000)]).unwrap(); let val1: u64 = 0xaa55aa55aa55aa55; let val2: u64 = 0x55aa55aa55aa55aa; gm.get_ref_at_addr(GuestAddress(0x500)).unwrap().store(val1); gm.get_ref_at_addr(GuestAddress(0x1000 + 32)) .unwrap() .store(val2); let num1: u64 = gm.read_obj_from_addr(GuestAddress(0x500)).unwrap(); let num2: u64 = gm.read_obj_from_addr(GuestAddress(0x1000 + 32)).unwrap(); assert_eq!(val1, num1); assert_eq!(val2, num2); } #[test] fn test_memory_size() { let start_region1 = GuestAddress(0x0); let size_region1 = 0x1000; let start_region2 = GuestAddress(0x10000); let size_region2 = 0x2000; let gm = GuestMemory::new(&[(start_region1, size_region1), (start_region2, size_region2)]) .unwrap(); let mem_size = gm.memory_size(); assert_eq!(mem_size, size_region1 + size_region2); } // Get the base address of the mapping for a GuestAddress. fn get_mapping(mem: &GuestMemory, addr: GuestAddress) -> Result<*const u8> { mem.do_in_region(addr, |mapping, _, _| Ok(mapping.as_ptr() as *const u8)) } #[test] fn guest_to_host() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); let mem = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x4000)]).unwrap(); // Verify the host addresses match what we expect from the mappings. let addr1_base = get_mapping(&mem, start_addr1).unwrap(); let addr2_base = get_mapping(&mem, start_addr2).unwrap(); let host_addr1 = mem.get_host_address(start_addr1).unwrap(); let host_addr2 = mem.get_host_address(start_addr2).unwrap(); assert_eq!(host_addr1, addr1_base); assert_eq!(host_addr2, addr2_base); // Check that a bad address returns an error. let bad_addr = GuestAddress(0x123456); assert!(mem.get_host_address(bad_addr).is_err()); } #[test] fn guest_to_host_range() { let start_addr1 = GuestAddress(0x0); let start_addr2 = GuestAddress(0x1000); let mem = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x4000)]).unwrap(); // Verify the host addresses match what we expect from the mappings. let addr1_base = get_mapping(&mem, start_addr1).unwrap(); let addr2_base = get_mapping(&mem, start_addr2).unwrap(); let host_addr1 = mem.get_host_address_range(start_addr1, 0x1000).unwrap(); let host_addr2 = mem.get_host_address_range(start_addr2, 0x1000).unwrap(); assert_eq!(host_addr1, addr1_base); assert_eq!(host_addr2, addr2_base); let host_addr3 = mem.get_host_address_range(start_addr2, 0x2000).unwrap(); assert_eq!(host_addr3, addr2_base); // Check that a valid guest address with an invalid size returns an error. assert!(mem.get_host_address_range(start_addr1, 0x2000).is_err()); // Check that a bad address returns an error. let bad_addr = GuestAddress(0x123456); assert!(mem.get_host_address_range(bad_addr, 0x1000).is_err()); } #[test] fn shm_offset() { if !kernel_has_memfd() { return; } let start_region1 = GuestAddress(0x0); let size_region1 = 0x1000; let start_region2 = GuestAddress(0x10000); let size_region2 = 0x2000; let gm = GuestMemory::new(&[(start_region1, size_region1), (start_region2, size_region2)]) .unwrap(); gm.write_obj_at_addr(0x1337u16, GuestAddress(0x0)).unwrap(); gm.write_obj_at_addr(0x0420u16, GuestAddress(0x10000)) .unwrap(); let _ = gm.with_regions::<_, ()>(|index, _, size, _, obj, offset| { let shm = match obj { BackingObject::Shm(s) => s, _ => { panic!("backing object isn't SharedMemory"); } }; let mmap = MemoryMappingBuilder::new(size) .from_shared_memory(shm) .offset(offset) .build() .unwrap(); if index == 0 { assert!(mmap.read_obj::(0x0).unwrap() == 0x1337u16); } if index == 1 { assert!(mmap.read_obj::(0x0).unwrap() == 0x0420u16); } Ok(()) }); } }