Initial commit: xenia-rs workspace for Xbox 360 RE
Rust reimplementation of the xenia Xbox 360 emulator targeting reverse- engineering and preservation, initially scoped to Project Sylpheed. Includes: - XEX2 loader (LZX decompression, AES decryption, PE parsing) - XISO / XGD2 disc image VFS - PPC interpreter with 200+ opcodes and VMX128 decoding - Static analyzer: functions, cross-references, labels, asm + SQLite output - HLE kernel covering the xboxkrnl/xam subset used by Sylpheed init - Debugger with in-memory and SQLite-backed execution tracing - `xenia-rs` CLI with extract/dis/exec commands that produce cumulative, superset SQLite databases and opt-in instruction/import/branch traces Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
47
crates/xenia-memory/src/access.rs
Normal file
47
crates/xenia-memory/src/access.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
/// Trait for all guest memory access. Every load/store goes through this,
|
||||
/// enabling MMIO checking and debugger observation on every access.
|
||||
/// This is the key abstraction that eliminates the need for MMIO exception handlers.
|
||||
pub trait MemoryAccess {
|
||||
fn read_u8(&self, addr: u32) -> u8;
|
||||
fn read_u16(&self, addr: u32) -> u16;
|
||||
fn read_u32(&self, addr: u32) -> u32;
|
||||
fn read_u64(&self, addr: u32) -> u64;
|
||||
fn read_f32(&self, addr: u32) -> f32 {
|
||||
f32::from_bits(self.read_u32(addr))
|
||||
}
|
||||
fn read_f64(&self, addr: u32) -> f64 {
|
||||
f64::from_bits(self.read_u64(addr))
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, addr: u32, val: u8);
|
||||
fn write_u16(&mut self, addr: u32, val: u16);
|
||||
fn write_u32(&mut self, addr: u32, val: u32);
|
||||
fn write_u64(&mut self, addr: u32, val: u64);
|
||||
fn write_f32(&mut self, addr: u32, val: f32) {
|
||||
self.write_u32(addr, val.to_bits());
|
||||
}
|
||||
fn write_f64(&mut self, addr: u32, val: f64) {
|
||||
self.write_u64(addr, val.to_bits());
|
||||
}
|
||||
|
||||
/// Read a block of bytes from guest memory.
|
||||
fn read_bytes(&self, addr: u32, buf: &mut [u8]) {
|
||||
for (i, byte) in buf.iter_mut().enumerate() {
|
||||
*byte = self.read_u8(addr.wrapping_add(i as u32));
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a block of bytes to guest memory.
|
||||
fn write_bytes(&mut self, addr: u32, buf: &[u8]) {
|
||||
for (i, &byte) in buf.iter().enumerate() {
|
||||
self.write_u8(addr.wrapping_add(i as u32), byte);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a direct host pointer for the given guest address.
|
||||
/// Returns None if the address is invalid or in an MMIO region.
|
||||
fn translate(&self, addr: u32) -> Option<*const u8>;
|
||||
|
||||
/// Get a mutable direct host pointer for the given guest address.
|
||||
fn translate_mut(&mut self, addr: u32) -> Option<*mut u8>;
|
||||
}
|
||||
265
crates/xenia-memory/src/heap.rs
Normal file
265
crates/xenia-memory/src/heap.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
use crate::access::MemoryAccess;
|
||||
use crate::mmio::MmioRegion;
|
||||
use crate::page_table::{AllocationState, MemoryProtect, PageEntry};
|
||||
use crate::MemoryError;
|
||||
|
||||
const PAGE_SIZE: u32 = 4096;
|
||||
/// Total guest address space: 4GB.
|
||||
const GUEST_ADDRESS_SPACE: usize = 0x1_0000_0000;
|
||||
/// Number of 4K pages in the 4GB address space.
|
||||
const PAGE_COUNT: usize = GUEST_ADDRESS_SPACE / PAGE_SIZE as usize;
|
||||
/// Physical memory mask (512MB physical address space).
|
||||
const PHYSICAL_ADDR_MASK: u32 = 0x1FFF_FFFF;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HeapType {
|
||||
GuestVirtual,
|
||||
GuestXex,
|
||||
GuestPhysical,
|
||||
}
|
||||
|
||||
/// The core guest memory system. Manages a 4GB virtual address space
|
||||
/// via mmap/VirtualAlloc, with page-level tracking and MMIO dispatch.
|
||||
pub struct GuestMemory {
|
||||
/// Host pointer to the base of the 4GB guest address space.
|
||||
membase: *mut u8,
|
||||
/// Page table tracking allocation state for each 4K page.
|
||||
page_table: Vec<PageEntry>,
|
||||
/// Registered MMIO regions (sorted by base address for binary search).
|
||||
mmio_regions: Vec<MmioRegion>,
|
||||
/// Whether the memory mapping is owned (should be unmapped on drop).
|
||||
owned: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for GuestMemory {}
|
||||
unsafe impl Sync for GuestMemory {}
|
||||
|
||||
impl GuestMemory {
|
||||
/// Create a new guest memory space by reserving a 4GB virtual address region.
|
||||
pub fn new() -> Result<Self, MemoryError> {
|
||||
let membase = crate::platform::reserve_address_space(GUEST_ADDRESS_SPACE)?;
|
||||
Ok(Self {
|
||||
membase,
|
||||
page_table: vec![PageEntry::default(); PAGE_COUNT],
|
||||
mmio_regions: Vec::new(),
|
||||
owned: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the host base pointer for the guest address space.
|
||||
pub fn membase(&self) -> *const u8 {
|
||||
self.membase
|
||||
}
|
||||
|
||||
/// Get a mutable host base pointer.
|
||||
pub fn membase_mut(&mut self) -> *mut u8 {
|
||||
self.membase
|
||||
}
|
||||
|
||||
/// Translate a guest virtual address to a host pointer.
|
||||
pub fn translate_virtual(&self, guest_addr: u32) -> *const u8 {
|
||||
unsafe { self.membase.add(guest_addr as usize) }
|
||||
}
|
||||
|
||||
/// Translate a guest virtual address to a mutable host pointer.
|
||||
pub fn translate_virtual_mut(&mut self, guest_addr: u32) -> *mut u8 {
|
||||
unsafe { self.membase.add(guest_addr as usize) }
|
||||
}
|
||||
|
||||
/// Translate a guest physical address to a host pointer.
|
||||
pub fn translate_physical(&self, guest_addr: u32) -> *const u8 {
|
||||
let phys = guest_addr & PHYSICAL_ADDR_MASK;
|
||||
unsafe { self.membase.add(phys as usize) }
|
||||
}
|
||||
|
||||
/// Register an MMIO region.
|
||||
pub fn add_mmio_region(&mut self, region: MmioRegion) {
|
||||
let base = region.base_address;
|
||||
let idx = self
|
||||
.mmio_regions
|
||||
.binary_search_by_key(&base, |r| r.base_address)
|
||||
.unwrap_or_else(|i| i);
|
||||
self.mmio_regions.insert(idx, region);
|
||||
}
|
||||
|
||||
/// Check if an address is in a registered MMIO region.
|
||||
fn find_mmio(&self, addr: u32) -> Option<&MmioRegion> {
|
||||
self.mmio_regions.iter().find(|r| r.contains(addr))
|
||||
}
|
||||
|
||||
/// Allocate a region in the guest address space.
|
||||
pub fn alloc(
|
||||
&mut self,
|
||||
base: u32,
|
||||
size: u32,
|
||||
protect: MemoryProtect,
|
||||
) -> Result<u32, MemoryError> {
|
||||
let page_start = (base / PAGE_SIZE) as usize;
|
||||
let page_count = ((size + PAGE_SIZE - 1) / PAGE_SIZE) as usize;
|
||||
|
||||
// Commit pages via platform
|
||||
let host_ptr = unsafe { self.membase.add(base as usize) };
|
||||
crate::platform::commit_memory(host_ptr, (page_count * PAGE_SIZE as usize) as usize)?;
|
||||
|
||||
// Update page table
|
||||
for i in 0..page_count {
|
||||
let idx = page_start + i;
|
||||
if idx < self.page_table.len() {
|
||||
let entry = &mut self.page_table[idx];
|
||||
entry.set_base_address(page_start as u32);
|
||||
entry.set_region_page_count(page_count as u32);
|
||||
entry.set_allocation_protect(protect);
|
||||
entry.set_current_protect(protect);
|
||||
entry.set_state(AllocationState::RESERVE | AllocationState::COMMIT);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
/// Read a slice of bytes from guest memory (bypassing MMIO for bulk reads).
|
||||
pub fn read_bulk(&self, addr: u32, buf: &mut [u8]) {
|
||||
let ptr = self.translate_virtual(addr);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a slice of bytes to guest memory (bypassing MMIO for bulk writes).
|
||||
pub fn write_bulk(&mut self, addr: u32, buf: &[u8]) {
|
||||
let ptr = self.translate_virtual_mut(addr);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(buf.as_ptr(), ptr, buf.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a guest address has been allocated/committed.
|
||||
pub fn is_mapped(&self, addr: u32) -> bool {
|
||||
let page = (addr / PAGE_SIZE) as usize;
|
||||
if page >= self.page_table.len() {
|
||||
return false;
|
||||
}
|
||||
self.page_table[page].state().contains(AllocationState::COMMIT)
|
||||
}
|
||||
|
||||
/// Get a page table entry for a given address.
|
||||
pub fn page_entry(&self, addr: u32) -> &PageEntry {
|
||||
let page = (addr / PAGE_SIZE) as usize;
|
||||
&self.page_table[page]
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryAccess for GuestMemory {
|
||||
fn read_u8(&self, addr: u32) -> u8 {
|
||||
if !self.is_mapped(addr) { return 0; }
|
||||
let ptr = self.translate_virtual(addr);
|
||||
unsafe { *ptr }
|
||||
}
|
||||
|
||||
fn read_u16(&self, addr: u32) -> u16 {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
(mmio.read_callback)(addr) as u16
|
||||
} else if !self.is_mapped(addr) {
|
||||
0
|
||||
} else {
|
||||
let ptr = self.translate_virtual(addr) as *const [u8; 2];
|
||||
u16::from_be_bytes(unsafe { *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32(&self, addr: u32) -> u32 {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
(mmio.read_callback)(addr)
|
||||
} else if !self.is_mapped(addr) {
|
||||
0
|
||||
} else {
|
||||
let ptr = self.translate_virtual(addr) as *const [u8; 4];
|
||||
u32::from_be_bytes(unsafe { *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u64(&self, addr: u32) -> u64 {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
let hi = (mmio.read_callback)(addr) as u64;
|
||||
let lo = (mmio.read_callback)(addr.wrapping_add(4)) as u64;
|
||||
(hi << 32) | lo
|
||||
} else if !self.is_mapped(addr) {
|
||||
0
|
||||
} else {
|
||||
let ptr = self.translate_virtual(addr) as *const [u8; 8];
|
||||
u64::from_be_bytes(unsafe { *ptr })
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, addr: u32, val: u8) {
|
||||
if !self.is_mapped(addr) { return; }
|
||||
let ptr = self.translate_virtual_mut(addr);
|
||||
unsafe { *ptr = val };
|
||||
}
|
||||
|
||||
fn write_u16(&mut self, addr: u32, val: u16) {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
(mmio.write_callback)(addr, val as u32);
|
||||
} else if !self.is_mapped(addr) {
|
||||
return;
|
||||
} else {
|
||||
let ptr = self.translate_virtual_mut(addr);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(val.to_be_bytes().as_ptr(), ptr, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u32(&mut self, addr: u32, val: u32) {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
(mmio.write_callback)(addr, val);
|
||||
} else if !self.is_mapped(addr) {
|
||||
return;
|
||||
} else {
|
||||
let ptr = self.translate_virtual_mut(addr);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(val.to_be_bytes().as_ptr(), ptr, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_u64(&mut self, addr: u32, val: u64) {
|
||||
if let Some(mmio) = self.find_mmio(addr) {
|
||||
(mmio.write_callback)(addr, (val >> 32) as u32);
|
||||
(mmio.write_callback)(addr.wrapping_add(4), val as u32);
|
||||
} else if !self.is_mapped(addr) {
|
||||
return;
|
||||
} else {
|
||||
let ptr = self.translate_virtual_mut(addr);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(val.to_be_bytes().as_ptr(), ptr, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate(&self, addr: u32) -> Option<*const u8> {
|
||||
if self.find_mmio(addr).is_some() || !self.is_mapped(addr) {
|
||||
None
|
||||
} else {
|
||||
Some(self.translate_virtual(addr))
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_mut(&mut self, addr: u32) -> Option<*mut u8> {
|
||||
if self.find_mmio(addr).is_some() {
|
||||
None
|
||||
} else {
|
||||
Some(self.translate_virtual_mut(addr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GuestMemory {
|
||||
fn drop(&mut self) {
|
||||
if self.owned && !self.membase.is_null() {
|
||||
unsafe {
|
||||
crate::platform::release_address_space(self.membase, GUEST_ADDRESS_SPACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
crates/xenia-memory/src/lib.rs
Normal file
31
crates/xenia-memory/src/lib.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
pub mod access;
|
||||
pub mod heap;
|
||||
pub mod mmio;
|
||||
pub mod page_table;
|
||||
|
||||
mod platform;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub use access::MemoryAccess;
|
||||
pub use heap::{GuestMemory, HeapType};
|
||||
pub use mmio::MmioRegion;
|
||||
pub use page_table::PageEntry;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MemoryError {
|
||||
#[error("Failed to allocate guest address space: {0}")]
|
||||
AllocationFailed(String),
|
||||
|
||||
#[error("Invalid guest address: {0:#010x}")]
|
||||
InvalidAddress(u32),
|
||||
|
||||
#[error("MMIO access at {0:#010x}")]
|
||||
MmioAccess(u32),
|
||||
|
||||
#[error("Protection violation at {0:#010x}")]
|
||||
ProtectionViolation(u32),
|
||||
|
||||
#[error("Out of memory in heap {0:?}")]
|
||||
OutOfMemory(HeapType),
|
||||
}
|
||||
27
crates/xenia-memory/src/mmio.rs
Normal file
27
crates/xenia-memory/src/mmio.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Represents a mapped MMIO region with read/write callbacks.
|
||||
/// Instead of trapping access violations (as the C++ JIT does), the interpreter
|
||||
/// explicitly checks each memory access against registered MMIO regions.
|
||||
pub struct MmioRegion {
|
||||
pub base_address: u32,
|
||||
pub mask: u32,
|
||||
pub size: u32,
|
||||
pub read_callback: Box<dyn Fn(u32) -> u32 + Send + Sync>,
|
||||
pub write_callback: Box<dyn Fn(u32, u32) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl MmioRegion {
|
||||
pub fn contains(&self, addr: u32) -> bool {
|
||||
let masked = addr & self.mask;
|
||||
masked >= self.base_address && masked < self.base_address + self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for MmioRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("MmioRegion")
|
||||
.field("base_address", &format_args!("{:#010x}", self.base_address))
|
||||
.field("mask", &format_args!("{:#010x}", self.mask))
|
||||
.field("size", &format_args!("{:#x}", self.size))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
122
crates/xenia-memory/src/page_table.rs
Normal file
122
crates/xenia-memory/src/page_table.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
/// Describes a single page in the page table.
|
||||
/// Mirrors the C++ `PageEntry` union from memory.h:82-99.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct PageEntry(u64);
|
||||
|
||||
impl PageEntry {
|
||||
/// Base address of the allocated region in 4K pages (20 bits).
|
||||
pub fn base_address(&self) -> u32 {
|
||||
(self.0 & 0xFFFFF) as u32
|
||||
}
|
||||
|
||||
pub fn set_base_address(&mut self, val: u32) {
|
||||
self.0 = (self.0 & !0xFFFFF) | (val as u64 & 0xFFFFF);
|
||||
}
|
||||
|
||||
/// Total number of pages in the allocated region (20 bits).
|
||||
pub fn region_page_count(&self) -> u32 {
|
||||
((self.0 >> 20) & 0xFFFFF) as u32
|
||||
}
|
||||
|
||||
pub fn set_region_page_count(&mut self, val: u32) {
|
||||
self.0 = (self.0 & !(0xFFFFF << 20)) | ((val as u64 & 0xFFFFF) << 20);
|
||||
}
|
||||
|
||||
/// Protection bits specified during region allocation (4 bits).
|
||||
pub fn allocation_protect(&self) -> MemoryProtect {
|
||||
MemoryProtect::from_bits_truncate(((self.0 >> 40) & 0xF) as u32)
|
||||
}
|
||||
|
||||
pub fn set_allocation_protect(&mut self, val: MemoryProtect) {
|
||||
self.0 = (self.0 & !(0xF << 40)) | ((val.bits() as u64 & 0xF) << 40);
|
||||
}
|
||||
|
||||
/// Current protection bits (4 bits).
|
||||
pub fn current_protect(&self) -> MemoryProtect {
|
||||
MemoryProtect::from_bits_truncate(((self.0 >> 44) & 0xF) as u32)
|
||||
}
|
||||
|
||||
pub fn set_current_protect(&mut self, val: MemoryProtect) {
|
||||
self.0 = (self.0 & !(0xF << 44)) | ((val.bits() as u64 & 0xF) << 44);
|
||||
}
|
||||
|
||||
/// Allocation state (2 bits).
|
||||
pub fn state(&self) -> AllocationState {
|
||||
AllocationState::from_bits_truncate(((self.0 >> 48) & 0x3) as u32)
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, val: AllocationState) {
|
||||
self.0 = (self.0 & !(0x3 << 48)) | ((val.bits() as u64 & 0x3) << 48);
|
||||
}
|
||||
|
||||
pub fn is_committed(&self) -> bool {
|
||||
self.state().contains(AllocationState::COMMIT)
|
||||
}
|
||||
|
||||
pub fn is_reserved(&self) -> bool {
|
||||
self.state().contains(AllocationState::RESERVE)
|
||||
}
|
||||
|
||||
pub fn is_free(&self) -> bool {
|
||||
self.state().is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct MemoryProtect: u32 {
|
||||
const READ = 1 << 0;
|
||||
const WRITE = 1 << 1;
|
||||
const NO_CACHE = 1 << 2;
|
||||
const WRITE_COMBINE = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct AllocationState: u32 {
|
||||
const RESERVE = 1 << 0;
|
||||
const COMMIT = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PageEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PageEntry")
|
||||
.field("base_address", &format_args!("{:#x}", self.base_address()))
|
||||
.field("region_page_count", &self.region_page_count())
|
||||
.field("allocation_protect", &self.allocation_protect())
|
||||
.field("current_protect", &self.current_protect())
|
||||
.field("state", &self.state())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_page_entry_bitfields() {
|
||||
let mut entry = PageEntry::default();
|
||||
assert!(entry.is_free());
|
||||
|
||||
entry.set_base_address(0x100);
|
||||
entry.set_region_page_count(0x10);
|
||||
entry.set_allocation_protect(MemoryProtect::READ | MemoryProtect::WRITE);
|
||||
entry.set_current_protect(MemoryProtect::READ);
|
||||
entry.set_state(AllocationState::RESERVE | AllocationState::COMMIT);
|
||||
|
||||
assert_eq!(entry.base_address(), 0x100);
|
||||
assert_eq!(entry.region_page_count(), 0x10);
|
||||
assert_eq!(
|
||||
entry.allocation_protect(),
|
||||
MemoryProtect::READ | MemoryProtect::WRITE
|
||||
);
|
||||
assert_eq!(entry.current_protect(), MemoryProtect::READ);
|
||||
assert!(entry.is_committed());
|
||||
assert!(entry.is_reserved());
|
||||
}
|
||||
}
|
||||
98
crates/xenia-memory/src/platform.rs
Normal file
98
crates/xenia-memory/src/platform.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use crate::MemoryError;
|
||||
|
||||
/// Reserve a contiguous virtual address region without committing physical pages.
|
||||
#[cfg(unix)]
|
||||
pub fn reserve_address_space(size: usize) -> Result<*mut u8, MemoryError> {
|
||||
unsafe {
|
||||
let ptr = libc::mmap(
|
||||
std::ptr::null_mut(),
|
||||
size,
|
||||
libc::PROT_NONE,
|
||||
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE,
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
if ptr == libc::MAP_FAILED {
|
||||
Err(MemoryError::AllocationFailed(format!(
|
||||
"mmap failed for {} bytes: {}",
|
||||
size,
|
||||
std::io::Error::last_os_error()
|
||||
)))
|
||||
} else {
|
||||
Ok(ptr as *mut u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit (make accessible) a region within a previously reserved address space.
|
||||
#[cfg(unix)]
|
||||
pub fn commit_memory(ptr: *mut u8, size: usize) -> Result<(), MemoryError> {
|
||||
unsafe {
|
||||
let result = libc::mprotect(ptr as *mut libc::c_void, size, libc::PROT_READ | libc::PROT_WRITE);
|
||||
if result != 0 {
|
||||
Err(MemoryError::AllocationFailed(format!(
|
||||
"mprotect failed for {} bytes: {}",
|
||||
size,
|
||||
std::io::Error::last_os_error()
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Release a previously reserved address space.
|
||||
#[cfg(unix)]
|
||||
pub unsafe fn release_address_space(ptr: *mut u8, size: usize) {
|
||||
unsafe { libc::munmap(ptr as *mut libc::c_void, size); }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn reserve_address_space(size: usize) -> Result<*mut u8, MemoryError> {
|
||||
unsafe {
|
||||
let ptr = windows_sys::Win32::System::Memory::VirtualAlloc(
|
||||
std::ptr::null_mut(),
|
||||
size,
|
||||
windows_sys::Win32::System::Memory::MEM_RESERVE,
|
||||
windows_sys::Win32::System::Memory::PAGE_NOACCESS,
|
||||
);
|
||||
if ptr.is_null() {
|
||||
Err(MemoryError::AllocationFailed(format!(
|
||||
"VirtualAlloc reserve failed for {} bytes",
|
||||
size,
|
||||
)))
|
||||
} else {
|
||||
Ok(ptr as *mut u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn commit_memory(ptr: *mut u8, size: usize) -> Result<(), MemoryError> {
|
||||
unsafe {
|
||||
let result = windows_sys::Win32::System::Memory::VirtualAlloc(
|
||||
ptr as *mut _,
|
||||
size,
|
||||
windows_sys::Win32::System::Memory::MEM_COMMIT,
|
||||
windows_sys::Win32::System::Memory::PAGE_READWRITE,
|
||||
);
|
||||
if result.is_null() {
|
||||
Err(MemoryError::AllocationFailed(format!(
|
||||
"VirtualAlloc commit failed for {} bytes",
|
||||
size,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub unsafe fn release_address_space(ptr: *mut u8, size: usize) {
|
||||
let _ = size;
|
||||
windows_sys::Win32::System::Memory::VirtualFree(
|
||||
ptr as *mut _,
|
||||
0,
|
||||
windows_sys::Win32::System::Memory::MEM_RELEASE,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user