proc_maps/
linux_maps.rs

1use libc;
2use std;
3use std::fs::File;
4use std::io::Read;
5use std::path::{Path, PathBuf};
6
7use MapRangeImpl;
8
9pub type Pid = libc::pid_t;
10
11/// A struct representing a single virtual memory region.
12///
13/// While this structure is only for Linux, the macOS, Windows, and FreeBSD
14/// variants have identical exposed methods
15#[derive(Debug, Clone, PartialEq)]
16pub struct MapRange {
17    range_start: usize,
18    range_end: usize,
19    pub offset: usize,
20    pub dev: String,
21    pub flags: String,
22    pub inode: usize,
23    pathname: Option<PathBuf>,
24}
25
26impl MapRangeImpl for MapRange {
27    fn size(&self) -> usize {
28        self.range_end - self.range_start
29    }
30    fn start(&self) -> usize {
31        self.range_start
32    }
33    fn filename(&self) -> Option<&Path> {
34        self.pathname.as_deref()
35    }
36    fn is_exec(&self) -> bool {
37        &self.flags[2..3] == "x"
38    }
39    fn is_write(&self) -> bool {
40        &self.flags[1..2] == "w"
41    }
42    fn is_read(&self) -> bool {
43        &self.flags[0..1] == "r"
44    }
45}
46
47/// Gets a Vec of [`MapRange`](linux_maps/struct.MapRange.html) structs for
48/// the passed in PID. (Note that while this function is for Linux, the macOS,
49/// Windows, and FreeBSD variants have the same interface)
50pub fn get_process_maps(pid: Pid) -> std::io::Result<Vec<MapRange>> {
51    // Parses /proc/PID/maps into a Vec<MapRange>
52    let maps_file = format!("/proc/{}/maps", pid);
53    let mut file = File::open(maps_file)?;
54
55    // Check that the file is not too big
56    let metadata = file.metadata()?;
57    if metadata.len() > 0x10000000 {
58        return Err(std::io::Error::from_raw_os_error(libc::EFBIG));
59    }
60
61    let mut contents = String::new();
62    file.read_to_string(&mut contents)?;
63    parse_proc_maps(&contents)
64}
65
66fn parse_proc_maps(contents: &str) -> std::io::Result<Vec<MapRange>> {
67    let mut vec: Vec<MapRange> = Vec::new();
68    for line in contents.split("\n") {
69        let mut split = line.split_whitespace();
70        let range = match split.next() {
71            None => break,
72            Some(s) => s,
73        };
74
75        let mut range_split = range.split("-");
76        let range_start = match range_split.next() {
77            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
78            Some(s) => match usize::from_str_radix(s, 16) {
79                Err(_) => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
80                Ok(i) => i,
81            },
82        };
83        let range_end = match range_split.next() {
84            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
85            Some(s) => match usize::from_str_radix(s, 16) {
86                Err(_) => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
87                Ok(i) => i,
88            },
89        };
90        if range_split.next().is_some() || range_start >= range_end {
91            return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
92        }
93
94        let flags = match split.next() {
95            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
96            Some(s) if s.len() < 3 => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
97            Some(s) => s.to_string(),
98        };
99        let offset = match split.next() {
100            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
101            Some(s) => match usize::from_str_radix(s, 16) {
102                Err(_) => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
103                // mmap: offset must be a multiple of the page size as returned by sysconf(_SC_PAGE_SIZE).
104                Ok(i) if i & 0xfff != 0 => {
105                    return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
106                }
107                Ok(i) => i,
108            },
109        };
110        let dev = match split.next() {
111            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
112            Some(s) => s.to_string(),
113        };
114        let inode = match split.next() {
115            None => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
116            Some(s) => match usize::from_str_radix(s, 10) {
117                Err(_) => return Err(std::io::Error::from_raw_os_error(libc::EINVAL)),
118                Ok(i) => i,
119            },
120        };
121        let pathname = match Some(split.collect::<Vec<&str>>().join(" ")).filter(|x| !x.is_empty())
122        {
123            Some(s) => Some(PathBuf::from(s)),
124            None => None,
125        };
126
127        vec.push(MapRange {
128            range_start,
129            range_end,
130            offset,
131            dev,
132            flags,
133            inode,
134            pathname,
135        });
136    }
137    Ok(vec)
138}
139
140#[test]
141fn test_parse_maps() {
142    let contents = include_str!("../ci/testdata/map.txt");
143    let vec = parse_proc_maps(contents).unwrap();
144    let expected = vec![
145        MapRange {
146            range_start: 0x00400000,
147            range_end: 0x00507000,
148            offset: 0,
149            dev: "00:14".to_string(),
150            flags: "r-xp".to_string(),
151            inode: 205736,
152            pathname: Some(PathBuf::from("/usr/bin/fish")),
153        },
154        MapRange {
155            range_start: 0x00708000,
156            range_end: 0x0070a000,
157            offset: 0,
158            dev: "00:00".to_string(),
159            flags: "rw-p".to_string(),
160            inode: 0,
161            pathname: None,
162        },
163        MapRange {
164            range_start: 0x0178c000,
165            range_end: 0x01849000,
166            offset: 0,
167            dev: "00:00".to_string(),
168            flags: "rw-p".to_string(),
169            inode: 0,
170            pathname: Some(PathBuf::from("[heap]")),
171        },
172        MapRange {
173            range_start: 0x7f438050,
174            range_end: 0x7f438060,
175            offset: 0,
176            dev: "fd:01".to_string(),
177            flags: "r--p".to_string(),
178            inode: 59034409,
179            pathname: Some(PathBuf::from(
180                "/usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.4200.6 (deleted)",
181            )),
182        },
183    ];
184    assert_eq!(vec, expected);
185
186    // Also check that maps_contain_addr works as expected
187    assert_eq!(super::maps_contain_addr(0x00400000, &vec), true);
188    assert_eq!(super::maps_contain_addr(0x00300000, &vec), false);
189}
190
191#[test]
192fn test_contains_addr_range() {
193    let vec = vec![
194        MapRange {
195            range_start: 0x00400000,
196            range_end: 0x00500000,
197            offset: 0,
198            dev: "00:14".to_string(),
199            flags: "r-xp".to_string(),
200            inode: 205736,
201            pathname: Some(PathBuf::from("/usr/bin/fish")),
202        },
203        MapRange {
204            range_start: 0x00600000,
205            range_end: 0x00700000,
206            offset: 0,
207            dev: "00:14".to_string(),
208            flags: "r--p".to_string(),
209            inode: 205736,
210            pathname: Some(PathBuf::from("/usr/bin/fish")),
211        },
212        MapRange {
213            range_start: 0x00700000,
214            range_end: 0x00800000,
215            offset: 0,
216            dev: "00:14".to_string(),
217            flags: "r--p".to_string(),
218            inode: 205736,
219            pathname: Some(PathBuf::from("/usr/bin/fish")),
220        },
221    ];
222
223    assert_eq!(super::maps_contain_addr_range(0x00400000, 0x1, &vec), true);
224    assert_eq!(
225        super::maps_contain_addr_range(0x00400000, 0x100000, &vec),
226        true
227    );
228    assert_eq!(
229        super::maps_contain_addr_range(0x00500000 - 1, 1, &vec),
230        true
231    );
232    assert_eq!(
233        super::maps_contain_addr_range(0x00600000, 0x100001, &vec),
234        true
235    );
236    assert_eq!(
237        super::maps_contain_addr_range(0x00600000, 0x200000, &vec),
238        true
239    );
240
241    assert_eq!(
242        super::maps_contain_addr_range(0x00400000, 0x100001, &vec),
243        false
244    );
245    assert_eq!(
246        super::maps_contain_addr_range(0x00400000, usize::MAX, &vec),
247        false
248    );
249    assert_eq!(super::maps_contain_addr_range(0x00400000, 0, &vec), false);
250    assert_eq!(
251        super::maps_contain_addr_range(0x00400000, 0x00200000, &vec),
252        false
253    );
254    assert_eq!(
255        super::maps_contain_addr_range(0x00400000, 0x00200001, &vec),
256        false
257    );
258}