#[cfg(feature = "nanvix-unstable")]
use std::mem::offset_of;
use flatbuffers::FlatBufferBuilder;
use hyperlight_common::flatbuffer_wrappers::function_call::{
FunctionCall, validate_guest_function_call_buffer,
};
use hyperlight_common::flatbuffer_wrappers::function_types::FunctionCallResult;
use hyperlight_common::flatbuffer_wrappers::guest_log_data::GuestLogData;
use hyperlight_common::vmem::{self, PAGE_TABLE_SIZE, PageTableEntry, PhysAddr};
#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
use hyperlight_common::vmem::{BasicMapping, MappingKind};
use tracing::{Span, instrument};
use super::layout::SandboxMemoryLayout;
use super::shared_mem::{
ExclusiveSharedMemory, GuestSharedMemory, HostSharedMemory, ReadonlySharedMemory, SharedMemory,
};
use crate::hypervisor::regs::CommonSpecialRegisters;
use crate::mem::memory_region::MemoryRegion;
#[cfg(crashdump)]
use crate::mem::memory_region::{CrashDumpRegion, MemoryRegionFlags, MemoryRegionType};
use crate::sandbox::snapshot::{NextAction, Snapshot};
use crate::{Result, new_error};
#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
fn mapping_kind_to_flags(kind: &MappingKind) -> (MemoryRegionFlags, MemoryRegionType) {
match kind {
MappingKind::Basic(BasicMapping {
readable,
writable,
executable,
}) => {
let mut flags = MemoryRegionFlags::empty();
if *readable {
flags |= MemoryRegionFlags::READ;
}
if *writable {
flags |= MemoryRegionFlags::WRITE;
}
if *executable {
flags |= MemoryRegionFlags::EXECUTE;
}
(flags, MemoryRegionType::Snapshot)
}
MappingKind::Cow(cow) => {
let mut flags = MemoryRegionFlags::empty();
if cow.readable {
flags |= MemoryRegionFlags::READ;
}
if cow.executable {
flags |= MemoryRegionFlags::EXECUTE;
}
(flags, MemoryRegionType::Scratch)
}
MappingKind::Unmapped => (MemoryRegionFlags::empty(), MemoryRegionType::Snapshot),
}
}
#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
fn try_coalesce_region(
regions: &mut [CrashDumpRegion],
virt_base: usize,
virt_end: usize,
host_base: usize,
flags: MemoryRegionFlags,
) -> bool {
if let Some(last) = regions.last_mut()
&& last.guest_region.end == virt_base
&& last.host_region.end == host_base
&& last.flags == flags
{
last.guest_region.end = virt_end;
last.host_region.end = host_base + (virt_end - virt_base);
return true;
}
false
}
mod unused_hack {
#[cfg(not(unshared_snapshot_mem))]
use crate::mem::shared_mem::ReadonlySharedMemory;
use crate::mem::shared_mem::SharedMemory;
pub trait SnapshotSharedMemoryT {
type T<S: SharedMemory>;
}
pub struct SnapshotSharedMemory_;
impl SnapshotSharedMemoryT for SnapshotSharedMemory_ {
#[cfg(not(unshared_snapshot_mem))]
type T<S: SharedMemory> = ReadonlySharedMemory;
#[cfg(unshared_snapshot_mem)]
type T<S: SharedMemory> = S;
}
pub type SnapshotSharedMemory<S> = <SnapshotSharedMemory_ as SnapshotSharedMemoryT>::T<S>;
}
impl ReadonlySharedMemory {
pub(crate) fn to_mgr_snapshot_mem(
&self,
) -> Result<SnapshotSharedMemory<ExclusiveSharedMemory>> {
#[cfg(not(unshared_snapshot_mem))]
let ret = self.clone();
#[cfg(unshared_snapshot_mem)]
let ret = self.copy_to_writable()?;
Ok(ret)
}
}
pub(crate) use unused_hack::SnapshotSharedMemory;
#[derive(Clone)]
pub(crate) struct SandboxMemoryManager<S: SharedMemory> {
pub(crate) shared_mem: SnapshotSharedMemory<S>,
pub(crate) scratch_mem: S,
pub(crate) layout: SandboxMemoryLayout,
pub(crate) entrypoint: NextAction,
pub(crate) mapped_rgns: u64,
pub(crate) abort_buffer: Vec<u8>,
}
pub(crate) struct GuestPageTableBuffer {
buffer: std::cell::RefCell<Vec<u8>>,
phys_base: usize,
}
impl vmem::TableReadOps for GuestPageTableBuffer {
type TableAddr = (usize, usize);
fn entry_addr(addr: (usize, usize), offset: u64) -> (usize, usize) {
let phys = Self::to_phys(addr) + offset;
Self::from_phys(phys)
}
unsafe fn read_entry(&self, addr: (usize, usize)) -> PageTableEntry {
let b = self.buffer.borrow();
let byte_offset =
(addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8;
b.get(byte_offset..byte_offset + 8)
.and_then(|s| <[u8; 8]>::try_from(s).ok())
.map(u64::from_ne_bytes)
.unwrap_or(0)
}
fn to_phys(addr: (usize, usize)) -> PhysAddr {
(addr.0 as u64 * PAGE_TABLE_SIZE as u64) + (addr.1 as u64 * 8)
}
fn from_phys(addr: PhysAddr) -> (usize, usize) {
(
addr as usize / PAGE_TABLE_SIZE,
(addr as usize % PAGE_TABLE_SIZE) / 8,
)
}
fn root_table(&self) -> (usize, usize) {
(self.phys_base / PAGE_TABLE_SIZE, 0)
}
}
impl vmem::TableOps for GuestPageTableBuffer {
type TableMovability = vmem::MayNotMoveTable;
unsafe fn alloc_table(&self) -> (usize, usize) {
let mut b = self.buffer.borrow_mut();
let table_index = b.len() / PAGE_TABLE_SIZE;
let new_len = b.len() + PAGE_TABLE_SIZE;
b.resize(new_len, 0);
(self.phys_base / PAGE_TABLE_SIZE + table_index, 0)
}
unsafe fn write_entry(
&self,
addr: (usize, usize),
entry: PageTableEntry,
) -> Option<vmem::Void> {
let mut b = self.buffer.borrow_mut();
let byte_offset =
(addr.0 - self.phys_base / PAGE_TABLE_SIZE) * PAGE_TABLE_SIZE + addr.1 * 8;
if let Some(slice) = b.get_mut(byte_offset..byte_offset + 8) {
slice.copy_from_slice(&entry.to_ne_bytes());
}
None
}
unsafe fn update_root(&self, impossible: vmem::Void) {
match impossible {}
}
}
impl GuestPageTableBuffer {
pub(crate) fn new(phys_base: usize) -> Self {
GuestPageTableBuffer {
buffer: std::cell::RefCell::new(vec![0u8; PAGE_TABLE_SIZE]),
phys_base,
}
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn size(&self) -> usize {
self.buffer.borrow().len()
}
pub(crate) fn into_bytes(self) -> Box<[u8]> {
self.buffer.into_inner().into_boxed_slice()
}
}
impl<S> SandboxMemoryManager<S>
where
S: SharedMemory,
{
#[instrument(skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn new(
layout: SandboxMemoryLayout,
shared_mem: SnapshotSharedMemory<S>,
scratch_mem: S,
entrypoint: NextAction,
) -> Self {
Self {
layout,
shared_mem,
scratch_mem,
entrypoint,
mapped_rgns: 0,
abort_buffer: Vec::new(),
}
}
pub(crate) fn get_abort_buffer_mut(&mut self) -> &mut Vec<u8> {
&mut self.abort_buffer
}
pub(crate) fn snapshot(
&mut self,
sandbox_id: u64,
mapped_regions: Vec<MemoryRegion>,
root_pt_gpa: u64,
rsp_gva: u64,
sregs: CommonSpecialRegisters,
entrypoint: NextAction,
) -> Result<Snapshot> {
Snapshot::new(
&mut self.shared_mem,
&mut self.scratch_mem,
sandbox_id,
self.layout,
crate::mem::exe::LoadInfo::dummy(),
mapped_regions,
root_pt_gpa,
rsp_gva,
sregs,
entrypoint,
)
}
}
impl SandboxMemoryManager<ExclusiveSharedMemory> {
pub(crate) fn from_snapshot(s: &Snapshot) -> Result<Self> {
let layout = *s.layout();
let shared_mem = s.memory().to_mgr_snapshot_mem()?;
let scratch_mem = ExclusiveSharedMemory::new(s.layout().get_scratch_size())?;
let entrypoint = s.entrypoint();
Ok(Self::new(layout, shared_mem, scratch_mem, entrypoint))
}
pub fn build(
self,
) -> Result<(
SandboxMemoryManager<HostSharedMemory>,
SandboxMemoryManager<GuestSharedMemory>,
)> {
let (hshm, gshm) = self.shared_mem.build();
let (hscratch, gscratch) = self.scratch_mem.build();
let mut host_mgr = SandboxMemoryManager {
shared_mem: hshm,
scratch_mem: hscratch,
layout: self.layout,
entrypoint: self.entrypoint,
mapped_rgns: self.mapped_rgns,
abort_buffer: self.abort_buffer,
};
let guest_mgr = SandboxMemoryManager {
shared_mem: gshm,
scratch_mem: gscratch,
layout: self.layout,
entrypoint: self.entrypoint,
mapped_rgns: self.mapped_rgns,
abort_buffer: Vec::new(), };
host_mgr.update_scratch_bookkeeping()?;
Ok((host_mgr, guest_mgr))
}
}
impl SandboxMemoryManager<HostSharedMemory> {
#[cfg(feature = "nanvix-unstable")]
pub(crate) fn write_file_mapping_entry(
&mut self,
guest_addr: u64,
size: u64,
label: &[u8; hyperlight_common::mem::FILE_MAPPING_LABEL_MAX_LEN + 1],
) -> Result<()> {
use hyperlight_common::mem::{FileMappingInfo, MAX_FILE_MAPPINGS};
let current_count =
self.shared_mem
.read::<u64>(self.layout.get_file_mappings_size_offset())? as usize;
if current_count >= MAX_FILE_MAPPINGS {
return Err(crate::new_error!(
"file mapping limit reached ({} of {})",
current_count,
MAX_FILE_MAPPINGS,
));
}
let entry_offset = self.layout.get_file_mappings_array_offset()
+ current_count * std::mem::size_of::<FileMappingInfo>();
let guest_addr_offset = offset_of!(FileMappingInfo, guest_addr);
let size_offset = offset_of!(FileMappingInfo, size);
let label_offset = offset_of!(FileMappingInfo, label);
self.shared_mem
.write::<u64>(entry_offset + guest_addr_offset, guest_addr)?;
self.shared_mem
.write::<u64>(entry_offset + size_offset, size)?;
self.shared_mem
.copy_from_slice(label, entry_offset + label_offset)?;
let new_count = (current_count + 1) as u64;
self.shared_mem
.write::<u64>(self.layout.get_file_mappings_size_offset(), new_count)?;
Ok(())
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_host_function_call(&mut self) -> Result<FunctionCall> {
self.scratch_mem.try_pop_buffer_into::<FunctionCall>(
self.layout.get_output_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_output_data_size(),
)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn write_response_from_host_function_call(
&mut self,
res: &FunctionCallResult,
) -> Result<()> {
let mut builder = FlatBufferBuilder::new();
let data = res.encode(&mut builder);
self.scratch_mem.push_buffer(
self.layout.get_input_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_input_data_size(),
data,
)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn write_guest_function_call(&mut self, buffer: &[u8]) -> Result<()> {
validate_guest_function_call_buffer(buffer).map_err(|e| {
new_error!(
"Guest function call buffer validation failed: {}",
e.to_string()
)
})?;
self.scratch_mem.push_buffer(
self.layout.get_input_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_input_data_size(),
buffer,
)?;
Ok(())
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn get_guest_function_call_result(&mut self) -> Result<FunctionCallResult> {
self.scratch_mem.try_pop_buffer_into::<FunctionCallResult>(
self.layout.get_output_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_output_data_size(),
)
}
#[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
pub(crate) fn read_guest_log_data(&mut self) -> Result<GuestLogData> {
self.scratch_mem.try_pop_buffer_into::<GuestLogData>(
self.layout.get_output_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_output_data_size(),
)
}
pub(crate) fn clear_io_buffers(&mut self) {
loop {
let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
self.layout.get_output_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_output_data_size(),
) else {
break;
};
}
loop {
let Ok(_) = self.scratch_mem.try_pop_buffer_into::<Vec<u8>>(
self.layout.get_input_data_buffer_scratch_host_offset(),
self.layout.sandbox_memory_config.get_input_data_size(),
) else {
break;
};
}
}
pub(crate) fn restore_snapshot(
&mut self,
snapshot: &Snapshot,
) -> Result<(
Option<SnapshotSharedMemory<GuestSharedMemory>>,
Option<GuestSharedMemory>,
)> {
let gsnapshot = if *snapshot.memory() == self.shared_mem {
None
} else {
let new_snapshot_mem = snapshot.memory().to_mgr_snapshot_mem()?;
let (hsnapshot, gsnapshot) = new_snapshot_mem.build();
self.shared_mem = hsnapshot;
Some(gsnapshot)
};
let new_scratch_size = snapshot.layout().get_scratch_size();
let gscratch = if new_scratch_size == self.scratch_mem.mem_size() {
self.scratch_mem.zero()?;
None
} else {
let new_scratch_mem = ExclusiveSharedMemory::new(new_scratch_size)?;
let (hscratch, gscratch) = new_scratch_mem.build();
self.scratch_mem = hscratch;
Some(gscratch)
};
self.layout = *snapshot.layout();
self.update_scratch_bookkeeping()?;
Ok((gsnapshot, gscratch))
}
#[inline]
fn update_scratch_bookkeeping_item(&mut self, offset: u64, value: u64) -> Result<()> {
let scratch_size = self.scratch_mem.mem_size();
let base_offset = scratch_size - offset as usize;
self.scratch_mem.write::<u64>(base_offset, value)
}
fn update_scratch_bookkeeping(&mut self) -> Result<()> {
use hyperlight_common::layout::*;
let scratch_size = self.scratch_mem.mem_size();
self.update_scratch_bookkeeping_item(SCRATCH_TOP_SIZE_OFFSET, scratch_size as u64)?;
self.update_scratch_bookkeeping_item(
SCRATCH_TOP_ALLOCATOR_OFFSET,
self.layout.get_first_free_scratch_gpa(),
)?;
self.scratch_mem.write::<u64>(
self.layout.get_input_data_buffer_scratch_host_offset(),
SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
)?;
self.scratch_mem.write::<u64>(
self.layout.get_output_data_buffer_scratch_host_offset(),
SandboxMemoryLayout::STACK_POINTER_SIZE_BYTES,
)?;
let snapshot_pt_end = self.shared_mem.mem_size();
let snapshot_pt_size = self.layout.get_pt_size();
let snapshot_pt_start = snapshot_pt_end - snapshot_pt_size;
self.scratch_mem.with_exclusivity(|scratch| {
#[cfg(not(unshared_snapshot_mem))]
let bytes = &self.shared_mem.as_slice()[snapshot_pt_start..snapshot_pt_end];
#[cfg(unshared_snapshot_mem)]
let bytes = {
let mut bytes = vec![0u8; snapshot_pt_size];
self.shared_mem
.copy_to_slice(&mut bytes, snapshot_pt_start)?;
bytes
};
#[allow(clippy::needless_borrow)]
scratch.copy_from_slice(&bytes, self.layout.get_pt_base_scratch_offset())
})??;
Ok(())
}
#[cfg(all(feature = "crashdump", not(feature = "nanvix-unstable")))]
pub(crate) fn get_guest_memory_regions(
&mut self,
root_pt: u64,
mmap_regions: &[MemoryRegion],
) -> Result<Vec<CrashDumpRegion>> {
use crate::sandbox::snapshot::SharedMemoryPageTableBuffer;
let len = hyperlight_common::layout::MAX_GVA;
let regions = self.shared_mem.with_contents(|snapshot| {
self.scratch_mem.with_contents(|scratch| {
let pt_buf =
SharedMemoryPageTableBuffer::new(snapshot, scratch, self.layout, root_pt);
let mappings: Vec<_> =
unsafe { hyperlight_common::vmem::virt_to_phys(&pt_buf, 0, len as u64) }
.collect();
if mappings.is_empty() {
return Err(new_error!("No page table mappings found (len {len})",));
}
let mut regions: Vec<CrashDumpRegion> = Vec::new();
for mapping in &mappings {
let virt_base = mapping.virt_base as usize;
let virt_end = (mapping.virt_base + mapping.len) as usize;
if let Some(resolved) = self.layout.resolve_gpa(mapping.phys_base, mmap_regions)
{
let (flags, region_type) = mapping_kind_to_flags(&mapping.kind);
let resolved = resolved.with_memories(snapshot, scratch);
let contents = resolved.as_ref();
let host_base = contents.as_ptr() as usize;
let host_len = (mapping.len as usize).min(contents.len());
if try_coalesce_region(&mut regions, virt_base, virt_end, host_base, flags)
{
continue;
}
regions.push(CrashDumpRegion {
guest_region: virt_base..virt_end,
host_region: host_base..host_base + host_len,
flags,
region_type,
});
}
}
Ok(regions)
})
})???;
Ok(regions)
}
#[cfg(all(feature = "crashdump", feature = "nanvix-unstable"))]
pub(crate) fn get_guest_memory_regions(
&mut self,
_root_pt: u64,
mmap_regions: &[MemoryRegion],
) -> Result<Vec<CrashDumpRegion>> {
use crate::mem::memory_region::HostGuestMemoryRegion;
let snapshot_base = SandboxMemoryLayout::BASE_ADDRESS;
let snapshot_size = self.shared_mem.mem_size();
let snapshot_host = self.shared_mem.base_addr();
let scratch_size = self.scratch_mem.mem_size();
let scratch_gva = hyperlight_common::layout::scratch_base_gva(scratch_size) as usize;
let scratch_host = self.scratch_mem.base_addr();
let mut regions = vec![
CrashDumpRegion {
guest_region: snapshot_base..snapshot_base + snapshot_size,
host_region: snapshot_host..snapshot_host + snapshot_size,
flags: MemoryRegionFlags::READ | MemoryRegionFlags::EXECUTE,
region_type: MemoryRegionType::Snapshot,
},
CrashDumpRegion {
guest_region: scratch_gva..scratch_gva + scratch_size,
host_region: scratch_host..scratch_host + scratch_size,
flags: MemoryRegionFlags::READ
| MemoryRegionFlags::WRITE
| MemoryRegionFlags::EXECUTE,
region_type: MemoryRegionType::Scratch,
},
];
for rgn in mmap_regions {
regions.push(CrashDumpRegion {
guest_region: rgn.guest_region.clone(),
host_region: HostGuestMemoryRegion::to_addr(rgn.host_region.start)
..HostGuestMemoryRegion::to_addr(rgn.host_region.end),
flags: rgn.flags,
region_type: rgn.region_type,
});
}
Ok(regions)
}
#[cfg(feature = "trace_guest")]
pub(crate) fn read_guest_memory_by_gva(
&mut self,
gva: u64,
len: usize,
root_pt: u64,
) -> Result<Vec<u8>> {
use hyperlight_common::vmem::PAGE_SIZE;
use crate::sandbox::snapshot::{SharedMemoryPageTableBuffer, access_gpa};
self.shared_mem.with_contents(|snap| {
self.scratch_mem.with_contents(|scratch| {
let pt_buf = SharedMemoryPageTableBuffer::new(snap, scratch, self.layout, root_pt);
let mappings: Vec<_> = unsafe {
hyperlight_common::vmem::virt_to_phys(&pt_buf, gva, len as u64)
}
.collect();
if mappings.is_empty() {
return Err(new_error!(
"No page table mappings found for GVA {:#x} (len {})",
gva,
len,
));
}
let mut result = Vec::with_capacity(len);
let mut current_gva = gva;
for mapping in &mappings {
if mapping.virt_base > current_gva {
return Err(new_error!(
"Page table walker returned mapping with virt_base {:#x} > current read position {:#x}",
mapping.virt_base,
current_gva,
));
}
let page_offset = (current_gva - mapping.virt_base) as usize;
let bytes_remaining = len - result.len();
let available_in_page = PAGE_SIZE - page_offset;
let bytes_to_copy = bytes_remaining.min(available_in_page);
let gpa = mapping.phys_base + page_offset as u64;
let (mem, offset) = access_gpa(snap, scratch, self.layout, gpa)
.ok_or_else(|| {
new_error!(
"Failed to resolve GPA {:#x} to host memory (GVA {:#x})",
gpa,
gva
)
})?;
let slice = mem
.get(offset..offset + bytes_to_copy)
.ok_or_else(|| {
new_error!(
"GPA {:#x} resolved to out-of-bounds host offset {} (need {} bytes)",
gpa,
offset,
bytes_to_copy
)
})?;
result.extend_from_slice(slice);
current_gva += bytes_to_copy as u64;
}
if result.len() != len {
tracing::error!(
"Page table walker returned mappings that don't cover the full requested length: got {}, expected {}",
result.len(),
len,
);
return Err(new_error!(
"Could not read full GVA range: got {} of {} bytes {:?}",
result.len(),
len,
mappings
));
}
Ok(result)
})
})??
}
}
#[cfg(test)]
#[cfg(all(not(feature = "nanvix-unstable"), target_arch = "x86_64"))]
mod tests {
use hyperlight_common::vmem::{MappingKind, PAGE_TABLE_SIZE};
use hyperlight_testing::sandbox_sizes::{LARGE_HEAP_SIZE, MEDIUM_HEAP_SIZE, SMALL_HEAP_SIZE};
use hyperlight_testing::simple_guest_as_string;
use crate::GuestBinary;
use crate::mem::memory_region::MemoryRegionFlags;
use crate::sandbox::SandboxConfiguration;
use crate::sandbox::snapshot::Snapshot;
fn verify_page_tables(name: &str, config: SandboxConfiguration) {
let path = simple_guest_as_string().expect("failed to get simple guest path");
let snapshot = Snapshot::from_env(GuestBinary::FilePath(path), config)
.unwrap_or_else(|e| panic!("{}: failed to create snapshot: {}", name, e));
let regions = snapshot.regions();
assert!(
unsafe { hyperlight_common::vmem::virt_to_phys(&snapshot, 0, 1) }
.next()
.is_none(),
"{}: NULL page (0x0) should NOT be mapped",
name
);
for region in regions {
let mut addr = region.guest_region.start as u64;
while addr < region.guest_region.end as u64 {
let mapping = unsafe { hyperlight_common::vmem::virt_to_phys(&snapshot, addr, 1) }
.next()
.unwrap_or_else(|| {
panic!(
"{}: {:?} region: address 0x{:x} is not mapped",
name, region.region_type, addr
)
});
assert_eq!(
mapping.phys_base, addr,
"{}: {:?} region: address 0x{:x} should identity map, got phys 0x{:x}",
name, region.region_type, addr, mapping.phys_base
);
let MappingKind::Basic(bm) = mapping.kind else {
panic!(
"{}: {:?} region: address 0x{:x} should be kind basic, got {:?}",
name, region.region_type, addr, mapping.kind
);
};
let actual = bm.writable;
let expected = region.flags.contains(MemoryRegionFlags::WRITE);
assert_eq!(
actual, expected,
"{}: {:?} region: address 0x{:x} has writable {}, expected {} (region flags: {:?})",
name, region.region_type, addr, actual, expected, region.flags
);
let actual = bm.executable;
let expected = region.flags.contains(MemoryRegionFlags::EXECUTE);
assert_eq!(
actual, expected,
"{}: {:?} region: address 0x{:x} has executable {}, expected {} (region flags: {:?})",
name, region.region_type, addr, actual, expected, region.flags
);
addr += PAGE_TABLE_SIZE as u64;
}
}
}
#[test]
fn test_page_tables_for_various_configurations() {
let test_cases: [(&str, SandboxConfiguration); 4] = [
("default", { SandboxConfiguration::default() }),
("small (8MB heap)", {
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(SMALL_HEAP_SIZE);
cfg
}),
("medium (64MB heap)", {
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(MEDIUM_HEAP_SIZE);
cfg
}),
("large (256MB heap)", {
let mut cfg = SandboxConfiguration::default();
cfg.set_heap_size(LARGE_HEAP_SIZE);
cfg.set_scratch_size(0x100000);
cfg
}),
];
for (name, config) in test_cases {
verify_page_tables(name, config);
}
}
}