use std::sync::RwLock;
#[cfg(all(target_pointer_width = "64", target_os = "linux"))]
const MAX_USER_ADDR: usize = 0x0000_7fff_ffff_f000;
#[cfg(all(target_pointer_width = "64", target_os = "macos"))]
const MAX_USER_ADDR: usize = 0x0000_7fff_ffff_f000;
#[cfg(all(target_pointer_width = "64", target_os = "windows"))]
const MAX_USER_ADDR: usize = 0x0000_7fff_ffff_0000;
#[cfg(all(
target_pointer_width = "64",
not(any(target_os = "linux", target_os = "macos", target_os = "windows"))
))]
const MAX_USER_ADDR: usize = 0x0000_7fff_ffff_ffff;
#[cfg(target_pointer_width = "32")]
const MAX_USER_ADDR: usize = 0x7fff_ffff;
#[cfg(all(target_os = "windows", target_pointer_width = "64"))]
const MAX_HEAP_END: usize = 0x7FFF_FFFF_FFFF_FFFF;
#[cfg(all(target_os = "windows", target_pointer_width = "32"))]
const MAX_HEAP_END: usize = 0x7FFF_FFFF;
const MIN_VALID_ADDR: usize = 0x1000;
#[derive(Clone, Debug)]
pub struct MemoryRegion {
pub start: usize,
pub end: usize,
}
#[derive(Clone, Debug, Default)]
pub struct ValidRegions {
regions: Vec<MemoryRegion>,
is_dynamic: bool,
}
impl ValidRegions {
pub fn empty() -> Self {
Self {
regions: Vec::new(),
is_dynamic: false,
}
}
pub fn from_regions(mut regions: Vec<MemoryRegion>) -> Self {
regions.sort_by_key(|r| r.start);
Self {
regions,
is_dynamic: true,
}
}
pub fn contains(&self, addr: usize) -> bool {
if addr <= MIN_VALID_ADDR {
return false;
}
if self.is_dynamic && !self.regions.is_empty() {
let idx = self.regions.partition_point(|region| region.start <= addr);
if idx > 0 {
let region = &self.regions[idx - 1];
return addr < region.end;
}
false
} else {
addr < MAX_USER_ADDR
}
}
pub fn len(&self) -> usize {
self.regions.len()
}
pub fn is_empty(&self) -> bool {
self.regions.is_empty()
}
pub fn is_dynamic(&self) -> bool {
self.is_dynamic
}
#[cfg(test)]
pub fn debug_dump(&self) {
eprintln!("ValidRegions (is_dynamic={}):", self.is_dynamic);
for (i, region) in self.regions.iter().enumerate() {
eprintln!(" Region {}: 0x{:x} - 0x{:x}", i, region.start, region.end);
}
}
}
static VALID_REGIONS: RwLock<Option<ValidRegions>> = RwLock::new(None);
#[cfg(target_os = "linux")]
fn merge_regions(regions: Vec<MemoryRegion>) -> Vec<MemoryRegion> {
if regions.is_empty() {
return regions;
}
let mut merged: Vec<MemoryRegion> = Vec::with_capacity(regions.len());
let mut current = regions[0].clone();
for region in regions.into_iter().skip(1) {
if region.start < current.end {
current.end = current.end.max(region.end);
} else {
merged.push(current);
current = region;
}
}
merged.push(current);
merged
}
#[cfg(target_os = "linux")]
fn get_valid_regions_impl() -> ValidRegions {
use std::fs;
let content = match fs::read_to_string("/proc/self/maps") {
Ok(c) => c,
Err(_) => {
return get_conservative_regions();
}
};
let mut regions: Vec<MemoryRegion> = content
.lines()
.filter_map(|line| {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
return None;
}
let range: Vec<&str> = parts[0].split('-').collect();
if range.len() != 2 {
return None;
}
let start = usize::from_str_radix(range[0], 16).ok()?;
let end = usize::from_str_radix(range[1], 16).ok()?;
if parts.len() < 2 {
return None;
}
let perms = parts[1];
if !perms.starts_with('r') {
return None;
}
Some(MemoryRegion { start, end })
})
.collect();
regions.sort_by_key(|r| r.start);
regions = merge_regions(regions);
if regions.is_empty() {
return get_conservative_regions();
}
ValidRegions::from_regions(regions)
}
#[cfg(target_os = "linux")]
fn get_conservative_regions() -> ValidRegions {
let regions = vec![MemoryRegion {
start: 0x10000,
end: 0x7FFF_FFFF_FFFF_FFFF, }];
ValidRegions::from_regions(regions)
}
#[cfg(target_os = "windows")]
fn get_valid_regions_impl() -> ValidRegions {
let mut regions = Vec::new();
regions.push(MemoryRegion {
start: 0x10000,
end: MAX_HEAP_END, });
ValidRegions::from_regions(regions)
}
#[cfg(target_os = "macos")]
fn get_valid_regions_impl() -> ValidRegions {
let regions = vec![MemoryRegion {
start: 0x1000, end: 0x7FFF_FFFF_FFFF_FFFF, }];
ValidRegions::from_regions(regions)
}
#[cfg(all(
not(target_os = "linux"),
not(target_os = "windows"),
not(target_os = "macos")
))]
fn get_valid_regions_impl() -> ValidRegions {
let mut regions = Vec::new();
regions.push(MemoryRegion {
start: 0x10000,
end: 0x7FF_FFFFF_FFFF_FFFF,
});
ValidRegions::from_regions(regions)
}
pub fn get_valid_regions() -> ValidRegions {
{
let read_guard = match VALID_REGIONS.read() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if read_guard.is_some() {
return read_guard
.as_ref()
.cloned()
.expect("VALID_REGIONS should be Some after is_some() check (read path)");
}
}
let mut write_guard = match VALID_REGIONS.write() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
if write_guard.is_some() {
return write_guard
.as_ref()
.cloned()
.expect("VALID_REGIONS should be Some after is_some() check (write path)");
}
let regions = get_valid_regions_impl();
*write_guard = Some(regions.clone());
regions
}
pub fn is_valid_ptr(p: usize) -> bool {
get_valid_regions().contains(p)
}
pub fn is_valid_ptr_static(p: usize) -> bool {
p > MIN_VALID_ADDR && p < MAX_USER_ADDR
}
pub struct MemoryView<'a> {
data: &'a [u8],
}
pub struct OwnedMemoryView {
data: Vec<u8>,
}
impl OwnedMemoryView {
pub fn new(data: Vec<u8>) -> Self {
Self { data }
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn read_usize(&self, offset: usize) -> Option<usize> {
let size = std::mem::size_of::<usize>();
if offset.saturating_add(size) > self.data.len() {
return None;
}
let mut buf = [0u8; 8];
buf[..size].copy_from_slice(&self.data[offset..offset + size]);
Some(usize::from_le_bytes(buf))
}
pub fn read_u8(&self, offset: usize) -> Option<u8> {
self.data.get(offset).copied()
}
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn chunks(&self, chunk_size: usize) -> impl Iterator<Item = &[u8]> {
self.data.chunks(chunk_size)
}
}
impl<'a> MemoryView<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data }
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn read_usize(&self, offset: usize) -> Option<usize> {
let size = std::mem::size_of::<usize>();
if offset.saturating_add(size) > self.data.len() {
return None;
}
let mut buf = [0u8; 8];
buf[..size].copy_from_slice(&self.data[offset..offset + size]);
Some(usize::from_le_bytes(buf))
}
pub fn read_u8(&self, offset: usize) -> Option<u8> {
self.data.get(offset).copied()
}
pub fn last_byte(&self) -> Option<u8> {
self.data.last().copied()
}
pub fn as_slice(&self) -> &'a [u8] {
self.data
}
pub fn chunks(&self, chunk_size: usize) -> impl Iterator<Item = &'a [u8]> {
self.data.chunks(chunk_size)
}
}
pub fn count_valid_pointers(view: &MemoryView) -> usize {
let ptr_size = std::mem::size_of::<usize>();
let mut count = 0;
for chunk in view.chunks(ptr_size) {
if chunk.len() < ptr_size {
break;
}
let mut buf = [0u8; 16]; buf[..ptr_size].copy_from_slice(chunk);
let v = if ptr_size == 8 {
usize::from_le_bytes(buf[..8].try_into().unwrap())
} else {
usize::from_le_bytes({
let mut arr = [0u8; 8];
arr[..ptr_size].copy_from_slice(&buf[..ptr_size]);
arr
})
};
if is_valid_ptr(v) {
count += 1;
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_view_read_usize() {
let data: [u8; 16] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
];
let view = MemoryView::new(&data);
let val0 = view.read_usize(0).unwrap();
let val8 = view.read_usize(8).unwrap();
assert_eq!(val0, 0x0706050403020100);
assert_eq!(val8, 0x0f0e0d0c0b0a0908);
}
#[test]
fn test_memory_view_bounds_check() {
let data = [0u8; 8];
let view = MemoryView::new(&data);
assert!(view.read_usize(0).is_some());
assert!(view.read_usize(1).is_none());
assert!(view.read_usize(8).is_none());
}
#[test]
#[cfg(target_os = "macos")]
fn test_is_valid_ptr_static() {
assert!(!is_valid_ptr_static(0));
assert!(!is_valid_ptr_static(0x1000));
assert!(is_valid_ptr_static(0x10000));
assert!(is_valid_ptr_static(0x7fff_ffff_0000));
assert!(!is_valid_ptr_static(0xffff_ffff_ffff_ffff));
}
#[test]
#[cfg(target_os = "macos")]
fn test_is_valid_ptr() {
assert!(!is_valid_ptr(0));
assert!(!is_valid_ptr(0x1000));
assert!(is_valid_ptr(0x10000));
}
#[test]
#[cfg(target_os = "macos")]
fn test_count_valid_pointers() {
let mut data = [0u8; 24];
let valid_ptr: usize = 0x10000;
data[..8].copy_from_slice(&valid_ptr.to_le_bytes());
let view = MemoryView::new(&data);
assert_eq!(count_valid_pointers(&view), 1);
}
#[test]
fn test_valid_regions_contains() {
let regions = ValidRegions::empty();
assert!(regions.contains(0x10000));
assert!(!regions.contains(0));
}
#[test]
fn test_valid_regions_from_regions() {
let regions = ValidRegions::from_regions(vec![
MemoryRegion {
start: 0x1000,
end: 0x2000,
},
MemoryRegion {
start: 0x3000,
end: 0x4000,
},
]);
assert!(regions.is_dynamic());
assert!(regions.contains(0x1500));
assert!(regions.contains(0x3500));
assert!(!regions.contains(0x2500));
assert!(!regions.contains(0x5000));
}
#[test]
fn test_get_valid_regions() {
let regions = get_valid_regions();
let _ = regions.contains(0x10000);
}
}