use sysconf::page::pagesize;
use super::*;
use super::perms::*;
extern crate test;
#[cfg_attr(windows, allow(unused))]
use std::time::{Duration, Instant};
#[cfg_attr(windows, allow(unused))]
use self::test::Bencher;
fn test_valid_map_address(ptr: *mut u8) {
assert!(ptr as usize > 0, "ptr: {:?}", ptr);
assert!(ptr as usize % pagesize() == 0, "ptr: {:?}", ptr);
}
unsafe fn test_zero_filled(ptr: *mut u8, size: usize) {
for i in 0..size {
assert_eq!(*ptr.offset(i as isize), 0);
}
}
unsafe fn test_write(ptr: *mut u8, size: usize) {
for i in 0..size {
*ptr.offset(i as isize) = (i & 0xff) as u8;
}
}
unsafe fn test_read(ptr: *mut u8, size: usize) {
for i in 0..size {
let got = *ptr.offset(i as isize);
let want = (i & 0xff) as u8;
assert_eq!(
got,
want,
"mismatch at byte {} in block {:?}: got {}, want {}",
i,
ptr,
got,
want
);
}
}
unsafe fn test_write_read(ptr: *mut u8, size: usize) {
test_write(ptr, size);
test_read(ptr, size);
}
#[test]
fn test_map() {
unsafe {
let mut ptr = map(pagesize(), PROT_READ_WRITE, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, pagesize(), PROT_READ_WRITE);
test_zero_filled(ptr, pagesize());
unmap(ptr, pagesize());
#[cfg(not(windows))]
unmap(ptr, pagesize());
ptr = map(16 * pagesize(), PROT_READ_WRITE, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, 16 * pagesize(), PROT_READ_WRITE);
test_zero_filled(ptr, 16 * pagesize());
unmap(ptr, 16 * pagesize());
#[cfg(not(windows))]
unmap(ptr, 16 * pagesize());
}
}
#[cfg(not(windows))]
#[test]
fn test_map_non_windows() {
unsafe {
let mut ptr = map(5 * pagesize(), PROT_READ_WRITE, false).unwrap();
test_valid_map_address(ptr);
test_zero_filled(ptr, 5 * pagesize());
unmap(ptr, pagesize());
unmap(ptr.offset(2 * pagesize() as isize), pagesize());
unmap(ptr.offset(4 * pagesize() as isize), pagesize());
test_zero_filled(ptr.offset(1 * pagesize() as isize), pagesize());
test_zero_filled(ptr.offset(3 * pagesize() as isize), pagesize());
let size = 1 << 29;
let t0 = Instant::now();
ptr = map(size, PROT_READ_WRITE, false).unwrap();
let diff = Instant::now().duration_since(t0);
let target = Duration::from_millis(1);
assert!(diff < target, "duration: {:?}", diff);
test_valid_map_address(ptr);
test_zero_filled(ptr.offset((size / 2) as isize), pagesize());
unmap(ptr, size);
}
}
#[test]
fn test_realloc_small() {
unsafe fn test(read: bool, write: bool, exec: bool) {
let builder = MapAllocBuilder::default()
.read(read)
.write(write)
.exec(exec);
#[cfg(windows)]
let builder = builder.commit(true);
let mut alloc = builder.build();
#[cfg(any(target_os = "linux", windows))]
let perm = get_perm(read, write, exec);
let test_contents = |ptr: *mut u8, size: usize| if read && write {
test_write_read(ptr, size);
} else if read {
test_zero_filled(ptr, size);
} else if write {
test_write(ptr, size);
};
let small = Layout::array::<u8>(1).unwrap();
let medium = Layout::array::<u8>(2048).unwrap();
let large = Layout::array::<u8>(4096).unwrap();
let ptr = <MapAlloc as Alloc>::alloc(&mut alloc, medium.clone()).unwrap();
test_valid_map_address(ptr);
if read {
test_zero_filled(ptr, medium.size());
}
test_contents(ptr, medium.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, medium.size(), perm);
alloc
.shrink_in_place(ptr, medium.clone(), small.clone())
.unwrap();
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, small.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, small.size(), perm);
alloc
.grow_in_place(ptr, small.clone(), large.clone())
.unwrap();
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, large.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, large.size(), perm);
let old_ptr = ptr;
let ptr = alloc.realloc(ptr, large.clone(), small.clone()).unwrap();
if cfg!(target_os = "linux") {
assert_eq!(old_ptr, ptr);
}
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, small.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, small.size(), perm);
<MapAlloc as Alloc>::dealloc(&mut alloc, ptr, small.clone());
}
unsafe {
test(false, false, false);
test(true, false, false);
test(false, true, false);
test(false, false, true);
test(true, true, false);
test(true, false, true);
test(false, true, true);
test(true, true, true);
}
}
#[test]
fn test_realloc_large() {
unsafe fn test(read: bool, write: bool, exec: bool) {
let builder = MapAllocBuilder::default()
.read(read)
.write(write)
.exec(exec);
#[cfg(windows)]
let builder = builder.commit(true);
let mut alloc = builder.build();
#[cfg(any(target_os = "linux", windows))]
let perm = get_perm(read, write, exec);
let test_contents = |ptr: *mut u8, size: usize| if read && write {
test_write_read(ptr, size);
} else if read {
test_zero_filled(ptr, size);
} else if write {
test_write(ptr, size);
};
let small = Layout::array::<u8>(alloc.pagesize).unwrap();
let medium = Layout::array::<u8>(alloc.pagesize * 8).unwrap();
let large = Layout::array::<u8>(alloc.pagesize * 16).unwrap();
let mut ptr = <MapAlloc as Alloc>::alloc(&mut alloc, large.clone()).unwrap();
test_valid_map_address(ptr);
if read {
test_zero_filled(ptr, large.size());
}
test_contents(ptr, large.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, large.size(), perm);
if cfg!(target_os = "linux") {
alloc
.shrink_in_place(ptr, large.clone(), small.clone())
.unwrap();
ptr = alloc.realloc(ptr, large.clone(), small.clone()).unwrap();
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, small.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, small.size(), perm);
alloc
.grow_in_place(ptr, small.clone(), medium.clone())
.unwrap();
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, medium.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, medium.size(), perm);
} else {
ptr = alloc.realloc(ptr, large.clone(), medium.clone()).unwrap();
test_valid_map_address(ptr);
if read && write {
test_read(ptr, medium.size());
}
}
let old_ptr = ptr;
let ptr = alloc.realloc(ptr, medium.clone(), small.clone()).unwrap();
if cfg!(target_os = "linux") {
assert_eq!(old_ptr, ptr);
}
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, small.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, small.size(), perm);
let ptr = alloc.realloc(ptr, small.clone(), large.clone()).unwrap();
if cfg!(target_os = "linux") {
assert_eq!(old_ptr, ptr);
}
if read && write {
test_read(ptr, small.size());
}
test_contents(ptr, large.size());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, large.size(), perm);
if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
let remaining = ptr.offset(alloc.pagesize as isize);
let remaining_layout = Layout::array::<u8>(large.size() - alloc.pagesize).unwrap();
let new = alloc.realloc(ptr, small.clone(), medium.clone()).unwrap();
if read && write {
test_read(new, small.size());
test_read(remaining, remaining_layout.size());
}
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(new, medium.size(), perm);
<MapAlloc as Alloc>::dealloc(&mut alloc, new, medium.clone());
<MapAlloc as Alloc>::dealloc(&mut alloc, remaining, remaining_layout.clone());
}
}
unsafe {
test(true, true, false);
}
}
#[test]
fn test_commit() {
unsafe {
let mut ptr = map(pagesize(), PROT_READ_WRITE, !cfg!(target_os = "macos")).unwrap();
test_valid_map_address(ptr);
test_zero_filled(ptr, pagesize());
unmap(ptr, pagesize());
ptr = map(pagesize(), PROT_READ_WRITE, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, pagesize(), PROT_READ_WRITE);
test_zero_filled(ptr, pagesize());
#[cfg(windows)]
commit(ptr, pagesize(), PROT_READ_WRITE);
uncommit(ptr, pagesize());
uncommit(ptr, pagesize());
unmap(ptr, pagesize());
}
}
#[test]
fn test_perms() {
unsafe {
let mut ptr = map(pagesize(), PROT_READ, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, pagesize(), PROT_READ);
test_zero_filled(ptr, pagesize());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, pagesize(), PROT_READ);
unmap(ptr, pagesize());
ptr = map(pagesize(), PROT_WRITE, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, pagesize(), PROT_WRITE);
test_write(ptr, pagesize());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, pagesize(), PROT_WRITE);
unmap(ptr, pagesize());
ptr = map(pagesize(), PROT_READ_WRITE, false).unwrap();
test_valid_map_address(ptr);
#[cfg(windows)]
commit(ptr, pagesize(), PROT_READ_WRITE);
test_zero_filled(ptr, pagesize());
test_write_read(ptr, pagesize());
#[cfg(any(target_os = "linux", windows))]
assert_block_perm(ptr, pagesize(), PROT_READ_WRITE);
unmap(ptr, pagesize());
#[cfg(target_os = "linux")]
{
fn test_perms(perm: Perm) {
unsafe {
let ptr = map(pagesize(), perm, false).unwrap();
test_valid_map_address(ptr);
assert_block_perm(ptr, pagesize(), perm);
unmap(ptr, pagesize());
}
}
test_perms(PROT_NONE);
test_perms(PROT_EXEC);
test_perms(PROT_READ_EXEC);
test_perms(PROT_WRITE_EXEC);
test_perms(PROT_READ_WRITE_EXEC);
}
}
}
#[cfg(not(windows))]
#[test]
#[should_panic]
fn test_map_panic_zero() {
unsafe {
map(0, PROT_READ_WRITE, false);
}
}
#[cfg(all(not(all(target_os = "linux", target_pointer_width = "64")), not(windows)))]
#[test]
#[should_panic]
fn test_map_panic_too_large() {
unsafe {
use core::usize::MAX;
map(MAX, PROT_READ_WRITE, false);
}
}
#[cfg(not(windows))]
#[test]
#[should_panic]
fn test_unmap_panic_zero() {
unsafe {
let ptr = map(pagesize(), PROT_READ_WRITE, false).unwrap();
unmap(ptr, 0);
}
}
#[test]
#[should_panic]
fn test_unmap_panic_unaligned() {
unsafe {
unmap((pagesize() / 2) as *mut u8, pagesize());
}
}
#[cfg(not(windows))]
#[bench]
#[ignore]
fn bench_large_map(b: &mut Bencher) {
b.iter(|| unsafe {
let ptr = map(1 << 29, PROT_READ_WRITE, false).unwrap();
unmap(ptr, 1 << 29);
})
}
#[cfg(target_os = "linux")]
fn assert_block_perm(ptr: *mut u8, size: usize, perm: Perm) {
let ptr = ptr as usize;
for block in perms() {
let disjoint = (ptr + size) <= block.begin as usize || block.end as usize <= ptr;
if !disjoint {
assert_eq!(perm, block.perm);
}
}
}
#[cfg(windows)]
fn assert_block_perm(ptr: *mut u8, size: usize, perm: Perm) {
unsafe {
use std::mem;
let mut meminfo: winapi::winnt::MEMORY_BASIC_INFORMATION = mem::uninitialized();
let mbi_size = mem::size_of::<winapi::winnt::MEMORY_BASIC_INFORMATION>();
let ret = kernel32::VirtualQuery(ptr as *mut _, &mut meminfo as *mut _, mbi_size as u64);
assert_ne!(ret, 0);
assert!(meminfo.RegionSize >= size as u64);
assert_eq!(meminfo.Protect, perm);
}
}
#[cfg(target_os = "linux")]
#[derive(Debug)]
struct Block {
begin: *mut u8,
end: *mut u8,
perm: Perm,
}
#[cfg(target_os = "linux")]
#[test]
fn test_perms_fn() {
perms();
}
#[cfg(target_os = "linux")]
fn perms() -> Vec<Block> {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert(String::from("---p"), PROT_NONE);
map.insert(String::from("r--p"), PROT_READ);
map.insert(String::from("-w-p"), PROT_WRITE);
map.insert(String::from("--xp"), PROT_EXEC);
map.insert(String::from("rw-p"), PROT_READ_WRITE);
map.insert(String::from("r-xp"), PROT_READ_EXEC);
map.insert(String::from("-wxp"), PROT_WRITE_EXEC);
map.insert(String::from("rwxp"), PROT_READ_WRITE_EXEC);
let mut blocks = Vec::new();
use std::fs::File;
use std::io::Read;
let mut output = String::new();
let mut file = File::open(format!("/proc/{}/maps", unsafe { libc::getpid() })).unwrap();
file.read_to_string(&mut output).unwrap();
for line in output.lines() {
let first_dash = line.find('-').unwrap();
let (first, mut remaining) = line.split_at(first_dash);
remaining = remaining.split_at(1).1; let first_space = remaining.find(' ').unwrap();
let (second, mut third) = remaining.split_at(first_space);
third = third.split_at(1).1.split(' ').next().unwrap();
let begin = usize::from_str_radix(first, 16).unwrap() as *mut u8;
let end = usize::from_str_radix(second, 16).unwrap() as *mut u8;
let perm = *map.get(third).unwrap();
blocks.push(Block { begin, end, perm });
}
blocks
}