Skip to main content

std/backtrace/src/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    /// Offset into the file (or "whatever").
17    offset: u64,
18    /// Usually the file backing the mapping.
19    ///
20    /// Note: The man page for proc includes a note about "coordination" by
21    /// using readelf to see the Offset field in ELF program headers. pnkfelix
22    /// is not yet sure if that is intended to be a comment on pathname, or what
23    /// form/purpose such coordination is meant to have.
24    ///
25    /// There are also some pseudo-paths:
26    /// "[stack]": The initial process's (aka main thread's) stack.
27    /// "[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.)
28    /// "[vdso]": Virtual dynamically linked shared object
29    /// "[heap]": The process's heap
30    ///
31    /// The pathname can be blank, which means it is an anonymous mapping
32    /// obtained via mmap.
33    ///
34    /// Newlines in pathname are replaced with an octal escape sequence.
35    ///
36    /// The pathname may have "(deleted)" appended onto it if the file-backed
37    /// path has been deleted.
38    ///
39    /// Note that modifications like the latter two indicated above imply that
40    /// in general the pathname may be ambiguous. (I.e. you cannot tell if the
41    /// denoted filename actually ended with the text "(deleted)", or if that
42    /// was added by the maps rendering.
43    pathname: OsString,
44}
45
46pub(super) fn parse_maps() -> Result<Vec<MapsEntry>, &'static str> {
47    let mut proc_self_maps =
48        File::open("/proc/self/maps").map_err(|_| "Couldn't open /proc/self/maps")?;
49    let mut buf = String::new();
50    let _bytes_read = proc_self_maps
51        .read_to_string(&mut buf)
52        .map_err(|_| "Couldn't read /proc/self/maps")?;
53
54    let mut v = Vec::new();
55    let mut buf = buf.as_str();
56    while let Some(match_idx) = buf.bytes().position(|b| b == b'\n') {
57        // Unsafe is unfortunately necessary to get the bounds check removed (for code size).
58
59        // SAFETY: match_idx is the position of the newline, so it must be valid.
60        let line = unsafe { buf.get_unchecked(..match_idx) };
61
62        v.push(line.parse()?);
63
64        // SAFETY: match_idx is the position of the newline, so the byte after it must be valid.
65        buf = unsafe { buf.get_unchecked((match_idx + 1)..) };
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    #[cfg(target_os = "android")]
81    pub(super) fn offset(&self) -> u64 {
82        self.offset
83    }
84}
85
86impl FromStr for MapsEntry {
87    type Err = &'static str;
88
89    // Format: address perms offset dev inode pathname
90    // e.g.: "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]"
91    // e.g.: "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795                  /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
92    // e.g.: "35b1a21000-35b1a22000 rw-p 00000000 00:00 0"
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        // While there are nicer standard library APIs available for this, we aim for minimal code size.
95
96        let mut state = s;
97
98        fn parse_start<'a>(state: &mut &'a str) -> &'a str {
99            // Unsafe is unfortunately necessary to get the bounds check removed (for code size).
100
101            let start_idx = state.bytes().position(|b| b != b' ');
102            if let Some(start_idx) = start_idx {
103                // SAFETY: It comes from position, so it's in bounds.
104                //         It must be on a UTF-8 boundary as it's the first byte that isn't ' '.
105                *state = unsafe { state.get_unchecked(start_idx..) };
106            }
107            let match_idx = state.bytes().position(|b| b == b' ');
108            match match_idx {
109                None => {
110                    let result = *state;
111                    *state = "";
112                    result
113                }
114                Some(match_idx) => {
115                    // SAFETY: match_index comes from .bytes().position() of an ASCII character,
116                    //         so it's both in bounds and a UTF-8 boundary
117                    let result = unsafe { state.get_unchecked(..match_idx) };
118                    // SAFETY: Since match_idx is the ' ', there must be at least the end after it.
119                    *state = unsafe { state.get_unchecked((match_idx + 1)..) };
120                    result
121                }
122            }
123        }
124
125        fn error(msg: &str) -> &str {
126            if cfg!(debug_assertions) {
127                msg
128            } else {
129                "invalid map entry"
130            }
131        }
132
133        let range_str = parse_start(&mut state);
134        if range_str.is_empty() {
135            return Err(error("Couldn't find address"));
136        }
137
138        let perms_str = parse_start(&mut state);
139        if perms_str.is_empty() {
140            return Err(error("Couldn't find permissions"));
141        }
142
143        let offset_str = parse_start(&mut state);
144        if offset_str.is_empty() {
145            return Err(error("Couldn't find offset"));
146        }
147
148        let dev_str = parse_start(&mut state);
149        if dev_str.is_empty() {
150            return Err(error("Couldn't find dev"));
151        }
152
153        let inode_str = parse_start(&mut state);
154        if inode_str.is_empty() {
155            return Err(error("Couldn't find inode"));
156        }
157
158        // Pathname may be omitted in which case it will be empty
159        let pathname_str = state.trim_ascii_start();
160
161        let hex = |s| usize::from_str_radix(s, 16).map_err(|_| error("Couldn't parse hex number"));
162        let hex64 = |s| u64::from_str_radix(s, 16).map_err(|_| error("Couldn't parse hex number"));
163
164        let address = if let Some((start, limit)) = range_str.split_once('-') {
165            (hex(start)?, hex(limit)?)
166        } else {
167            return Err(error("Couldn't parse address range"));
168        };
169        let offset = hex64(offset_str)?;
170        let pathname = pathname_str.into();
171
172        Ok(MapsEntry {
173            address,
174            offset,
175            pathname,
176        })
177    }
178}
179
180// Make sure we can parse 64-bit sample output if we're on a 64-bit target.
181#[cfg(target_pointer_width = "64")]
182#[test]
183fn check_maps_entry_parsing_64bit() {
184    assert_eq!(
185        "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  \
186                [vsyscall]"
187            .parse::<MapsEntry>()
188            .unwrap(),
189        MapsEntry {
190            address: (0xffffffffff600000, 0xffffffffff601000),
191            offset: 0x00000000,
192            pathname: "[vsyscall]".into(),
193        }
194    );
195
196    assert_eq!(
197        "7f5985f46000-7f5985f48000 rw-p 00039000 103:06 76021795                  \
198                /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"
199            .parse::<MapsEntry>()
200            .unwrap(),
201        MapsEntry {
202            address: (0x7f5985f46000, 0x7f5985f48000),
203            offset: 0x00039000,
204            pathname: "/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2".into(),
205        }
206    );
207    assert_eq!(
208        "35b1a21000-35b1a22000 rw-p 00000000 00:00 0"
209            .parse::<MapsEntry>()
210            .unwrap(),
211        MapsEntry {
212            address: (0x35b1a21000, 0x35b1a22000),
213            offset: 0x00000000,
214            pathname: Default::default(),
215        }
216    );
217}
218
219// (This output was taken from a 32-bit machine, but will work on any target)
220#[test]
221fn check_maps_entry_parsing_32bit() {
222    /* Example snippet of output:
223    08056000-08077000 rw-p 00000000 00:00 0          [heap]
224    b7c79000-b7e02000 r--p 00000000 08:01 60662705   /usr/lib/locale/locale-archive
225    b7e02000-b7e03000 rw-p 00000000 00:00 0
226        */
227    assert_eq!(
228        "08056000-08077000 rw-p 00000000 00:00 0          \
229                [heap]"
230            .parse::<MapsEntry>()
231            .unwrap(),
232        MapsEntry {
233            address: (0x08056000, 0x08077000),
234            offset: 0x00000000,
235            pathname: "[heap]".into(),
236        }
237    );
238
239    assert_eq!(
240        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
241                /usr/lib/locale/locale-archive"
242            .parse::<MapsEntry>()
243            .unwrap(),
244        MapsEntry {
245            address: (0xb7c79000, 0xb7e02000),
246            offset: 0x00000000,
247            pathname: "/usr/lib/locale/locale-archive".into(),
248        }
249    );
250    assert_eq!(
251        "b7e02000-b7e03000 rw-p 00000000 00:00 0"
252            .parse::<MapsEntry>()
253            .unwrap(),
254        MapsEntry {
255            address: (0xb7e02000, 0xb7e03000),
256            offset: 0x00000000,
257            pathname: Default::default(),
258        }
259    );
260    assert_eq!(
261        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
262                /executable/path/with some spaces"
263            .parse::<MapsEntry>()
264            .unwrap(),
265        MapsEntry {
266            address: (0xb7c79000, 0xb7e02000),
267            offset: 0x00000000,
268            pathname: "/executable/path/with some spaces".into(),
269        }
270    );
271    assert_eq!(
272        "b7c79000-b7e02000 r--p 00000000 08:01 60662705   \
273                /executable/path/with  multiple-continuous    spaces  "
274            .parse::<MapsEntry>()
275            .unwrap(),
276        MapsEntry {
277            address: (0xb7c79000, 0xb7e02000),
278            offset: 0x00000000,
279            pathname: "/executable/path/with  multiple-continuous    spaces  ".into(),
280        }
281    );
282    assert_eq!(
283        "  b7c79000-b7e02000  r--p  00000000  08:01  60662705   \
284                /executable/path/starts-with-spaces"
285            .parse::<MapsEntry>()
286            .unwrap(),
287        MapsEntry {
288            address: (0xb7c79000, 0xb7e02000),
289            offset: 0x00000000,
290            pathname: "/executable/path/starts-with-spaces".into(),
291        }
292    );
293}