use std::{
collections::BTreeMap,
ops::Deref,
result::Result as StdResult,
sync::{
atomic::{AtomicU64, AtomicUsize, Ordering},
Arc, RwLock,
},
};
use imbl::HashMap as ImHashMap;
use crate::{
emulation::{
memory::{
region::{MemoryProtection, MemoryRegion, SectionInfo},
statics::StaticFieldStorage,
},
EmValue, EmulationError, HeapRef, ManagedHeap,
},
metadata::token::Token,
Error, Result,
};
#[derive(Clone, Debug)]
pub struct SharedHeap {
inner: Arc<ManagedHeap>,
}
impl SharedHeap {
#[must_use]
pub fn new(max_size: usize) -> Self {
Self {
inner: Arc::new(ManagedHeap::new(max_size)),
}
}
pub fn from_heap(heap: ManagedHeap) -> Self {
Self {
inner: Arc::new(heap),
}
}
#[must_use]
pub fn heap(&self) -> &ManagedHeap {
&self.inner
}
#[must_use]
pub fn ref_count(&self) -> usize {
Arc::strong_count(&self.inner)
}
#[must_use]
pub fn is_unique(&self) -> bool {
Arc::strong_count(&self.inner) == 1
}
pub fn fork(&self) -> Result<Self> {
Ok(Self {
inner: Arc::new(self.inner.fork()?),
})
}
}
impl Default for SharedHeap {
fn default() -> Self {
Self::new(64 * 1024 * 1024) }
}
impl Deref for SharedHeap {
type Target = ManagedHeap;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub const DEFAULT_MAX_UNMANAGED_BYTES: usize = 64 * 1024 * 1024;
#[derive(Clone, Debug)]
struct PinnedArrayEntry {
array_ref: HeapRef,
base_addr: u64,
byte_length: usize,
element_size: usize,
}
#[derive(Debug)]
pub struct AddressSpace {
heap: SharedHeap,
regions: RwLock<BTreeMap<u64, MemoryRegion>>,
statics: StaticFieldStorage,
next_address: AtomicU64,
size: u64,
protection_overrides: RwLock<ImHashMap<u64, MemoryProtection>>,
monitor_locks: RwLock<ImHashMap<u64, u32>>,
pinned_arrays: RwLock<ImHashMap<u64, PinnedArrayEntry>>,
unmanaged_bytes: AtomicUsize,
max_unmanaged_bytes: AtomicUsize,
}
impl AddressSpace {
const PAGE_SIZE: u64 = super::page::PAGE_SIZE as u64;
#[must_use]
pub fn new() -> Self {
Self::with_config(64 * 1024 * 1024, 0x1_0000_0000) }
#[must_use]
pub fn with_config(heap_size: usize, address_space_size: u64) -> Self {
Self {
heap: SharedHeap::new(heap_size),
regions: RwLock::new(BTreeMap::new()),
statics: StaticFieldStorage::new(),
next_address: AtomicU64::new(0x1000_0000), size: address_space_size,
protection_overrides: RwLock::new(ImHashMap::new()),
monitor_locks: RwLock::new(ImHashMap::new()),
pinned_arrays: RwLock::new(ImHashMap::new()),
unmanaged_bytes: AtomicUsize::new(0),
max_unmanaged_bytes: AtomicUsize::new(DEFAULT_MAX_UNMANAGED_BYTES),
}
}
#[must_use]
pub fn with_heap(heap: SharedHeap) -> Self {
Self {
heap,
regions: RwLock::new(BTreeMap::new()),
statics: StaticFieldStorage::new(),
next_address: AtomicU64::new(0x1000_0000),
size: 0x1_0000_0000,
protection_overrides: RwLock::new(ImHashMap::new()),
monitor_locks: RwLock::new(ImHashMap::new()),
pinned_arrays: RwLock::new(ImHashMap::new()),
unmanaged_bytes: AtomicUsize::new(0),
max_unmanaged_bytes: AtomicUsize::new(DEFAULT_MAX_UNMANAGED_BYTES),
}
}
#[must_use]
pub fn heap(&self) -> &SharedHeap {
&self.heap
}
#[must_use]
pub fn managed_heap(&self) -> &ManagedHeap {
self.heap.heap()
}
#[must_use]
pub fn statics(&self) -> &StaticFieldStorage {
&self.statics
}
pub fn monitor_enter(&self, object_id: u64) -> u32 {
let Ok(mut locks) = self.monitor_locks.write() else {
return 0;
};
let count = locks
.get(&object_id)
.copied()
.unwrap_or(0)
.saturating_add(1);
*locks = locks.update(object_id, count);
count
}
pub fn monitor_exit(&self, object_id: u64) -> bool {
let Ok(mut locks) = self.monitor_locks.write() else {
return false;
};
match locks.get(&object_id).copied() {
Some(count) if count > 1 => {
*locks = locks.update(object_id, count.saturating_sub(1));
true
}
Some(1) => {
*locks = locks.without(&object_id);
true
}
_ => false, }
}
#[must_use]
pub fn monitor_is_locked(&self, object_id: u64) -> bool {
self.monitor_locks
.read()
.map(|l| l.get(&object_id).copied().unwrap_or(0) > 0)
.unwrap_or(false)
}
pub fn map_at(&self, address: u64, region: MemoryRegion) -> Result<()> {
let mut regions = self.regions.write().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
let base = region.base();
let predecessor = regions.range(..=base).next_back().map(|(_, r)| r);
let successor = regions
.range(base.saturating_add(1)..)
.next()
.map(|(_, r)| r);
for existing in [predecessor, successor].into_iter().flatten() {
if Self::regions_overlap(existing, ®ion) {
return Err(EmulationError::InvalidAddress {
address,
reason: "region overlaps with existing mapping".to_string(),
}
.into());
}
}
regions.insert(base, region);
Ok(())
}
pub fn map(&self, region: MemoryRegion) -> Result<u64> {
let size = region.size();
let aligned_size = size.saturating_add(0xFFF) & !0xFFF;
let base = self
.next_address
.fetch_add(aligned_size as u64, Ordering::SeqCst);
if region.is_pe_image() {
return Err(EmulationError::InternalError {
description: "PE images must use map_at with explicit base address".to_string(),
}
.into());
}
let region = region.with_base(base);
self.map_at(base, region)?;
Ok(base)
}
pub fn unmap(&self, base: u64) -> Result<()> {
let mut regions = self.regions.write().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
if regions.remove(&base).is_some() {
Ok(())
} else {
Err(EmulationError::InvalidAddress {
address: base,
reason: "no region at this address".to_string(),
}
.into())
}
}
pub fn read(&self, address: u64, len: usize) -> Result<Vec<u8>> {
if let Some(result) = self.read_pinned(address, len) {
return result;
}
if !self.covers_range(address, len)? {
return Err(EmulationError::InvalidAddress {
address,
reason: format!("read of {len} bytes is not fully mapped"),
}
.into());
}
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
let mut buffer = vec![0u8; len];
self.read_mapped(®ions, address, &mut buffer)?;
Ok(buffer)
}
pub fn read_into(&self, address: u64, dest: &mut [u8]) -> Result<()> {
if let Some(result) = self.read_pinned(address, dest.len()) {
let bytes = result?;
if bytes.len() != dest.len() {
return Err(EmulationError::InvalidAddress {
address,
reason: "pinned read length mismatch".to_string(),
}
.into());
}
dest.copy_from_slice(&bytes);
return Ok(());
}
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
self.read_mapped(®ions, address, dest)
}
fn read_mapped(
&self,
regions: &BTreeMap<u64, MemoryRegion>,
address: u64,
dest: &mut [u8],
) -> Result<()> {
let Some(region) = Self::region_containing(regions, address) else {
return Err(EmulationError::InvalidAddress {
address,
reason: "address not mapped".to_string(),
}
.into());
};
if !region.contains_range(address, dest.len()) {
return Err(EmulationError::InvalidAddress {
address,
reason: "address not mapped".to_string(),
}
.into());
}
self.check_access(region, address, dest.len(), MemoryProtection::READ)?;
if region.read_into(address, dest) {
Ok(())
} else {
Err(EmulationError::InvalidAddress {
address,
reason: "read failed".to_string(),
}
.into())
}
}
pub fn write(&self, address: u64, data: &[u8]) -> Result<()> {
if let Some(result) = self.write_pinned(address, data) {
return result;
}
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
let Some(region) = Self::region_containing(®ions, address) else {
return Err(EmulationError::InvalidAddress {
address,
reason: "address not mapped".to_string(),
}
.into());
};
if !region.contains_range(address, data.len()) {
return Err(EmulationError::InvalidAddress {
address,
reason: "address not mapped".to_string(),
}
.into());
}
self.check_access(region, address, data.len(), MemoryProtection::WRITE)?;
if region.write(address, data) {
Ok(())
} else {
Err(EmulationError::InvalidAddress {
address,
reason: "write failed".to_string(),
}
.into())
}
}
fn check_access(
&self,
region: &MemoryRegion,
address: u64,
len: usize,
required: MemoryProtection,
) -> Result<()> {
if len == 0 {
return Ok(());
}
let last = address.saturating_add(len.saturating_sub(1) as u64);
for probe in [address, last] {
let Some(protection) = self.protection_of(region, probe) else {
continue;
};
if protection.contains(MemoryProtection::GUARD) {
return Err(EmulationError::AccessViolation {
address: probe,
reason: "guard page".to_string(),
}
.into());
}
if !protection.contains(required) {
return Err(EmulationError::AccessViolation {
address: probe,
reason: format!("protection {protection:?} does not permit {required:?}"),
}
.into());
}
}
Ok(())
}
fn protection_of(&self, region: &MemoryRegion, address: u64) -> Option<MemoryProtection> {
let page_addr = address & !(Self::PAGE_SIZE - 1);
if let Ok(overrides) = self.protection_overrides.read() {
if let Some(&protection) = overrides.get(&page_addr) {
return Some(protection);
}
}
region.protection_at(address).ok()
}
pub fn read_exact<const N: usize>(&self, address: u64) -> Result<[u8; N]> {
let mut buffer = [0u8; N];
self.read_into(address, &mut buffer)?;
Ok(buffer)
}
pub fn read_u8(&self, address: u64) -> Result<u8> {
Ok(u8::from_le_bytes(self.read_exact::<1>(address)?))
}
pub fn read_u16(&self, address: u64) -> Result<u16> {
Ok(u16::from_le_bytes(self.read_exact::<2>(address)?))
}
pub fn read_u32(&self, address: u64) -> Result<u32> {
Ok(u32::from_le_bytes(self.read_exact::<4>(address)?))
}
pub fn read_u64(&self, address: u64) -> Result<u64> {
Ok(u64::from_le_bytes(self.read_exact::<8>(address)?))
}
#[must_use]
pub fn is_valid(&self, address: u64) -> bool {
if let Ok(pins) = self.pinned_arrays.read() {
for entry in pins.values() {
let end = entry.base_addr.saturating_add(entry.byte_length as u64);
if address >= entry.base_addr && address < end {
return true;
}
}
}
let Ok(regions) = self.regions.read() else {
return false;
};
Self::region_containing(®ions, address).is_some()
}
#[must_use]
pub fn get_region(&self, address: u64) -> Option<MemoryRegion> {
let regions = self.regions.read().ok()?;
Self::region_containing(®ions, address).cloned()
}
#[must_use]
pub fn get_protection(&self, address: u64) -> Option<MemoryProtection> {
let page_addr = address & !(Self::PAGE_SIZE - 1);
if let Ok(overrides) = self.protection_overrides.read() {
if let Some(&prot) = overrides.get(&page_addr) {
return Some(prot);
}
}
let regions = self.regions.read().ok()?;
Self::region_containing(®ions, address).and_then(|r| r.protection_at(address).ok())
}
pub fn set_protection(
&self,
address: u64,
size: usize,
new_protection: MemoryProtection,
) -> Option<MemoryProtection> {
let start_page = address & !(Self::PAGE_SIZE - 1);
let old_protection = if let Ok(overrides) = self.protection_overrides.read() {
if overrides.contains_key(&start_page) {
drop(overrides);
self.get_protection(address)?
} else {
drop(overrides);
let region_prot = self.get_protection(address)?;
if region_prot.contains(MemoryProtection::EXECUTE) {
MemoryProtection::READ_EXECUTE
} else {
region_prot
}
}
} else {
self.get_protection(address)?
};
let end_addr = address.saturating_add(size as u64);
let end_page = end_addr.saturating_add(Self::PAGE_SIZE - 1) & !(Self::PAGE_SIZE - 1);
if let Ok(mut overrides) = self.protection_overrides.write() {
let mut page = start_page;
while page < end_page {
overrides.insert(page, new_protection);
page = page.saturating_add(Self::PAGE_SIZE);
}
}
Some(old_protection)
}
pub fn get_static(&self, field_token: Token) -> Result<Option<EmValue>> {
self.statics.get(field_token)
}
pub fn set_static(&self, field_token: Token, value: EmValue) -> Result<()> {
self.statics.set(field_token, value)
}
pub fn alloc_unmanaged(&self, size: usize) -> Result<u64> {
let max = self.max_unmanaged_bytes.load(Ordering::Relaxed);
let current = self.unmanaged_bytes.load(Ordering::Relaxed);
if current.saturating_add(size) > max {
return Err(EmulationError::HeapMemoryLimitExceeded {
current,
limit: max,
}
.into());
}
let region = MemoryRegion::unmanaged_alloc(0, size);
let address = self.map(region)?;
self.unmanaged_bytes.fetch_add(size, Ordering::Relaxed);
Ok(address)
}
pub fn alloc_unmanaged_at(&self, address: u64, size: usize, label: &str) -> Result<()> {
let max = self.max_unmanaged_bytes.load(Ordering::Relaxed);
let current = self.unmanaged_bytes.load(Ordering::Relaxed);
if current.saturating_add(size) > max {
return Err(EmulationError::HeapMemoryLimitExceeded {
current,
limit: max,
}
.into());
}
let region = MemoryRegion::mapped_data(
address,
&vec![0u8; size],
label,
MemoryProtection::READ_WRITE,
);
self.map_at(address, region)?;
self.unmanaged_bytes.fetch_add(size, Ordering::Relaxed);
Ok(())
}
pub fn set_max_unmanaged_bytes(&self, max: usize) {
self.max_unmanaged_bytes.store(max, Ordering::Relaxed);
}
#[must_use]
pub fn unmanaged_bytes(&self) -> usize {
self.unmanaged_bytes.load(Ordering::Relaxed)
}
#[must_use]
pub fn max_unmanaged_bytes(&self) -> usize {
self.max_unmanaged_bytes.load(Ordering::Relaxed)
}
pub fn free_unmanaged(&self, address: u64) -> Result<()> {
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
let freed_size = regions
.get(&address)
.filter(|r| r.is_unmanaged_alloc())
.map(MemoryRegion::size);
drop(regions);
if let Some(size) = freed_size {
self.unmap(address)?;
let _ =
self.unmanaged_bytes
.try_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
Some(current.saturating_sub(size))
});
Ok(())
} else {
Err(EmulationError::InvalidAddress {
address,
reason: "not an unmanaged allocation".to_string(),
}
.into())
}
}
pub fn reserve_address_range(&self, size: usize) -> Option<u64> {
let aligned_size = u64::try_from(size.saturating_add(0xFFF) & !0xFFF).ok()?;
let regions = self.regions.read().ok()?;
let region_end = |region: &MemoryRegion| {
region
.base()
.saturating_add(u64::try_from(region.size()).unwrap_or(u64::MAX))
};
loop {
let base = self.next_address.load(Ordering::SeqCst);
let end = base.checked_add(aligned_size)?;
if end > self.size {
return None;
}
let blocker = regions
.range(..base)
.next_back()
.map(|(_, region)| region)
.filter(|region| region_end(region) > base)
.or_else(|| regions.range(base..end).next().map(|(_, region)| region));
let next = match blocker {
Some(region) => (region_end(region).checked_add(0xFFF)?) & !0xFFF,
None => {
if self
.next_address
.compare_exchange(base, end, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return Some(base);
}
continue;
}
};
let _ = self
.next_address
.try_update(Ordering::SeqCst, Ordering::SeqCst, |current| {
if current >= next {
None
} else {
Some(next)
}
});
}
}
pub fn register_pinned_array(
&self,
base_addr: u64,
array_ref: HeapRef,
element_size: usize,
element_count: usize,
) -> Result<()> {
let byte_length = element_size.checked_mul(element_count).ok_or_else(|| {
EmulationError::InternalError {
description: "pinned array byte length overflow".to_string(),
}
})?;
if byte_length > 0 && self.region_overlaps_range(base_addr, byte_length)? {
return Err(EmulationError::InvalidAddress {
address: base_addr,
reason: "pinned array would shadow a mapped region".to_string(),
}
.into());
}
let entry = PinnedArrayEntry {
array_ref,
base_addr,
byte_length,
element_size,
};
let mut pins = self.pinned_arrays.write().map_err(|_| {
Error::from(EmulationError::LockPoisoned {
description: "pinned arrays",
})
})?;
pins.insert(base_addr, entry);
Ok(())
}
#[must_use]
pub fn pinned_base_of(&self, array_ref: HeapRef) -> Option<u64> {
let pins = self.pinned_arrays.read().ok()?;
let base = pins
.values()
.find(|entry| entry.array_ref == array_ref)
.map(|entry| entry.base_addr);
base
}
fn read_pinned(&self, addr: u64, len: usize) -> Option<Result<Vec<u8>>> {
if len == 0 {
return None;
}
let pins = self.pinned_arrays.read().ok()?;
if pins.is_empty() {
return None;
}
for entry in pins.values() {
let end = entry.base_addr.saturating_add(entry.byte_length as u64);
let read_end = addr.saturating_add(len as u64);
if addr >= entry.base_addr && read_end <= end {
return Some(self.read_pinned_bytes(entry, addr, len));
}
}
None
}
fn read_pinned_bytes(
&self,
entry: &PinnedArrayEntry,
addr: u64,
len: usize,
) -> Result<Vec<u8>> {
let byte_offset =
addr.checked_sub(entry.base_addr)
.ok_or_else(|| EmulationError::InvalidAddress {
address: addr,
reason: "pinned array address underflow".to_string(),
})? as usize;
let heap = self.managed_heap();
let mut result = vec![0u8; len];
if entry.element_size == 1 {
for (i, slot) in result.iter_mut().enumerate().take(len) {
let elem_idx = byte_offset.saturating_add(i);
match heap.get_array_element(entry.array_ref, elem_idx) {
Ok(EmValue::I32(v)) => {
#[allow(clippy::cast_sign_loss)]
{
*slot = (v & 0xFF) as u8;
}
}
Ok(_) | Err(_) => *slot = 0,
}
}
} else {
let start_elem = byte_offset.checked_div(entry.element_size).unwrap_or(0);
let read_end_offset = byte_offset.saturating_add(len);
let end_elem = read_end_offset.div_ceil(entry.element_size);
let mut elem_buf = vec![0u8; entry.element_size];
for elem_idx in start_elem..end_elem {
Self::emvalue_to_bytes(
&heap
.get_array_element(entry.array_ref, elem_idx)
.unwrap_or(EmValue::I32(0)),
&mut elem_buf,
);
let elem_byte_start = elem_idx.saturating_mul(entry.element_size);
for (j, &b) in elem_buf.iter().enumerate() {
let abs_byte = elem_byte_start.saturating_add(j);
if abs_byte >= byte_offset && abs_byte < read_end_offset {
if let Some(slot) = result.get_mut(abs_byte.saturating_sub(byte_offset)) {
*slot = b;
}
}
}
}
}
Ok(result)
}
fn write_pinned(&self, addr: u64, data: &[u8]) -> Option<Result<()>> {
if data.is_empty() {
return None;
}
let pins = self.pinned_arrays.read().ok()?;
if pins.is_empty() {
return None;
}
for entry in pins.values() {
let end = entry.base_addr.saturating_add(entry.byte_length as u64);
let write_end = addr.saturating_add(data.len() as u64);
if addr >= entry.base_addr && write_end <= end {
return Some(self.write_pinned_bytes(entry, addr, data));
}
}
None
}
fn write_pinned_bytes(&self, entry: &PinnedArrayEntry, addr: u64, data: &[u8]) -> Result<()> {
let byte_offset =
addr.checked_sub(entry.base_addr)
.ok_or_else(|| EmulationError::InvalidAddress {
address: addr,
reason: "pinned array address underflow".to_string(),
})? as usize;
let heap = self.managed_heap();
if entry.element_size == 1 {
for (i, &byte) in data.iter().enumerate() {
let elem_idx = byte_offset.saturating_add(i);
heap.set_array_element(entry.array_ref, elem_idx, EmValue::I32(i32::from(byte)))?;
}
} else {
let start_elem = byte_offset.checked_div(entry.element_size).unwrap_or(0);
let write_end_offset = byte_offset.saturating_add(data.len());
let end_elem = write_end_offset.div_ceil(entry.element_size);
for elem_idx in start_elem..end_elem {
let elem_byte_start = elem_idx.saturating_mul(entry.element_size);
let mut elem_buf = vec![0u8; entry.element_size];
Self::emvalue_to_bytes(
&heap
.get_array_element(entry.array_ref, elem_idx)
.unwrap_or(EmValue::I32(0)),
&mut elem_buf,
);
for (j, byte) in elem_buf.iter_mut().enumerate() {
let abs_byte = elem_byte_start.saturating_add(j);
if abs_byte >= byte_offset && abs_byte < write_end_offset {
if let Some(&src) = data.get(abs_byte.saturating_sub(byte_offset)) {
*byte = src;
}
}
}
let value = Self::bytes_to_emvalue(&elem_buf);
heap.set_array_element(entry.array_ref, elem_idx, value)?;
}
}
Ok(())
}
fn emvalue_to_bytes(value: &EmValue, buf: &mut [u8]) {
fn copy_le(buf: &mut [u8], bytes: &[u8]) {
let copy_len = buf.len().min(bytes.len());
if let (Some(dst), Some(src)) = (buf.get_mut(..copy_len), bytes.get(..copy_len)) {
dst.copy_from_slice(src);
}
}
match value {
EmValue::I32(v) => copy_le(buf, &v.to_le_bytes()),
EmValue::I64(v) | EmValue::NativeInt(v) => copy_le(buf, &v.to_le_bytes()),
EmValue::F32(v) => copy_le(buf, &v.to_le_bytes()),
EmValue::F64(v) => copy_le(buf, &v.to_le_bytes()),
_ => buf.fill(0),
}
}
fn bytes_to_emvalue(bytes: &[u8]) -> EmValue {
match bytes.len() {
1 => match bytes.first() {
Some(&b) => EmValue::I32(i32::from(b)),
None => EmValue::I32(0),
},
2 => match <[u8; 2]>::try_from(bytes) {
Ok(arr) => EmValue::I32(i32::from(i16::from_le_bytes(arr))),
Err(_) => EmValue::I32(0),
},
4 => match <[u8; 4]>::try_from(bytes) {
Ok(arr) => EmValue::I32(i32::from_le_bytes(arr)),
Err(_) => EmValue::I32(0),
},
8 => match <[u8; 8]>::try_from(bytes) {
Ok(arr) => EmValue::I64(i64::from_le_bytes(arr)),
Err(_) => EmValue::I32(0),
},
_ => EmValue::I32(0),
}
}
pub fn copy_block(&self, dest: u64, src: u64, size: usize) -> Result<()> {
if size == 0 {
return Ok(());
}
let src_data = self.read(src, size)?;
self.write(dest, &src_data)
}
pub fn init_block(&self, address: u64, value: u8, size: usize) -> Result<()> {
if size == 0 {
return Ok(());
}
if !self.covers_range(address, size)? {
return Err(EmulationError::InvalidAddress {
address,
reason: format!("initblk destination range of {size} bytes is not mapped"),
}
.into());
}
const CHUNK: usize = 64 * 1024;
let chunk_len = size.min(CHUNK);
let chunk = vec![value; chunk_len];
let mut written: usize = 0;
while written < size {
let remaining = size.saturating_sub(written);
let n = remaining.min(chunk_len);
let slice = chunk
.get(..n)
.ok_or_else(|| EmulationError::InternalError {
description: "initblk chunk slice out of range".to_string(),
})?;
let offset = u64::try_from(written).map_err(|_| EmulationError::InvalidAddress {
address,
reason: "initblk offset exceeds address width".to_string(),
})?;
let target =
address
.checked_add(offset)
.ok_or_else(|| EmulationError::InvalidAddress {
address,
reason: "initblk range overflows the address space".to_string(),
})?;
self.write(target, slice)?;
written = written.saturating_add(n);
}
Ok(())
}
fn region_overlaps_range(&self, address: u64, size: usize) -> Result<bool> {
let len = u64::try_from(size).map_err(|_| EmulationError::InvalidAddress {
address,
reason: "range length exceeds the address width".to_string(),
})?;
let Some(end) = address.checked_add(len) else {
return Ok(false);
};
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
let predecessor_overlaps =
regions
.range(..address)
.next_back()
.is_some_and(|(_, region)| {
region
.base()
.saturating_add(u64::try_from(region.size()).unwrap_or(u64::MAX))
> address
});
Ok(predecessor_overlaps || regions.range(address..end).next().is_some())
}
fn covers_range(&self, address: u64, size: usize) -> Result<bool> {
let len = u64::try_from(size).map_err(|_| EmulationError::InvalidAddress {
address,
reason: "range length exceeds the address width".to_string(),
})?;
let Some(end) = address.checked_add(len) else {
return Ok(false);
};
if let Ok(pins) = self.pinned_arrays.read() {
for entry in pins.values() {
let pin_end = entry.base_addr.saturating_add(entry.byte_length as u64);
if address >= entry.base_addr && end <= pin_end {
return Ok(true);
}
}
}
let regions = self.regions.read().map_err(|_| {
Error::from(EmulationError::InternalError {
description: "region lock poisoned".to_string(),
})
})?;
Ok(Self::region_containing(®ions, address)
.is_some_and(|r| r.contains_range(address, size)))
}
pub fn map_pe_image(
&self,
data: &[u8],
preferred_base: u64,
sections: Vec<SectionInfo>,
name: impl Into<String>,
) -> Result<u64> {
let region = MemoryRegion::pe_image(preferred_base, data, sections, name);
self.map_at(preferred_base, region)?;
Ok(preferred_base)
}
pub fn map_data(&self, address: u64, data: &[u8], label: impl Into<String>) -> Result<()> {
let region = MemoryRegion::mapped_data(address, data, label, MemoryProtection::READ_WRITE);
self.map_at(address, region)
}
#[must_use]
pub fn regions(&self) -> Vec<(u64, usize, String)> {
match self.regions.read() {
Ok(regions) => regions
.values()
.map(|r| (r.base(), r.size(), r.label().to_string()))
.collect(),
Err(_) => Vec::new(),
}
}
#[must_use]
pub fn mapped_size(&self) -> usize {
match self.regions.read() {
Ok(regions) => regions.values().map(MemoryRegion::size).sum(),
Err(_) => 0,
}
}
fn region_containing(
regions: &BTreeMap<u64, MemoryRegion>,
address: u64,
) -> Option<&MemoryRegion> {
regions
.range(..=address)
.next_back()
.map(|(_, region)| region)
.filter(|region| region.contains(address))
}
fn regions_overlap(a: &MemoryRegion, b: &MemoryRegion) -> bool {
let a_start = a.base();
let a_end = a.end();
let b_start = b.base();
let b_end = b.end();
a_start < b_end && b_start < a_end
}
pub fn alloc_string(&self, value: &str) -> Result<HeapRef> {
self.heap.alloc_string(value)
}
pub fn get_string(&self, heap_ref: HeapRef) -> Result<std::sync::Arc<str>> {
self.heap.get_string(heap_ref)
}
pub fn alloc_object(&self, type_token: Token) -> Result<HeapRef> {
self.heap.alloc_object(type_token)
}
pub fn get_field(&self, heap_ref: HeapRef, field_token: Token) -> Result<EmValue> {
self.heap.get_field(heap_ref, field_token)
}
pub fn set_field(&self, heap_ref: HeapRef, field_token: Token, value: EmValue) -> Result<()> {
self.heap.set_field(heap_ref, field_token, value)
}
}
impl Default for AddressSpace {
fn default() -> Self {
Self::new()
}
}
impl AddressSpace {
#[must_use]
pub fn spawn_fresh(&self) -> Self {
let regions = match self.regions.read() {
Ok(r) => r.clone(),
Err(_) => BTreeMap::new(),
};
Self {
heap: SharedHeap::default(),
regions: RwLock::new(regions),
statics: StaticFieldStorage::new(),
next_address: AtomicU64::new(self.next_address.load(Ordering::SeqCst)),
size: self.size,
protection_overrides: RwLock::new(ImHashMap::new()),
monitor_locks: RwLock::new(ImHashMap::new()),
pinned_arrays: RwLock::new(ImHashMap::new()),
unmanaged_bytes: AtomicUsize::new(0),
max_unmanaged_bytes: AtomicUsize::new(DEFAULT_MAX_UNMANAGED_BYTES),
}
}
pub fn fork(&self) -> Result<Self> {
let regions = self
.regions
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "address space regions",
})?
.iter()
.map(|(&base, region)| region.fork().map(|forked| (base, forked)))
.collect::<StdResult<BTreeMap<_, _>, _>>()?;
let protection_overrides = self
.protection_overrides
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "address space protection overrides",
})?
.clone();
let monitor_locks = self
.monitor_locks
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "address space monitor locks",
})?
.clone();
let pinned_arrays = self
.pinned_arrays
.read()
.map_err(|_| EmulationError::LockPoisoned {
description: "address space pinned arrays",
})?
.clone();
Ok(Self {
heap: self.heap.fork()?,
regions: RwLock::new(regions),
statics: self.statics.fork()?,
next_address: AtomicU64::new(self.next_address.load(Ordering::SeqCst)),
size: self.size,
protection_overrides: RwLock::new(protection_overrides),
monitor_locks: RwLock::new(monitor_locks),
pinned_arrays: RwLock::new(pinned_arrays),
unmanaged_bytes: AtomicUsize::new(self.unmanaged_bytes.load(Ordering::Relaxed)),
max_unmanaged_bytes: AtomicUsize::new(self.max_unmanaged_bytes.load(Ordering::Relaxed)),
})
}
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
memory::{
addressspace::{AddressSpace, SharedHeap},
region::MemoryProtection,
},
EmValue,
},
metadata::{token::Token, typesystem::CilFlavor},
};
#[test]
fn test_address_space_creation() {
let space = AddressSpace::new();
assert!(space.regions().is_empty());
}
#[test]
fn test_map_and_read_data() {
let space = AddressSpace::new();
let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
space.map_data(0x1000, &data, "test").unwrap();
let read = space.read(0x1000, 4).unwrap();
assert_eq!(read, data);
}
#[test]
fn test_write_data() {
let space = AddressSpace::new();
space.map_data(0x1000, &[0u8; 16], "test").unwrap();
space.write(0x1000, &[0xCA, 0xFE]).unwrap();
let read = space.read(0x1000, 2).unwrap();
assert_eq!(read, vec![0xCA, 0xFE]);
}
#[test]
fn write_to_read_only_memory_faults() {
let space = AddressSpace::new();
space.map_data(0x1000, &[0u8; 16], "test").unwrap();
space
.set_protection(0x1000, 16, MemoryProtection::READ)
.unwrap();
assert!(space.read(0x1000, 4).is_ok(), "reads must still work");
assert!(space.write(0x1000, &[0xFF]).is_err());
}
#[test]
fn guard_page_faults_on_read_and_write() {
let space = AddressSpace::new();
space.map_data(0x2000, &[0u8; 16], "test").unwrap();
space
.set_protection(
0x2000,
16,
MemoryProtection::READ_WRITE | MemoryProtection::GUARD,
)
.unwrap();
assert!(space.read(0x2000, 1).is_err());
assert!(space.write(0x2000, &[0x01]).is_err());
}
#[test]
fn fixed_size_reads_match_the_allocating_read() {
let space = AddressSpace::new();
space
.map_data(
0x3000,
&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
"test",
)
.unwrap();
assert_eq!(space.read_u8(0x3000).unwrap(), 0x11);
assert_eq!(space.read_u16(0x3000).unwrap(), 0x2211);
assert_eq!(space.read_u32(0x3000).unwrap(), 0x4433_2211);
assert_eq!(space.read_u64(0x3000).unwrap(), 0x8877_6655_4433_2211);
assert_eq!(
space.read_exact::<4>(0x3000).unwrap().to_vec(),
space.read(0x3000, 4).unwrap()
);
assert!(space.read_u32(0x9000).is_err(), "unmapped must still fault");
}
#[test]
fn reserved_ranges_skip_mapped_regions() {
let space = AddressSpace::new();
let cursor = space.reserve_address_range(0x1000).unwrap();
let blocked = cursor + 0x1000;
space.map_data(blocked, &[0u8; 0x2000], "image").unwrap();
let next = space.reserve_address_range(0x1000).unwrap();
assert!(
next >= blocked + 0x2000,
"reservation {next:#x} overlaps the region at {blocked:#x}"
);
}
#[test]
fn pinned_array_cannot_shadow_a_mapped_region() {
let space = AddressSpace::new();
space.map_data(0x4000, &[0u8; 0x1000], "image").unwrap();
let array = space.managed_heap().alloc_array(CilFlavor::I4, 4).unwrap();
assert!(space.register_pinned_array(0x4000, array, 4, 4).is_err());
}
#[test]
fn test_static_fields() {
let space = AddressSpace::new();
let field = Token::new(0x04000001);
assert!(space.get_static(field).unwrap().is_none());
space.set_static(field, EmValue::I32(42)).unwrap();
assert_eq!(space.get_static(field).unwrap(), Some(EmValue::I32(42)));
}
#[test]
fn test_shared_heap() {
let space1 = AddressSpace::new();
let str_ref = space1.alloc_string("Hello").unwrap();
let space2 = AddressSpace::with_heap(space1.heap().clone());
let s1 = space1.get_string(str_ref).unwrap();
let s2 = space2.get_string(str_ref).unwrap();
assert_eq!(&*s1, "Hello");
assert_eq!(&*s2, "Hello");
let str_ref2 = space2.alloc_string("World").unwrap();
let s3 = space1.get_string(str_ref2).unwrap();
assert_eq!(&*s3, "World");
}
#[test]
fn test_unmanaged_alloc() {
let space = AddressSpace::new();
let addr = space.alloc_unmanaged(256).unwrap();
assert!(space.is_valid(addr));
space.write(addr, &[1, 2, 3, 4]).unwrap();
let data = space.read(addr, 4).unwrap();
assert_eq!(data, vec![1, 2, 3, 4]);
space.free_unmanaged(addr).unwrap();
assert!(!space.is_valid(addr));
}
#[test]
fn test_heap_delegation() {
let space = AddressSpace::new();
let str_ref = space.alloc_string("Test").unwrap();
let s = space.get_string(str_ref).unwrap();
assert_eq!(&*s, "Test");
let type_token = Token::new(0x02000001);
let field_token = Token::new(0x04000001);
let obj_ref = space.alloc_object(type_token).unwrap();
space
.set_field(obj_ref, field_token, EmValue::I32(100))
.unwrap();
let value = space.get_field(obj_ref, field_token).unwrap();
assert_eq!(value, EmValue::I32(100));
}
#[test]
fn test_fork_memory_isolation() {
let space = AddressSpace::new();
space.map_data(0x1000, &[1, 2, 3, 4], "test").unwrap();
let forked = space.fork().unwrap();
assert_eq!(space.read(0x1000, 4).unwrap(), vec![1, 2, 3, 4]);
assert_eq!(forked.read(0x1000, 4).unwrap(), vec![1, 2, 3, 4]);
forked.write(0x1000, &[0xFF, 0xFE]).unwrap();
assert_eq!(space.read(0x1000, 4).unwrap(), vec![1, 2, 3, 4]);
assert_eq!(forked.read(0x1000, 4).unwrap(), vec![0xFF, 0xFE, 3, 4]);
}
#[test]
fn test_fork_heap_isolation() {
let space = AddressSpace::new();
let str_ref = space.alloc_string("Original").unwrap();
let forked = space.fork().unwrap();
assert_eq!(&*space.get_string(str_ref).unwrap(), "Original");
assert_eq!(&*forked.get_string(str_ref).unwrap(), "Original");
let new_ref = forked.alloc_string("Forked").unwrap();
assert_eq!(&*forked.get_string(new_ref).unwrap(), "Forked");
assert!(space.get_string(new_ref).is_err());
}
#[test]
fn test_fork_statics_isolation() {
let space = AddressSpace::new();
let field = Token::new(0x04000001);
space.set_static(field, EmValue::I32(42)).unwrap();
let forked = space.fork().unwrap();
assert_eq!(space.get_static(field).unwrap(), Some(EmValue::I32(42)));
assert_eq!(forked.get_static(field).unwrap(), Some(EmValue::I32(42)));
forked.set_static(field, EmValue::I32(100)).unwrap();
assert_eq!(space.get_static(field).unwrap(), Some(EmValue::I32(42)));
assert_eq!(forked.get_static(field).unwrap(), Some(EmValue::I32(100)));
}
#[test]
fn test_fork_protection_isolation() {
let space = AddressSpace::new();
space.map_data(0x1000, &vec![0u8; 0x2000], "test").unwrap();
space.set_protection(0x1000, 0x1000, MemoryProtection::READ_EXECUTE);
let forked = space.fork().unwrap();
assert_eq!(
space.get_protection(0x1000),
Some(MemoryProtection::READ_EXECUTE)
);
assert_eq!(
forked.get_protection(0x1000),
Some(MemoryProtection::READ_EXECUTE)
);
forked.set_protection(0x1000, 0x1000, MemoryProtection::READ_WRITE);
assert_eq!(
space.get_protection(0x1000),
Some(MemoryProtection::READ_EXECUTE)
);
assert_eq!(
forked.get_protection(0x1000),
Some(MemoryProtection::READ_WRITE)
);
}
#[test]
fn test_multiple_forks_isolation() {
let space = AddressSpace::new();
let field = Token::new(0x04000001);
space.set_static(field, EmValue::I32(1)).unwrap();
let fork1 = space.fork().unwrap();
let fork2 = space.fork().unwrap();
fork1.set_static(field, EmValue::I32(10)).unwrap();
fork2.set_static(field, EmValue::I32(20)).unwrap();
assert_eq!(space.get_static(field).unwrap(), Some(EmValue::I32(1)));
assert_eq!(fork1.get_static(field).unwrap(), Some(EmValue::I32(10)));
assert_eq!(fork2.get_static(field).unwrap(), Some(EmValue::I32(20)));
}
#[test]
fn test_shared_heap_fork() {
let heap = SharedHeap::new(1024 * 1024);
let str_ref = heap.alloc_string("Hello").unwrap();
let forked = heap.fork().unwrap();
assert_eq!(&*heap.get_string(str_ref).unwrap(), "Hello");
assert_eq!(&*forked.get_string(str_ref).unwrap(), "Hello");
let new_ref = forked.alloc_string("World").unwrap();
assert_eq!(&*forked.get_string(new_ref).unwrap(), "World");
assert!(heap.get_string(new_ref).is_err());
}
#[test]
fn unmapped_read_is_refused_without_allocating() {
let space = AddressSpace::new();
space.map_data(0x1000, &[0u8; 16], "test").unwrap();
assert!(space.read(0xDEAD_0000, 1 << 40).is_err());
assert!(space.read(0x1000, 1 << 40).is_err());
assert_eq!(space.read(0x1000, 4).unwrap(), vec![0u8; 4]);
}
#[test]
fn copy_block_with_an_unmapped_source_is_refused_without_allocating() {
let space = AddressSpace::new();
space.map_data(0x1000, &[0u8; 16], "test").unwrap();
assert!(space.copy_block(0x1000, 0xDEAD_0000, 1 << 40).is_err());
}
}