extern crate libc;
#[cfg(target_os = "macos")]
extern crate anyhow;
#[cfg(target_os = "macos")]
extern crate libproc;
#[cfg(target_os = "macos")]
extern crate mach2;
#[cfg(windows)]
extern crate winapi;
#[cfg(target_os = "macos")]
pub mod mac_maps;
#[cfg(target_os = "macos")]
pub use mac_maps::{get_process_maps, MapRange, Pid};
#[cfg(any(target_os = "linux", target_os = "android"))]
pub mod linux_maps;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub use linux_maps::{get_process_maps, MapRange, Pid};
#[cfg(windows)]
pub mod win_maps;
#[cfg(windows)]
pub use win_maps::{get_process_maps, MapRange, Pid};
#[cfg(target_os = "freebsd")]
pub mod freebsd_maps;
#[cfg(target_os = "freebsd")]
pub use freebsd_maps::{get_process_maps, MapRange, Pid};
trait MapRangeImpl {
fn size(&self) -> usize;
fn start(&self) -> usize;
fn filename(&self) -> Option<&std::path::Path>;
fn is_exec(&self) -> bool;
fn is_write(&self) -> bool;
fn is_read(&self) -> bool;
}
impl MapRange {
#[inline]
pub fn size(&self) -> usize {
MapRangeImpl::size(self)
}
#[inline]
pub fn start(&self) -> usize {
MapRangeImpl::start(self)
}
#[inline]
pub fn filename(&self) -> Option<&std::path::Path> {
MapRangeImpl::filename(self)
}
#[inline]
pub fn is_exec(&self) -> bool {
MapRangeImpl::is_exec(self)
}
#[inline]
pub fn is_write(&self) -> bool {
MapRangeImpl::is_write(self)
}
#[inline]
pub fn is_read(&self) -> bool {
MapRangeImpl::is_read(self)
}
}
fn map_contain_addr(map: &MapRange, addr: usize) -> bool {
let start = map.start();
(addr >= start) && (addr < (start + map.size()))
}
pub fn maps_contain_addr(addr: usize, maps: &[MapRange]) -> bool {
maps.iter().any(|map| map_contain_addr(map, addr))
}
pub fn maps_contain_addr_range(mut addr: usize, mut size: usize, maps: &[MapRange]) -> bool {
if size == 0 || addr.checked_add(size).is_none() {
return false;
}
while size > 0 {
match maps.iter().find(|map| map_contain_addr(map, addr)) {
None => return false,
Some(map) => {
let end = map.start() + map.size();
if addr + size <= end {
return true;
} else {
size -= end - addr;
addr = end;
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use crate::get_process_maps;
use crate::Pid;
#[cfg(not(target_os = "windows"))]
fn test_process_path() -> Option<std::path::PathBuf> {
std::env::current_exe().ok().and_then(|p| {
p.parent().map(|p| {
p.with_file_name("test")
.with_extension(std::env::consts::EXE_EXTENSION)
})
})
}
#[cfg(not(target_os = "freebsd"))]
#[test]
fn test_map_from_test_binary_present() -> () {
let maps = get_process_maps(std::process::id() as Pid).unwrap();
let region = maps.iter().find(|map| {
if let Some(filename) = map.filename() {
filename.to_string_lossy().contains("proc_maps")
} else {
false
}
});
assert!(
region.is_some(),
"We should have a map for the current test process"
);
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_map_from_invoked_binary_present() -> () {
let path = test_process_path().unwrap();
if !path.exists() {
println!("Skipping test because the 'test' binary hasn't been built");
return;
}
let mut have_expected_map = false;
for _ in 1..10 {
let mut child = std::process::Command::new(&path)
.spawn()
.expect("failed to execute test process");
let maps = get_process_maps(child.id() as Pid).unwrap();
child.kill().expect("failed to kill test process");
let region = maps.iter().find(|map| {
if let Some(filename) = map.filename() {
filename.to_string_lossy().contains("/test")
} else {
false
}
});
if region.is_some() {
have_expected_map = true;
break;
} else {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
assert!(
have_expected_map,
"We should have a map from the binary we invoked!"
);
}
}