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