1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
//! Get virtual memory maps from another process
//!
//! This crate provides a function—[`get_process_maps`](linux_maps/fn.get_process_maps.html)
//! that returns a Vec of [`MapRange`](linux_maps/struct.MapRange.html) structs.
//!
//! This code works on Linux, macOS, and Windows. Each operating system has a different
//! implementation, but the functions and structs for all OSes share the same
//! interface - so this can be used generically across operating systems.
//!
//! Note: on macOS this requires root access, and even with root will still not
//! work on processes that have System Integrity Protection enabled
//! (anything in /usr/bin for example).
//!
//! # Example
//!
//! ```rust,no_run
//! use proc_maps::{get_process_maps, MapRange, Pid};
//!
//! let maps = get_process_maps(123456 as Pid).unwrap();
//! for map in maps {
//! println!("Filename {:?} Address {} Size {}", map.filename(), map.start(), map.size());
//! }
//! ```
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 to implement on MapRange, to provide an implementation.
///
/// By using a private trait, and providing an inherent implementation, we ensure the provided methods
/// are the same for all supported OSes.
trait MapRangeImpl {
/// Returns the size of this MapRange in bytes
fn size(&self) -> usize;
/// Returns the address this MapRange starts at
fn start(&self) -> usize;
/// Returns the filename of the loaded module
fn filename(&self) -> Option<&std::path::Path>;
/// Returns whether this range contains executable code
fn is_exec(&self) -> bool;
/// Returns whether this range contains writeable memory
fn is_write(&self) -> bool;
/// Returns whether this range contains readable memory
fn is_read(&self) -> bool;
}
impl MapRange {
/// Returns the size of this MapRange in bytes
#[inline]
pub fn size(&self) -> usize {
MapRangeImpl::size(self)
}
/// Returns the address this MapRange starts at
#[inline]
pub fn start(&self) -> usize {
MapRangeImpl::start(self)
}
/// Returns the filename of the loaded module
#[inline]
pub fn filename(&self) -> Option<&std::path::Path> {
MapRangeImpl::filename(self)
}
/// Returns whether this range contains executable code
#[inline]
pub fn is_exec(&self) -> bool {
MapRangeImpl::is_exec(self)
}
/// Returns whether this range contains writeable memory
#[inline]
pub fn is_write(&self) -> bool {
MapRangeImpl::is_write(self)
}
/// Returns whether this range contains readable memory
#[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()))
}
/// Returns whether or not any MapRange contains the given address
/// Note: this will only work correctly on macOS and Linux.
pub fn maps_contain_addr(addr: usize, maps: &[MapRange]) -> bool {
maps.iter().any(|map| map_contain_addr(map, addr))
}
#[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;
// The maps aren't populated immediately on Linux, so retry a few times if needed
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!"
);
}
}