mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-11 12:35:26 +00:00
In addition to SharedMemory, we allow File (e.g. mmap'd file) to be an backing object of MemoryRegion. BUG=b:194137301 TEST=kokoro Change-Id: I9dafd028eaf74cf34820ba3f16e458c08ec67cb8 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3159883 Tested-by: kokoro <noreply+kokoro@google.com> Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org> Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
1066 lines
38 KiB
Rust
1066 lines
38 KiB
Rust
// 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<T> = result::Result<T, Error>;
|
|
|
|
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<SharedMemory>),
|
|
File(Arc<File>),
|
|
}
|
|
|
|
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<dyn AsRawDescriptor + Sync + Send> 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<SharedMemory>,
|
|
) -> Result<Self> {
|
|
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<File>,
|
|
) -> Result<Self> {
|
|
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<RawDescriptor> {
|
|
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<SharedMemory> {
|
|
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<GuestMemory> {
|
|
// Create shm
|
|
|
|
let shm = Arc::new(GuestMemory::create_shm(ranges)?);
|
|
// Create memory regions
|
|
let mut regions = Vec::<MemoryRegion>::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<MemoryRegion>) -> Result<Self> {
|
|
// 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<GuestAddress> {
|
|
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<F, E>(&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<usize> {
|
|
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<usize> {
|
|
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<u64, ()> {
|
|
/// # 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<T: DataInit>(&self, guest_addr: GuestAddress) -> Result<T> {
|
|
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<T: DataInit>(&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<VolatileSlice> {
|
|
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::<u64>(GuestAddress(0x1010))?;
|
|
/// assert_eq!(vref.load(), 47u64);
|
|
/// # Ok(())
|
|
/// # }
|
|
/// ```
|
|
pub fn get_ref_at_addr<T: DataInit>(&self, addr: GuestAddress) -> Result<VolatileRef<T>> {
|
|
let buf = self.get_slice_at_addr(addr, size_of::<T>())?;
|
|
// Safe because we have know that `buf` is at least `size_of::<T>()` 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<u32, ()> {
|
|
/// # 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<F, T>(&self, guest_addr: GuestAddress, cb: F) -> Result<T>
|
|
where
|
|
F: FnOnce(&MemoryMapping, usize, u64) -> Result<T>,
|
|
{
|
|
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<u64> {
|
|
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<VolatileSlice<'_>> {
|
|
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::<u16>(0x0).unwrap() == 0x1337u16);
|
|
}
|
|
|
|
if index == 1 {
|
|
assert!(mmap.read_obj::<u16>(0x0).unwrap() == 0x0420u16);
|
|
}
|
|
|
|
Ok(())
|
|
});
|
|
}
|
|
}
|