backtrace/symbolize/gimli/
parse_running_mmaps_unix.rs

1// Note: This file is only currently used on targets that call out to the code
2// in `mod libs_dl_iterate_phdr` (e.g. linux, freebsd, ...); it may be more
3// general purpose, but it hasn't been tested elsewhere.
4
5use super::mystd::fs::File;
6use super::mystd::io::Read;
7use super::mystd::str::FromStr;
8use super::{OsString, String, Vec};
9
10#[derive(PartialEq, Eq, Debug)]
11pub(super) struct MapsEntry {
12    /// start (inclusive) and limit (exclusive) of address range.
13    address: (usize, usize),
14    /// The perms field are the permissions for the entry
15    ///
16    /// r = read
17    /// w = write
18    /// x = execute
19    /// s = shared
20    /// p = private (copy on write)
21    perms: [char; 4],
22    /// Offset into the file (or "whatever").
23    offset: usize,
24    /// device (major, minor)
25    dev: (usize, usize),
26    /// inode on the device. 0 indicates that no inode is associated with the memory region (e.g. uninitalized data aka BSS).
27    inode: usize,
28    /// Usually the file backing the mapping.
29    ///
30    /// Note: The man page for proc includes a note about "coordination" by
31    /// using readelf to see the Offset field in ELF program headers. pnkfelix
32    /// is not yet sure if that is intended to be a comment on pathname, or what
33    /// form/purpose such coordination is meant to have.
34    ///
35    /// There are also some pseudo-paths:
36    /// "[stack]": The initial process's (aka main thread's) stack.
37    /// "[stack:<tid>]": a specific thread's stack. (This was only present for a limited range of Linux verisons; it was determined to be too expensive to provide.)
38    /// "[vdso]": Virtual dynamically linked shared object
39    /// "[heap]": The process's heap
40    ///
41    /// The pathname can be blank, which means it is an anonymous mapping
42    /// obtained via mmap.
43    ///
44    /// Newlines in pathname are replaced with an octal escape sequence.
45    ///
46    /// The pathname may have "(deleted)" appended onto it if the file-backed
47    /// path has been deleted.
48    ///
49    /// Note that modifications like the latter two indicated above imply that
50    /// in general the pathname may be ambiguous. (I.e. you cannot tell if the
51    /// denoted filename actually ended with the text "(deleted)", or if that
52    /// was added by the maps rendering.
53    pathname: OsString,
54}
55
56pub(super) fn parse_maps() -> Result<Vec<MapsEntry>, &'static str> {
57    let mut v = Vec::new();
58    let mut proc_self_maps =
59        File::open("/proc/self/maps").map_err(|_| "Couldn't open /proc/self/maps")?;
60    let mut buf = String::new();
61    let _bytes_read = proc_self_maps
62        .read_to_string(&mut buf)
63        .map_err(|_| "Couldn't read /proc/self/maps")?;
64    for line in buf.lines() {
65        v.push(line.parse()?);
66    }
67
68    Ok(v)
69}
70
71impl MapsEntry {
72    pub(super) fn pathname(&self) -> &OsString {
73        &self.pathname
74    }
75
76    pub(super) fn ip_matches(&self, ip: usize) -> bool {
77        self.address.0 <= ip && ip < self.address.1
78    }
79}
80
81impl FromStr for MapsEntry {
82    type Err = &'static str;
83
84    // Format: address perms offset dev inode pathname
85    // e.g.: "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]"
86    // e.g.: "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
87    // e.g.: "35b1a21000-35b1a22000 rw-p 00000000 00:00 0"
88    //
89    // Note that paths may contain spaces, so we can't use `str::split` for parsing (until
90    // Split::remainder is stabilized #77998).
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        let (range_str, s) = s.trim_start().split_once(' ').unwrap_or((s, ""));
93        if range_str.is_empty() {
94            return Err("Couldn't find address");
95        }
96
97        let (perms_str, s) = s.trim_start().split_once(' ').unwrap_or((s, ""));
98        if perms_str.is_empty() {
99            return Err("Couldn't find permissions");
100        }
101
102        let (offset_str, s) = s.trim_start().split_once(' ').unwrap_or((s, ""));
103        if offset_str.is_empty() {
104            return Err("Couldn't find offset");
105        }
106
107        let (dev_str, s) = s.trim_start().split_once(' ').unwrap_or((s, ""));
108        if dev_str.is_empty() {
109            return Err("Couldn't find dev");
110        }
111
112        let (inode_str, s) = s.trim_start().split_once(' ').unwrap_or((s, ""));
113        if inode_str.is_empty() {
114            return Err("Couldn't find inode");
115        }
116
117        // Pathname may be omitted in which case it will be empty
118        let pathname_str = s.trim_start();
119
120        let hex = |s| usize::from_str_radix(s, 16).map_err(|_| "Couldn't parse hex number");
121        let address = if let Some((start, limit)) = range_str.split_once('-') {
122            (hex(start)?, hex(limit)?)
123        } else {
124            return Err("Couldn't parse address range");
125        };
126        let perms: [char; 4] = {
127            let mut chars = perms_str.chars();
128            let mut c = || chars.next().ok_or("insufficient perms");
129            let perms = [c()?, c()?, c()?, c()?];
130            if chars.next().is_some() {
131                return Err("too many perms");
132            }
133            perms
134        };
135        let offset = hex(offset_str)?;
136        let dev = if let Some((major, minor)) = dev_str.split_once(':') {
137            (hex(major)?, hex(minor)?)
138        } else {
139            return Err("Couldn't parse dev");
140        };
141        let inode = hex(inode_str)?;
142        let pathname = pathname_str.into();
143
144        Ok(MapsEntry {
145            address,
146            perms,
147            offset,
148            dev,
149            inode,
150            pathname,
151        })
152    }
153}
154
155// Make sure we can parse 64-bit sample output if we're on a 64-bit target.
156#[cfg(target_pointer_width = "64")]
157#[test]
158fn check_maps_entry_parsing_64bit() {
159    assert_eq!(
160        "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  \
161                [vsyscall]"
162            .parse::<MapsEntry>()
163            .unwrap(),
164        MapsEntry {
165            address: (0xffffffffff600000, 0xffffffffff601000),
166            perms: ['-', '-', 'x', 'p'],
167            offset: 0x00000000,
168            dev: (0x00, 0x00),
169            inode: 0x0,
170            pathname: "[vsyscall]".into(),
171        }
172    );
173
174    assert_eq!(
175        "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795                  \
176                /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
177            .parse::<MapsEntry>()
178            .unwrap(),
179        MapsEntry {
180            address: (0x7f5985f46000, 0x7f5985f48000),
181            perms: ['r', 'w', '-', 'p'],
182            offset: 0x00039000,
183            dev: (0x103, 0x06),
184            inode: 0x76021795,
185            pathname: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2".into(),
186        }
187    );
188    assert_eq!(
189        "35b1a21000-35b1a22000 rw-p 00000000 00:00 0"
190            .parse::<MapsEntry>()
191            .unwrap(),
192        MapsEntry {
193            address: (0x35b1a21000, 0x35b1a22000),
194            perms: ['r', 'w', '-', 'p'],
195            offset: 0x00000000,
196            dev: (0x00, 0x00),
197            inode: 0x0,
198            pathname: Default::default(),
199        }
200    );
201}
202
203// (This output was taken from a 32-bit machine, but will work on any target)
204#[test]
205fn check_maps_entry_parsing_32bit() {
206    /* Example snippet of output:
207    08056000-08077000 rw-p 00000000 00:00 0          [heap]
208    b7c79000-b7e02000 r--p 00000000 08:01 60662705   /usr/lib/locale/locale-archive
209    b7e02000-b7e03000 rw-p 00000000 00:00 0
210        */
211    assert_eq!(
212        "08056000-08077000 rw-p 00000000 00:00 0          \
213                [heap]"
214            .parse::<MapsEntry>()
215            .unwrap(),
216        MapsEntry {
217            address: (0x08056000, 0x08077000),
218            perms: ['r', 'w', '-', 'p'],
219            offset: 0x00000000,
220            dev: (0x00, 0x00),
221            inode: 0x0,
222            pathname: "[heap]".into(),
223        }
224    );
225
226    assert_eq!(
227        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
228                /usr/lib/locale/locale-archive"
229            .parse::<MapsEntry>()
230            .unwrap(),
231        MapsEntry {
232            address: (0xb7c79000, 0xb7e02000),
233            perms: ['r', '-', '-', 'p'],
234            offset: 0x00000000,
235            dev: (0x08, 0x01),
236            inode: 0x60662705,
237            pathname: "/usr/lib/locale/locale-archive".into(),
238        }
239    );
240    assert_eq!(
241        "b7e02000-b7e03000 rw-p 00000000 00:00 0"
242            .parse::<MapsEntry>()
243            .unwrap(),
244        MapsEntry {
245            address: (0xb7e02000, 0xb7e03000),
246            perms: ['r', 'w', '-', 'p'],
247            offset: 0x00000000,
248            dev: (0x00, 0x00),
249            inode: 0x0,
250            pathname: Default::default(),
251        }
252    );
253    assert_eq!(
254        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
255                /executable/path/with some spaces"
256            .parse::<MapsEntry>()
257            .unwrap(),
258        MapsEntry {
259            address: (0xb7c79000, 0xb7e02000),
260            perms: ['r', '-', '-', 'p'],
261            offset: 0x00000000,
262            dev: (0x08, 0x01),
263            inode: 0x60662705,
264            pathname: "/executable/path/with some spaces".into(),
265        }
266    );
267    assert_eq!(
268        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
269                /executable/path/with  multiple-continuous    spaces  "
270            .parse::<MapsEntry>()
271            .unwrap(),
272        MapsEntry {
273            address: (0xb7c79000, 0xb7e02000),
274            perms: ['r', '-', '-', 'p'],
275            offset: 0x00000000,
276            dev: (0x08, 0x01),
277            inode: 0x60662705,
278            pathname: "/executable/path/with  multiple-continuous    spaces  ".into(),
279        }
280    );
281    assert_eq!(
282        "  b7c79000-b7e02000  r--p  00000000  08:01  60662705   \
283                /executable/path/starts-with-spaces"
284            .parse::<MapsEntry>()
285            .unwrap(),
286        MapsEntry {
287            address: (0xb7c79000, 0xb7e02000),
288            perms: ['r', '-', '-', 'p'],
289            offset: 0x00000000,
290            dev: (0x08, 0x01),
291            inode: 0x60662705,
292            pathname: "/executable/path/starts-with-spaces".into(),
293        }
294    );
295}