Skip to main content

procmaps/
lib.rs

1#[macro_use]
2extern crate nom;
3
4use std::{error, fmt, result};
5use std::io::Read;
6use std::ops::{Deref, DerefMut};
7use libc::pid_t;
8use std::fs::File;
9use std::path::PathBuf;
10
11
12pub type Result<T> = result::Result<T, Error>;
13
14#[derive(Debug)]
15pub enum Error {
16    InvalidInput,
17    IoError,
18}
19
20impl fmt::Display for Error {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match *self {
23            Error::InvalidInput => write!(f, "Invalid input"),
24            Error::IoError => write!(f, "IO Error"),
25        }
26    }
27}
28
29impl error::Error for Error {
30    fn description(&self) -> &str {
31        match *self {
32            Error::InvalidInput => "Incorrect input data for memory mapping",
33            Error::IoError => "I/O error",
34        }
35    }
36
37    fn cause(&self) -> Option<&dyn error::Error> {
38        None
39    }
40}
41
42impl From<std::io::Error> for Error {
43    fn from(_: std::io::Error) -> Error {
44        Error::IoError
45    }
46}
47
48impl <'a>From<Error> for nom::Err<&'a str> {
49    fn from(_: Error) -> nom::Err<&'a str> {
50        nom::Err::Incomplete(nom::Needed::Unknown)
51    }
52}
53
54impl <T>From<nom::Err<T>> for Error {
55    fn from(_: nom::Err<T>) -> Error {
56        Error::InvalidInput
57    }
58}
59
60/// Represents the privacy of a mapping.
61#[derive(PartialEq, Debug)]
62pub enum Privacy {
63    /// This mapping is shared
64    Shared,
65    /// This mapping is private (copy on write)
66    Private,
67}
68
69/// Represents the permissions of for a memory mapping.
70#[derive(Debug)]
71pub struct Permissions {
72    pub readable: bool,
73    pub writable: bool,
74    pub executable: bool,
75    pub privacy: Privacy,
76}
77
78impl Permissions {
79    fn from_str(input: &str) -> Result<Self> {
80        if input.len() != 4 {
81            return Err(Error::InvalidInput)
82        }
83        let input = input.as_bytes();
84        let readable = input[0] == b'r';
85        let writable = input[1] == b'w';
86        let executable = input[2] == b'x';
87
88        let privacy = match input[3] {
89            b'p' => Privacy::Private,
90            b's' => Privacy::Shared,
91            _e => return Err(Error::InvalidInput),
92        };
93
94        Ok(Permissions {
95            readable: readable,
96            writable: writable,
97            executable: executable,
98            privacy: privacy,
99        })
100    }
101}
102
103/// This enum represents the pathname field of a given process.
104/// Usually this is a file that backs up a given mapping.
105#[derive(PartialEq, Debug)]
106pub enum Path {
107    /// A file backs up this mapping
108    MappedFile(String),
109    /// This mapping is the main thread stack
110    Stack,
111    /// This mapping is a thread's stack
112    ThreadStack(usize),
113    /// This mapping is the virtual dynamically linked shared object
114    Vdso,
115    /// This mapping is the process's heap
116    Heap,
117    /// This mapping holds variables updated by the kernel
118    Vvar,
119    /// This region is the vsyscall mapping
120    Vsyscall,
121}
122
123impl From<&str> for Path {
124    fn from(input: &str) -> Self {
125        // TODO: add ThreadStack type
126        match input {
127            "[heap]" => Path::Heap,
128            "[stack]" => Path::Stack,
129            "[vdso]" => Path::Vdso,
130            "[vvar]" => Path::Vvar,
131            "[vsyscall]" => Path::Vvar,
132            s => Path::MappedFile(s.to_string())
133        }
134    }
135}
136
137
138/// Holds data for a given memory mapped region.
139/// [For more information.](http://man7.org/linux/man-pages/man5/proc.5.html)
140#[derive(Debug)]
141pub struct Map {
142    /// Base of mapped region in process
143    pub base: usize,
144    /// Ceiling of mapped region in process
145    pub ceiling: usize,
146    /// Access permissions of memory region
147    pub perms: Permissions,
148    /// If this mapping is backed by a file, this is the offset into the file.
149    pub offset: usize,
150    /// Major device number
151    pub dev_major: usize,
152    /// Minor device number
153    pub dev_minor: usize,
154    /// The inode on the above device
155    pub inode: usize,
156    /// If there is no pathname, this mapping was obtained via mmap(2)
157    pub pathname: Path,
158}
159
160impl Map {
161    /// Calculate the size of the mapped region
162    pub fn size_of_mapping(&self) -> usize {
163        self.ceiling - self.base
164    }
165
166    fn from_str(input: &str) -> Result<Map> {
167        let res = parse_map(input);
168
169        match res {
170            Ok(val) => Ok(val.1),
171            Err(_e) => Err(Error::InvalidInput),
172        }
173    }
174}
175
176named!(parse_map<&str, Map>,
177    do_parse!(
178        base: map_res!(take_until!("-"), |b| usize::from_str_radix(b, 16))      >>
179        take!(1)                                                                >>
180        ceiling: map_res!(take_until!(" "), |b| usize::from_str_radix(b, 16))   >>
181        take!(1)                                                                >>
182        perms: map_res!(take_until!(" "), |b| Permissions::from_str(b))         >>
183        take!(1)                                                                >>
184        offset: map_res!(take_until!(" "), |b| usize::from_str_radix(b, 16))    >>
185        take!(1)                                                                >>
186        dev_major: map_res!(take_until!(":"), |b| usize::from_str_radix(b, 16)) >>
187        take!(1)                                                                >>
188        dev_minor: map_res!(take_until!(" "), |b| usize::from_str_radix(b, 16)) >>
189        take!(1)                                                                >>
190        inode: map_res!(take_until!(" "), |b| usize::from_str_radix(b, 16))     >>
191        take!(1)                                                                >>
192        pathname: opt!(take_until!("\n"))                                       >>
193        (Map {
194            base: base,
195            ceiling: ceiling,
196            perms: perms,
197            offset: offset,
198            dev_major: dev_major,
199            dev_minor: dev_minor,
200            inode: inode,
201            pathname: pathname.unwrap().trim().into(),
202        })
203    )
204);
205
206
207/// A collection of memory mapped regions.
208#[derive(Debug)]
209pub struct Mappings(Vec<Map>);
210
211impl Mappings {
212    /// Returns mappings for a given pid
213    pub fn from_pid(pid: pid_t) -> Result<Mappings> {
214        let path = format!("/proc/{}/maps", pid);
215        let mut file = File::open(path)?;
216        let mut input = String::new();
217        file.read_to_string(&mut input)?;
218
219        let mut res: Vec<Map> = Vec::new();
220        let mut iter: Vec<&str> = input.split("\n").collect();
221        iter.pop();
222        for s in iter {
223            let map = Map::from_str(&format!("{}\n", &s))?;
224            res.push(map);
225        }
226
227        Ok(Mappings(res))
228    }
229
230    pub fn from_path(path: &mut PathBuf) -> Result<Mappings> {
231        path.push("maps");
232        let mut file = File::open(path)?;
233        let mut input = String::new();
234        file.read_to_string(&mut input)?;
235
236        let mut res: Vec<Map> = Vec::new();
237        let mut iter: Vec<&str> = input.split("\n").collect();
238        iter.pop();
239        for s in iter {
240            let map = Map::from_str(&format!("{}\n", &s))?;
241            res.push(map);
242        }
243
244        Ok(Mappings(res))
245    }
246}
247
248impl Deref for Mappings {
249    type Target = Vec<Map>;
250
251    fn deref(&self) -> &Self::Target {
252        &self.0
253    }
254}
255
256impl DerefMut for Mappings {
257    fn deref_mut(&mut self) -> &mut Vec<Map> { &mut self.0 }
258}
259
260#[cfg(test)]
261mod tests {
262    use crate::*;
263
264    //TODO: more tests ie: check none pathname
265    #[test]
266    fn test_parse_map() {
267        let input = "55e8d4153000-55e8d416f000 r-xp 00000000 08:02 9175073                    /bin/dash\n";
268        let res = parse_map(input).unwrap().1;
269        println!("{:?}", res);
270        assert_eq!(res.base, 94458478931968);
271        assert_eq!(res.ceiling, 94458479046656);
272        assert_eq!(res.offset, 0);
273        assert_eq!(res.dev_major, 8);
274        assert_eq!(res.dev_minor, 2);
275        assert_eq!(res.inode, 152522867);
276        assert_eq!(res.pathname, Path::MappedFile("/bin/dash".to_string()));
277    }
278
279    #[test]
280    fn test_map_path_types() {
281        let input = "7fffdb68b000-7fffdb6ac000 rw-p 00000000 00:00 0                          [stack]\n";
282        let res = Map::from_str(input).unwrap();
283        assert_eq!(res.pathname, Path::Stack);
284
285        let input = "7fffdb7a7000-7fffdb7aa000 r--p 00000000 00:00 0                          [vvar]\n";
286        let res = Map::from_str(input).unwrap();
287        assert_eq!(res.pathname, Path::Vvar);
288
289        let input = "7fffdb7aa000-7fffdb7ac000 r-xp 00000000 00:00 0                          [vdso]\n";
290        let res = Map::from_str(input).unwrap();
291        assert_eq!(res.pathname, Path::Vdso);
292    }
293
294    #[test]
295    fn test_map_from_str_invalid_inputs() {
296        // Invalid permissions
297        let input = "7fffdb68b000-7fffdb6ac000 rw- 00000000 00:00 0                          [stack]\n";
298        let res = Map::from_str(input);
299        assert!(res.is_err());
300
301        // Invalid device
302        let input = "7fffdb7a7000-7fffdb7aa000 r--p 00000000 0000 0                          [vvar]\n";
303        let res = Map::from_str(input);
304        assert!(res.is_err());
305
306        // Invalid address format
307        let input = "7fffdb7aa0007fffdb7ac000 r-xp 00000000 00:00 0                          [vdso]\n";
308        let res = Map::from_str(input);
309        assert!(res.is_err());
310    }
311
312    #[test]
313    fn test_size_of_mapping() {
314        let input = "55e8d4153000-55e8d416f000 r-xp 00000000 08:02 9175073                    /bin/dash\n";
315        let res = Map::from_str(input).unwrap();
316        assert_eq!(res.size_of_mapping(), 114688usize);
317    }
318
319    #[test]
320    fn test_map_perms() {
321        let input = "55e8d4153000-55e8d416f000 r-xp 00000000 08:02 9175073                    /bin/dash\n";
322        let res = Map::from_str(input).unwrap();
323        println!("{:?}", res);
324        assert!(res.perms.readable);
325        assert!(!res.perms.writable);
326        assert!(res.perms.executable);
327        assert_eq!(res.perms.privacy, Privacy::Private);
328    }
329
330    /*
331    #[bench]
332    fn bench_map_from_str(b: &mut Bencher) {
333        let input = "55e8d4153000-55e8d416f000 r-xp 00000000 08:02 9175073                    /bin/dash\n";
334
335        b.iter(||
336            Map::from_str(input).unwrap()
337        )
338    }
339    */
340
341    #[test]
342    fn test_map_from_str() {
343        let mut file = File::open("tests/example.txt").unwrap();
344        let mut input = String::new();
345        file.read_to_string(&mut input).unwrap();
346
347        let mut iter: Vec<&str> = input.split("\n").collect();
348        iter.pop();
349        for s in iter {
350            let map = Map::from_str(&format!("{}\n", &s)).unwrap();
351            println!("{:?}", map);
352        }
353    }
354
355    #[test]
356    fn test_maps() {
357        use std::process::id;
358        let m = Mappings::from_pid(id() as pid_t);
359        assert!(m.is_ok());
360        println!("{:?}", m);
361    }
362}