vm_info/
mapped_region.rs

1use std::io::BufRead;
2use std::{fmt, fs, io, num, path};
3
4use super::regex;
5
6use super::ProcessId;
7
8/// Permissions for a mapped region.
9#[derive(Clone)]
10pub struct Permissions {
11    read: bool,
12    write: bool,
13    execute: bool,
14    shared: bool,
15}
16
17impl Permissions {
18    pub fn read(&self) -> bool {
19        self.read
20    }
21    pub fn write(&self) -> bool {
22        self.write
23    }
24    pub fn execute(&self) -> bool {
25        self.execute
26    }
27    /// True iff `private()` is false.
28    pub fn shared(&self) -> bool {
29        self.shared
30    }
31    /// True iff `shared()` is false.
32    pub fn private(&self) -> bool {
33        !self.shared
34    }
35}
36
37impl fmt::Debug for Permissions {
38    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39        let read = if self.read { 'r' } else { '-' };
40        let write = if self.write { 'w' } else { '-' };
41        let execute = if self.execute { 'x' } else { '-' };
42        let shared = if self.shared { 's' } else { 'p' };
43        write!(f, "{}{}{}{}", read, write, execute, shared)
44    }
45}
46
47/// Metadata for a mapped virtual memory region. See the `proc(5)` manpage.
48#[derive(Clone, Debug)]
49pub struct MemoryRegion {
50    /// Start of the address in the process's address space.
51    pub start_address: usize,
52    pub end_address: usize,
53    pub permissions: Permissions,
54    /// Offset into the mapped file
55    pub offset: usize,
56    /// Device major number
57    // 12 bits currently
58    pub dev_major: u32,
59    /// Device minor number
60    // 20 bits
61    pub dev_minor: u32,
62    /// inode, if available
63    pub inode: Option<u64>,
64    /// Filename, or pseudo-path (e.g. `[stack]`), or `None` for anonymous mappings
65    pub pathname: Option<String>,
66}
67
68#[derive(Debug)]
69pub enum Error {
70    ParseFailed(String),
71    IoError(io::Error),
72}
73
74impl From<io::Error> for Error {
75    fn from(e: io::Error) -> Self {
76        Error::IoError(e)
77    }
78}
79
80impl From<num::ParseIntError> for Error {
81    fn from(err: num::ParseIntError) -> Self {
82        Error::ParseFailed(format!("{:?}", err))
83    }
84}
85
86/// Iterator across VM regions.
87pub struct MemoryRegionIter<R: io::Read> {
88    reader: io::BufReader<R>,
89    buf: String,
90    finished: bool,
91}
92
93impl<R: io::Read> MemoryRegionIter<R> {
94    fn parse_buf(&mut self) -> Result<MemoryRegion, Error> {
95        lazy_static! {
96            static ref RE: regex::Regex = regex::Regex::new(
97                "^([0-9a-f]+)-([0-9a-f]+) ([a-z-]{4}) ([0-9a-f]+) ([0-9a-f]+):([0-9a-f]+) (\\d+) +(.*)$")
98                .expect("regex is valid");
99        }
100
101        let trimmed = self.buf.trim_end_matches('\n');
102        let captures = RE
103            .captures(trimmed)
104            .ok_or_else(|| Error::ParseFailed(trimmed.to_string()))?;
105        let start_address = usize::from_str_radix(&captures[1], 16)?;
106        let end_address = usize::from_str_radix(&captures[2], 16)?;
107        let p_bytes = &captures[3].as_bytes();
108        let permissions = Permissions {
109            read: p_bytes[0] == b'r',
110            write: p_bytes[1] == b'w',
111            execute: p_bytes[2] == b'x',
112            shared: p_bytes[3] == b's',
113        };
114        let offset = usize::from_str_radix(&captures[4], 16)?;
115        let dev_major = u32::from_str_radix(&captures[5], 16)?;
116        let dev_minor = u32::from_str_radix(&captures[6], 16)?;
117        let inode = u64::from_str_radix(&captures[7], 10)?;
118        let inode = if inode == 0 { None } else { Some(inode) };
119        let pathname = &captures[8];
120        let pathname = if pathname.is_empty() {
121            None
122        } else {
123            Some(String::from(pathname))
124        };
125
126        Ok(MemoryRegion {
127            start_address,
128            end_address,
129            permissions,
130            offset,
131            dev_major,
132            dev_minor,
133            inode,
134            pathname,
135        })
136    }
137}
138
139impl<R: io::Read> Iterator for MemoryRegionIter<R> {
140    type Item = Result<MemoryRegion, Error>;
141
142    fn next(&mut self) -> Option<Self::Item> {
143        if self.finished {
144            return None;
145        }
146
147        self.buf.clear();
148
149        match self.reader.read_line(&mut self.buf) {
150            Ok(bytes_read) => {
151                if bytes_read == 0 {
152                    self.finished = true;
153                    None
154                } else {
155                    Some(self.parse_buf())
156                }
157            }
158            Err(e) => {
159                // if reading hits an error, we likely will never succeed again, so we should
160                // stop iterating
161                self.finished = true;
162                Some(Err(Error::IoError(e)))
163            }
164        }
165    }
166}
167
168/// Load all mappings out of Linux procfs.
169///
170/// This may fail depending on permissions setup.
171pub fn iter_mappings(pid: ProcessId) -> io::Result<MemoryRegionIter<fs::File>> {
172    let path = path::PathBuf::from(match pid {
173        ProcessId::SelfPid => String::from("/proc/self/maps"),
174        ProcessId::Num(n) => format!("/proc/{}/maps", n),
175    });
176
177    let reader = fs::File::open(path)?;
178
179    Ok(iter_mapping_reader(reader))
180}
181
182fn iter_mapping_reader<R: io::Read>(reader: R) -> MemoryRegionIter<R> {
183    let buf_reader = io::BufReader::new(reader);
184
185    MemoryRegionIter {
186        reader: buf_reader,
187        buf: String::new(),
188        finished: false,
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    extern crate libc;
195
196    use super::*;
197    use std::path;
198
199    #[test]
200    fn sample_maps_file() {
201        let path = path::Path::new("src/test-data/obexd-map.txt");
202        let reader = fs::File::open(path).unwrap();
203
204        let mappings = iter_mapping_reader(reader)
205            .map(|r| r.unwrap())
206            .collect::<Vec<MemoryRegion>>();
207
208        assert_eq!(80, mappings.len());
209
210        let first = &mappings[0];
211        assert_eq!(94858956906496, first.start_address);
212        assert_eq!(94858957352960, first.end_address);
213        assert!(first.permissions.read());
214        assert!(!first.permissions.write());
215        assert!(first.permissions.execute());
216        assert!(!first.permissions.shared());
217        assert!(first.permissions.private());
218        assert_eq!(0, first.offset);
219        assert_eq!(8, first.dev_major);
220        assert_eq!(3, first.dev_minor);
221        assert_eq!(Some(58219319), first.inode);
222        assert_eq!(
223            "/usr/libexec/bluetooth/obexd",
224            first.pathname.as_ref().unwrap()
225        );
226
227        let anon = &mappings[12];
228        assert_eq!(0, anon.offset);
229        assert_eq!(0, anon.dev_major);
230        assert_eq!(0, anon.dev_minor);
231        assert_eq!(None, anon.inode);
232        assert_eq!(None, anon.pathname.as_ref());
233
234        // has offset
235        let libpthread = &mappings[50];
236        assert!(!libpthread.permissions.read());
237        assert!(!libpthread.permissions.write());
238        assert!(!libpthread.permissions.execute());
239        assert!(!libpthread.permissions.shared());
240        assert!(libpthread.permissions.private());
241        assert_eq!(106496, libpthread.offset);
242
243        // different permissions, has inode
244        let gconv = &mappings[71];
245        assert!(gconv.permissions.read());
246        assert!(!gconv.permissions.write());
247        assert!(!gconv.permissions.execute());
248        assert!(gconv.permissions.shared());
249        assert!(!gconv.permissions.private());
250        assert_eq!(3, gconv.dev_minor);
251        assert_eq!(Some(55705632), gconv.inode);
252
253        let last = &mappings[79];
254        assert_eq!(18446744073699065856, last.start_address);
255        assert_eq!(18446744073699069952, last.end_address);
256        assert!(last.permissions.read());
257        assert!(!last.permissions.write());
258        assert!(last.permissions.execute());
259        assert!(!last.permissions.shared());
260        assert!(last.permissions.private());
261        assert_eq!(0, last.offset);
262        assert_eq!(0, last.dev_major);
263        assert_eq!(0, last.dev_minor);
264        assert_eq!(None, last.inode);
265        assert_eq!("[vsyscall]", last.pathname.as_ref().unwrap());
266    }
267
268    #[test]
269    fn maps_file_with_hex_device_nodes() {
270        let path = path::Path::new("src/test-data/issue-1.txt");
271        let reader = fs::File::open(path).unwrap();
272        let mappings = iter_mapping_reader(reader)
273            .map(|r| r.unwrap())
274            .collect::<Vec<MemoryRegion>>();
275
276        assert_eq!(518, mappings.len());
277
278        let hex_dev_node_map = &mappings[0];
279
280        assert_eq!(0xFD, hex_dev_node_map.dev_major);
281        assert_eq!(0x00, hex_dev_node_map.dev_minor);
282    }
283
284    #[test]
285    fn read_self() {
286        // can find stack
287        assert_eq!(
288            1,
289            iter_mappings(ProcessId::SelfPid)
290                .unwrap()
291                .filter_map(|r| r.unwrap().pathname.clone())
292                .filter(|p| p == "[stack]")
293                .count()
294        )
295    }
296
297    #[test]
298    fn read_own_pid() {
299        let pid = unsafe { libc::getpid() as u32 };
300
301        // can find stack
302        assert_eq!(
303            1,
304            iter_mappings(ProcessId::Num(pid))
305                .unwrap()
306                .filter_map(|r| r.unwrap().pathname.clone())
307                .filter(|p| p == "[stack]")
308                .count()
309        )
310    }
311
312    #[test]
313    fn permissions_debug() {
314        let p1 = Permissions {
315            read: false,
316            write: false,
317            execute: false,
318            shared: false,
319        };
320
321        assert_eq!("---p", format!("{:?}", p1));
322
323        let p2 = Permissions {
324            read: true,
325            write: true,
326            execute: true,
327            shared: true,
328        };
329
330        assert_eq!("rwxs", format!("{:?}", p2));
331    }
332
333    #[test]
334    fn iteration_aborts_on_error() {
335        let path = path::Path::new("src/test-data/issue-1.txt");
336        let file_reader = fs::File::open(path).unwrap();
337        let error_reader = ErroringReader {
338            reader: file_reader,
339            bytes_until_error: 500,
340        };
341
342        // if we don't properly abort iteration on error, this will infinite loop
343        let mappings = iter_mapping_reader(error_reader)
344            .filter_map(|r| r.ok())
345            .collect::<Vec<MemoryRegion>>();
346
347        // iteration stopped
348        assert_eq!(5, mappings.len());
349    }
350
351    struct ErroringReader<R: io::Read> {
352        reader: R,
353        bytes_until_error: usize,
354    }
355
356    impl<R: io::Read> io::Read for ErroringReader<R> {
357        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
358            if self.bytes_until_error == 0 {
359                return Err(io::Error::from(io::ErrorKind::Other));
360            }
361
362            let bytes = self.reader.read(buf)?;
363
364            let remaining_bytes = self.bytes_until_error.saturating_sub(bytes);
365
366            let res = if remaining_bytes == 0 {
367                // only read the available bytes
368                Ok(self.bytes_until_error)
369            } else {
370                Ok(bytes)
371            };
372
373            self.bytes_until_error = remaining_bytes;
374
375            res
376        }
377    }
378}