use crate::util::alloc::AllocationError;
use crate::util::opaque_pointer::*;
use crate::util::Address;
use crate::vm::{Collection, VMBinding};
use bytemuck::NoUninit;
use libc::{PROT_EXEC, PROT_NONE, PROT_READ, PROT_WRITE};
use std::io::{Error, Result};
use sysinfo::MemoryRefreshKind;
use sysinfo::{RefreshKind, System};
#[cfg(target_os = "linux")]
const MMAP_FLAGS: libc::c_int = libc::MAP_ANON | libc::MAP_PRIVATE | libc::MAP_FIXED_NOREPLACE;
#[cfg(target_os = "macos")]
const MMAP_FLAGS: libc::c_int = libc::MAP_ANON | libc::MAP_PRIVATE | libc::MAP_FIXED;
#[derive(Debug, Copy, Clone)]
pub struct MmapStrategy {
pub huge_page: HugePageSupport,
pub prot: MmapProtection,
}
impl MmapStrategy {
pub fn new(transparent_hugepages: bool, prot: MmapProtection) -> Self {
Self {
huge_page: if transparent_hugepages {
HugePageSupport::TransparentHugePages
} else {
HugePageSupport::No
},
prot,
}
}
pub const INTERNAL_MEMORY: Self = Self {
huge_page: HugePageSupport::No,
prot: MmapProtection::ReadWrite,
};
pub const SIDE_METADATA: Self = Self::INTERNAL_MEMORY;
#[cfg(test)]
pub const TEST: Self = Self::INTERNAL_MEMORY;
}
#[repr(i32)]
#[derive(Debug, Copy, Clone)]
pub enum MmapProtection {
ReadWrite,
ReadWriteExec,
NoAccess,
}
impl MmapProtection {
pub fn into_native_flags(self) -> libc::c_int {
match self {
Self::ReadWrite => PROT_READ | PROT_WRITE,
Self::ReadWriteExec => PROT_READ | PROT_WRITE | PROT_EXEC,
Self::NoAccess => PROT_NONE,
}
}
}
#[repr(u8)]
#[derive(Debug, Copy, Clone, NoUninit)]
pub enum HugePageSupport {
No,
TransparentHugePages,
}
pub enum MmapAnnotation<'a> {
Space {
name: &'a str,
},
SideMeta {
space: &'a str,
meta: &'a str,
},
Test {
file: &'a str,
line: u32,
},
Misc {
name: &'a str,
},
}
#[macro_export]
macro_rules! mmap_anno_test {
() => {
&$crate::util::memory::MmapAnnotation::Test {
file: file!(),
line: line!(),
}
};
}
pub use mmap_anno_test;
impl std::fmt::Display for MmapAnnotation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MmapAnnotation::Space { name } => write!(f, "mmtk:space:{name}"),
MmapAnnotation::SideMeta { space, meta } => write!(f, "mmtk:sidemeta:{space}:{meta}"),
MmapAnnotation::Test { file, line } => write!(f, "mmtk:test:{file}:{line}"),
MmapAnnotation::Misc { name } => write!(f, "mmtk:misc:{name}"),
}
}
}
pub(crate) fn result_is_mapped(result: Result<()>) -> bool {
match result {
Ok(_) => false,
Err(err) => err.raw_os_error().unwrap() == libc::EEXIST,
}
}
pub fn zero(start: Address, len: usize) {
set(start, 0, len);
}
pub fn set(start: Address, val: u8, len: usize) {
unsafe {
std::ptr::write_bytes::<u8>(start.to_mut_ptr(), val, len);
}
}
#[allow(clippy::let_and_return)] pub unsafe fn dzmmap(
start: Address,
size: usize,
strategy: MmapStrategy,
anno: &MmapAnnotation,
) -> Result<()> {
let flags = libc::MAP_ANON | libc::MAP_PRIVATE | libc::MAP_FIXED;
let ret = mmap_fixed(start, size, flags, strategy, anno);
#[cfg(not(target_os = "linux"))]
if ret.is_ok() {
zero(start, size)
}
ret
}
#[allow(clippy::let_and_return)] pub fn dzmmap_noreplace(
start: Address,
size: usize,
strategy: MmapStrategy,
anno: &MmapAnnotation,
) -> Result<()> {
let flags = MMAP_FLAGS;
let ret = mmap_fixed(start, size, flags, strategy, anno);
#[cfg(not(target_os = "linux"))]
if ret.is_ok() {
zero(start, size)
}
ret
}
pub fn mmap_noreserve(
start: Address,
size: usize,
mut strategy: MmapStrategy,
anno: &MmapAnnotation,
) -> Result<()> {
strategy.prot = MmapProtection::NoAccess;
let flags = MMAP_FLAGS | libc::MAP_NORESERVE;
mmap_fixed(start, size, flags, strategy, anno)
}
fn mmap_fixed(
start: Address,
size: usize,
flags: libc::c_int,
strategy: MmapStrategy,
_anno: &MmapAnnotation,
) -> Result<()> {
let ptr = start.to_mut_ptr();
let prot = strategy.prot.into_native_flags();
wrap_libc_call(
&|| unsafe { libc::mmap(start.to_mut_ptr(), size, prot, flags, -1, 0) },
ptr,
)?;
#[cfg(all(
any(target_os = "linux", target_os = "android"),
not(feature = "no_mmap_annotation")
))]
{
let anno_str = _anno.to_string();
let anno_cstr = std::ffi::CString::new(anno_str).unwrap();
let result = wrap_libc_call(
&|| unsafe {
libc::prctl(
libc::PR_SET_VMA,
libc::PR_SET_VMA_ANON_NAME,
start.to_ptr::<libc::c_void>(),
size,
anno_cstr.as_ptr(),
)
},
0,
);
if let Err(e) = result {
debug!("Error while calling prctl: {e}");
}
}
match strategy.huge_page {
HugePageSupport::No => Ok(()),
HugePageSupport::TransparentHugePages => {
#[cfg(target_os = "linux")]
{
wrap_libc_call(
&|| unsafe { libc::madvise(start.to_mut_ptr(), size, libc::MADV_HUGEPAGE) },
0,
)
}
#[cfg(not(target_os = "linux"))]
unreachable!()
}
}
}
pub fn munmap(start: Address, size: usize) -> Result<()> {
wrap_libc_call(&|| unsafe { libc::munmap(start.to_mut_ptr(), size) }, 0)
}
pub fn handle_mmap_error<VM: VMBinding>(
error: Error,
tls: VMThread,
addr: Address,
bytes: usize,
) -> ! {
use std::io::ErrorKind;
eprintln!("Failed to mmap {}, size {}", addr, bytes);
eprintln!("{}", get_process_memory_maps());
match error.kind() {
ErrorKind::OutOfMemory => {
trace!("Signal MmapOutOfMemory!");
VM::VMCollection::out_of_memory(tls, AllocationError::MmapOutOfMemory);
unreachable!()
}
ErrorKind::Other => {
if let Some(os_errno) = error.raw_os_error() {
if os_errno == libc::ENOMEM {
trace!("Signal MmapOutOfMemory!");
VM::VMCollection::out_of_memory(tls, AllocationError::MmapOutOfMemory);
unreachable!()
}
}
}
ErrorKind::AlreadyExists => {
panic!("Failed to mmap, the address is already mapped. Should MMTk quarantine the address range first?");
}
_ => {}
}
panic!("Unexpected mmap failure: {:?}", error)
}
pub(crate) fn panic_if_unmapped(_start: Address, _size: usize, _anno: &MmapAnnotation) {
#[cfg(target_os = "linux")]
{
let flags = MMAP_FLAGS;
match mmap_fixed(
_start,
_size,
flags,
MmapStrategy {
huge_page: HugePageSupport::No,
prot: MmapProtection::ReadWrite,
},
_anno,
) {
Ok(_) => panic!("{} of size {} is not mapped", _start, _size),
Err(e) => {
assert!(
e.kind() == std::io::ErrorKind::AlreadyExists,
"Failed to check mapped: {:?}",
e
);
}
}
}
}
pub fn munprotect(start: Address, size: usize, prot: MmapProtection) -> Result<()> {
let prot = prot.into_native_flags();
wrap_libc_call(
&|| unsafe { libc::mprotect(start.to_mut_ptr(), size, prot) },
0,
)
}
pub fn mprotect(start: Address, size: usize) -> Result<()> {
wrap_libc_call(
&|| unsafe { libc::mprotect(start.to_mut_ptr(), size, PROT_NONE) },
0,
)
}
fn wrap_libc_call<T: PartialEq>(f: &dyn Fn() -> T, expect: T) -> Result<()> {
let ret = f();
if ret == expect {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn get_process_memory_maps() -> String {
use std::fs::File;
use std::io::Read;
let mut data = String::new();
let mut f = File::open("/proc/self/maps").unwrap();
f.read_to_string(&mut data).unwrap();
data
}
#[cfg(target_os = "macos")]
pub fn get_process_memory_maps() -> String {
let pid = std::process::id();
let output = std::process::Command::new("vmmap")
.arg(pid.to_string()) .output() .expect("Failed to execute vmmap command");
if output.status.success() {
let output_str =
std::str::from_utf8(&output.stdout).expect("Failed to convert output to string");
output_str.to_string()
} else {
let error_message =
std::str::from_utf8(&output.stderr).expect("Failed to convert error message to string");
panic!("Failed to get process memory map: {}", error_message)
}
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
pub fn get_process_memory_maps() -> String {
"(process map unavailable)".to_string()
}
pub(crate) fn get_system_total_memory() -> u64 {
let sys = System::new_with_specifics(
RefreshKind::nothing().with_memory(MemoryRefreshKind::nothing().with_ram()),
);
sys.total_memory()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::constants::BYTES_IN_PAGE;
use crate::util::test_util::MEMORY_TEST_REGION;
use crate::util::test_util::{serial_test, with_cleanup};
const START: Address = MEMORY_TEST_REGION.start;
#[test]
fn test_mmap() {
serial_test(|| {
with_cleanup(
|| {
let res = unsafe {
dzmmap(START, BYTES_IN_PAGE, MmapStrategy::TEST, mmap_anno_test!())
};
assert!(res.is_ok());
let res = unsafe {
dzmmap(START, BYTES_IN_PAGE, MmapStrategy::TEST, mmap_anno_test!())
};
assert!(res.is_ok());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
);
});
}
#[test]
fn test_munmap() {
serial_test(|| {
with_cleanup(
|| {
let res = dzmmap_noreplace(
START,
BYTES_IN_PAGE,
MmapStrategy::TEST,
mmap_anno_test!(),
);
assert!(res.is_ok());
let res = munmap(START, BYTES_IN_PAGE);
assert!(res.is_ok());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
)
})
}
#[cfg(target_os = "linux")]
#[test]
fn test_mmap_noreplace() {
serial_test(|| {
with_cleanup(
|| {
let res = unsafe {
dzmmap(START, BYTES_IN_PAGE, MmapStrategy::TEST, mmap_anno_test!())
};
assert!(res.is_ok());
let res = dzmmap_noreplace(
START,
BYTES_IN_PAGE,
MmapStrategy::TEST,
mmap_anno_test!(),
);
assert!(res.is_err());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
)
});
}
#[test]
fn test_mmap_noreserve() {
serial_test(|| {
with_cleanup(
|| {
let res =
mmap_noreserve(START, BYTES_IN_PAGE, MmapStrategy::TEST, mmap_anno_test!());
assert!(res.is_ok());
let res = unsafe {
dzmmap(START, BYTES_IN_PAGE, MmapStrategy::TEST, mmap_anno_test!())
};
assert!(res.is_ok());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
)
})
}
#[cfg(target_os = "linux")]
#[test]
#[should_panic]
fn test_check_is_mmapped_for_unmapped() {
serial_test(|| {
with_cleanup(
|| {
panic_if_unmapped(START, BYTES_IN_PAGE, mmap_anno_test!());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
)
})
}
#[test]
fn test_check_is_mmapped_for_mapped() {
serial_test(|| {
with_cleanup(
|| {
assert!(dzmmap_noreplace(
START,
BYTES_IN_PAGE,
MmapStrategy::TEST,
mmap_anno_test!()
)
.is_ok());
panic_if_unmapped(START, BYTES_IN_PAGE, mmap_anno_test!());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE).is_ok());
},
)
})
}
#[cfg(target_os = "linux")]
#[test]
#[should_panic]
fn test_check_is_mmapped_for_unmapped_next_to_mapped() {
serial_test(|| {
with_cleanup(
|| {
assert!(dzmmap_noreplace(
START,
BYTES_IN_PAGE,
MmapStrategy::TEST,
mmap_anno_test!(),
)
.is_ok());
panic_if_unmapped(START + BYTES_IN_PAGE, BYTES_IN_PAGE, mmap_anno_test!());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE * 2).is_ok());
},
)
})
}
#[test]
#[should_panic]
#[ignore]
fn test_check_is_mmapped_for_partial_mapped() {
serial_test(|| {
with_cleanup(
|| {
assert!(dzmmap_noreplace(
START,
BYTES_IN_PAGE,
MmapStrategy::TEST,
mmap_anno_test!()
)
.is_ok());
panic_if_unmapped(START, BYTES_IN_PAGE * 2, mmap_anno_test!());
},
|| {
assert!(munmap(START, BYTES_IN_PAGE * 2).is_ok());
},
)
})
}
#[test]
fn test_get_system_total_memory() {
let total = get_system_total_memory();
println!("Total memory: {:?}", total);
}
}