1 use crate::auxv_reader::AuxvType;
2 use crate::errors::MapsReaderError;
3 use crate::thread_info::Pid;
4 use byteorder::{NativeEndian, ReadBytesExt};
5 use goblin::elf;
6 use memmap2::{Mmap, MmapOptions};
7 use std::convert::TryInto;
8 use std::fs::File;
9 use std::mem::size_of;
10 use std::path::PathBuf;
11 
12 pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
13 pub const DELETED_SUFFIX: &str = " (deleted)";
14 pub const RESERVED_FLAGS: &str = "---p";
15 
16 type Result<T> = std::result::Result<T, MapsReaderError>;
17 
18 #[derive(Debug, PartialEq, Clone)]
19 pub struct SystemMappingInfo {
20     pub start_address: usize,
21     pub end_address: usize,
22 }
23 
24 // One of these is produced for each mapping in the process (i.e. line in
25 // /proc/$x/maps).
26 #[derive(Debug, PartialEq, Clone)]
27 pub struct MappingInfo {
28     // On Android, relocation packing can mean that the reported start
29     // address of the mapping must be adjusted by a bias in order to
30     // compensate for the compression of the relocation section. The
31     // following two members hold (after LateInit) the adjusted mapping
32     // range. See crbug.com/606972 for more information.
33     pub start_address: usize,
34     pub size: usize,
35     // When Android relocation packing causes |start_addr| and |size| to
36     // be modified with a load bias, we need to remember the unbiased
37     // address range. The following structure holds the original mapping
38     // address range as reported by the operating system.
39     pub system_mapping_info: SystemMappingInfo,
40     pub offset: usize,    // offset into the backed file.
41     pub executable: bool, // true if the mapping has the execute bit set.
42     pub name: Option<String>,
43     // pub elf_obj: Option<elf::Elf>,
44 }
45 
46 #[derive(Debug)]
47 pub struct MappingEntry {
48     pub mapping: MappingInfo,
49     pub identifier: Vec<u8>,
50 }
51 
52 // A list of <MappingInfo, GUID>
53 pub type MappingList = Vec<MappingEntry>;
54 
55 #[derive(Debug)]
56 pub enum MappingInfoParsingResult {
57     SkipLine,
58     Success(MappingInfo),
59 }
60 
is_mapping_a_path(pathname: Option<&str>) -> bool61 fn is_mapping_a_path(pathname: Option<&str>) -> bool {
62     match pathname {
63         Some(x) => x.contains('/'),
64         None => false,
65     }
66 }
67 
68 impl MappingInfo {
parse_from_line( line: &str, linux_gate_loc: AuxvType, last_mapping: Option<&mut MappingInfo>, ) -> Result<MappingInfoParsingResult>69     pub fn parse_from_line(
70         line: &str,
71         linux_gate_loc: AuxvType,
72         last_mapping: Option<&mut MappingInfo>,
73     ) -> Result<MappingInfoParsingResult> {
74         let mut last_whitespace = false;
75 
76         // There is no `line.splitn_whitespace(6)`, so we have to do it somewhat manually
77         // Split at the first whitespace, trim of the rest.
78         let mut splits = line
79             .trim()
80             .splitn(6, |c: char| {
81                 if c.is_whitespace() {
82                     if last_whitespace {
83                         return false;
84                     }
85                     last_whitespace = true;
86                     true
87                 } else {
88                     last_whitespace = false;
89                     false
90                 }
91             })
92             .map(str::trim);
93 
94         let address = splits
95             .next()
96             .ok_or(MapsReaderError::MapEntryMalformed("address"))?;
97         let perms = splits
98             .next()
99             .ok_or(MapsReaderError::MapEntryMalformed("permissions"))?;
100         let mut offset = usize::from_str_radix(
101             splits
102                 .next()
103                 .ok_or(MapsReaderError::MapEntryMalformed("offset"))?,
104             16,
105         )?;
106         let _dev = splits
107             .next()
108             .ok_or(MapsReaderError::MapEntryMalformed("dev"))?;
109         let _inode = splits
110             .next()
111             .ok_or(MapsReaderError::MapEntryMalformed("inode"))?;
112         let mut pathname = splits.next(); // Optional
113 
114         // Due to our ugly `splitn_whitespace()` hack from above, we might have
115         // only trailing whitespaces as the name, so we it might still be "Some()"
116         if let Some(x) = pathname {
117             if x.is_empty() {
118                 pathname = None;
119             }
120         }
121 
122         let mut addresses = address.split('-');
123         let start_address = usize::from_str_radix(addresses.next().unwrap(), 16)?;
124         let end_address = usize::from_str_radix(addresses.next().unwrap(), 16)?;
125 
126         let executable = perms.contains('x');
127 
128         // Only copy name if the name is a valid path name, or if
129         // it's the VDSO image.
130         let is_path = is_mapping_a_path(pathname);
131 
132         if !is_path && linux_gate_loc != 0 && start_address == linux_gate_loc.try_into()? {
133             pathname = Some(LINUX_GATE_LIBRARY_NAME);
134             offset = 0;
135         }
136 
137         match (pathname, last_mapping) {
138             (Some(_name), Some(module)) => {
139                 // Merge adjacent mappings into one module, assuming they're a single
140                 // library mapped by the dynamic linker.
141                 if (start_address == module.start_address + module.size)
142                     && (pathname == module.name.as_deref())
143                 {
144                     module.system_mapping_info.end_address = end_address;
145                     module.size = end_address - module.start_address;
146                     module.executable |= executable;
147                     return Ok(MappingInfoParsingResult::SkipLine);
148                 }
149             }
150             (None, Some(module)) => {
151                 // Also merge mappings that result from address ranges that the
152                 // linker reserved but which a loaded library did not use. These
153                 // appear as an anonymous private mapping with no access flags set
154                 // and which directly follow an executable mapping.
155                 let module_end_address = module.start_address + module.size;
156                 if (start_address == module_end_address)
157                     && module.executable
158                     && is_mapping_a_path(module.name.as_deref())
159                     && (offset == 0 || offset == module_end_address)
160                     && perms == RESERVED_FLAGS
161                 {
162                     module.size = end_address - module.start_address;
163                     return Ok(MappingInfoParsingResult::SkipLine);
164                 }
165             }
166             _ => (),
167         }
168 
169         let name = pathname.map(ToOwned::to_owned);
170 
171         let info = MappingInfo {
172             start_address,
173             size: end_address - start_address,
174             system_mapping_info: SystemMappingInfo {
175                 start_address,
176                 end_address,
177             },
178             offset,
179             executable,
180             name,
181             // elf_obj,
182         };
183 
184         Ok(MappingInfoParsingResult::Success(info))
185     }
186 
get_mmap(name: &Option<String>, offset: usize) -> Result<Mmap>187     pub fn get_mmap(name: &Option<String>, offset: usize) -> Result<Mmap> {
188         if !MappingInfo::is_mapped_file_safe_to_open(&name) {
189             return Err(MapsReaderError::NotSafeToOpenMapping(
190                 name.clone().unwrap_or_default(),
191             ));
192         }
193 
194         // Not doing this as root_prefix is always "" at the moment
195         //   if (!dumper.GetMappingAbsolutePath(mapping, filename))
196         let filename = name.clone().unwrap_or_default();
197         let mapped_file = unsafe {
198             MmapOptions::new()
199                 .offset(offset.try_into()?) // try_into() to work for both 32 and 64 bit
200                 .map(&File::open(filename)?)?
201         };
202 
203         if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
204             return Err(MapsReaderError::MmapSanityCheckFailed);
205         }
206         Ok(mapped_file)
207     }
208 
handle_deleted_file_in_mapping(path: &str, pid: Pid) -> Result<String>209     pub fn handle_deleted_file_in_mapping(path: &str, pid: Pid) -> Result<String> {
210         // Check for ' (deleted)' in |path|.
211         // |path| has to be at least as long as "/x (deleted)".
212         if !path.ends_with(DELETED_SUFFIX) {
213             return Ok(path.to_string());
214         }
215 
216         // Check |path| against the /proc/pid/exe 'symlink'.
217         let exe_link = format!("/proc/{}/exe", pid);
218         let link_path = std::fs::read_link(&exe_link)?;
219 
220         // This is a no-op for now (until we want to support root_prefix for chroot-envs)
221         // if (!GetMappingAbsolutePath(new_mapping, new_path))
222         //   return false;
223 
224         if link_path != PathBuf::from(path) {
225             return Err(MapsReaderError::SymlinkError(
226                 PathBuf::from(path),
227                 link_path,
228             ));
229         }
230 
231         // Check to see if someone actually named their executable 'foo (deleted)'.
232 
233         // This makes currently no sense, as exe_link == new_path
234         // if let (Some(exe_stat), Some(new_path_stat)) = (nix::stat::stat(exe_link), nix::stat::stat(new_path)) {
235         //     if exe_stat.st_dev == new_path_stat.st_dev && exe_stat.st_ino == new_path_stat.st_ino {
236         //         return Err("".into());
237         //     }
238         // }
239         Ok(exe_link)
240     }
241 
stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool242     pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool {
243         // Loop over all stack words that would have been on the stack in
244         // the target process (i.e. are word aligned, and at addresses >=
245         // the stack pointer).  Regardless of the alignment of |stack_copy|,
246         // the memory starting at |stack_copy| + |offset| represents an
247         // aligned word in the target process.
248         let low_addr = self.system_mapping_info.start_address;
249         let high_addr = self.system_mapping_info.end_address;
250         let mut offset = (sp_offset + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
251         while offset <= stack_copy.len() - size_of::<usize>() {
252             let addr = match std::mem::size_of::<usize>() {
253                 4 => stack_copy[offset..]
254                     .as_ref()
255                     .read_u32::<NativeEndian>()
256                     .map(|u| u as usize),
257                 8 => stack_copy[offset..]
258                     .as_ref()
259                     .read_u64::<NativeEndian>()
260                     .map(|u| u as usize),
261                 x => panic!("Unexpected type width: {}", x),
262             };
263             if let Ok(addr) = addr {
264                 if low_addr <= addr && addr <= high_addr {
265                     return true;
266                 }
267                 offset += size_of::<usize>();
268             } else {
269                 break;
270             }
271         }
272         false
273     }
274 
is_mapped_file_safe_to_open(name: &Option<String>) -> bool275     pub fn is_mapped_file_safe_to_open(name: &Option<String>) -> bool {
276         // It is unsafe to attempt to open a mapped file that lives under /dev,
277         // because the semantics of the open may be driver-specific so we'd risk
278         // hanging the crash dumper. And a file in /dev/ almost certainly has no
279         // ELF file identifier anyways.
280         if let Some(name) = name {
281             if name.starts_with("/dev/") {
282                 return false;
283             }
284         }
285         true
286     }
287 
elf_file_so_name(&self) -> Result<String>288     fn elf_file_so_name(&self) -> Result<String> {
289         // Find the shared object name (SONAME) by examining the ELF information
290         // for |mapping|. If the SONAME is found copy it into the passed buffer
291         // |soname| and return true. The size of the buffer is |soname_size|.
292         let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?;
293 
294         let elf_obj = elf::Elf::parse(&mapped_file)?;
295 
296         let soname = elf_obj.soname.ok_or_else(|| {
297             MapsReaderError::NoSoName(self.name.clone().unwrap_or_else(|| "None".to_string()))
298         })?;
299         Ok(soname.to_string())
300     }
301 
get_mapping_effective_name_and_path(&self) -> Result<(String, String)>302     pub fn get_mapping_effective_name_and_path(&self) -> Result<(String, String)> {
303         let mut file_path = self.name.clone().unwrap_or_default();
304         let file_name;
305 
306         // Tools such as minidump_stackwalk use the name of the module to look up
307         // symbols produced by dump_syms. dump_syms will prefer to use a module's
308         // DT_SONAME as the module name, if one exists, and will fall back to the
309         // filesystem name of the module.
310 
311         // Just use the filesystem name if no SONAME is present.
312         let file_name = match self.elf_file_so_name() {
313             Ok(name) => name,
314             Err(_) => {
315                 //   file_path := /path/to/libname.so
316                 //   file_name := libname.so
317                 let split: Vec<_> = file_path.rsplitn(2, '/').collect();
318                 file_name = split.first().unwrap().to_string();
319                 return Ok((file_path, file_name));
320             }
321         };
322 
323         if self.executable && self.offset != 0 {
324             // If an executable is mapped from a non-zero offset, this is likely because
325             // the executable was loaded directly from inside an archive file (e.g., an
326             // apk on Android).
327             // In this case, we append the file_name to the mapped archive path:
328             //   file_name := libname.so
329             //   file_path := /path/to/ARCHIVE.APK/libname.so
330             file_path = format!("{}/{}", file_path, file_name);
331         } else {
332             // Otherwise, replace the basename with the SONAME.
333             let split: Vec<_> = file_path.rsplitn(2, '/').collect();
334             if split.len() == 2 {
335                 // NOTE: rsplitn reverses the order, so the remainder is the last item
336                 file_path = format!("{}/{}", split[1], file_name);
337             } else {
338                 file_path = file_name.clone();
339             }
340         }
341 
342         Ok((file_path, file_name))
343     }
344 
is_contained_in(&self, user_mapping_list: &MappingList) -> bool345     pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool {
346         for user in user_mapping_list {
347             // Ignore any mappings that are wholly contained within
348             // mappings in the mapping_info_ list.
349             if self.start_address >= user.mapping.start_address
350                 && (self.start_address + self.size)
351                     <= (user.mapping.start_address + user.mapping.size)
352             {
353                 return true;
354             }
355         }
356         false
357     }
358 
is_interesting(&self) -> bool359     pub fn is_interesting(&self) -> bool {
360         // only want modules with filenames.
361         self.name.is_some() &&
362         // Only want to include one mapping per shared lib.
363         // Avoid filtering executable mappings.
364         (self.offset == 0 || self.executable) &&
365         // big enough to get a signature for.
366         self.size >= 4096
367     }
368 
contains_address(&self, address: usize) -> bool369     pub fn contains_address(&self, address: usize) -> bool {
370         self.system_mapping_info.start_address <= address
371             && address < self.system_mapping_info.end_address
372     }
373 }
374 
375 #[cfg(test)]
376 #[cfg(target_pointer_width = "64")] // All addresses are 64 bit and I'm currently too lazy to adjust it to work for both
377 mod tests {
378     use super::*;
379 
get_lines_and_loc() -> (Vec<&'static str>, u64)380     fn get_lines_and_loc() -> (Vec<&'static str>, u64) {
381         (vec![
382 "5597483fc000-5597483fe000 r--p 00000000 00:31 4750073                    /usr/bin/cat",
383 "5597483fe000-559748402000 r-xp 00002000 00:31 4750073                    /usr/bin/cat",
384 "559748402000-559748404000 r--p 00006000 00:31 4750073                    /usr/bin/cat",
385 "559748404000-559748405000 r--p 00007000 00:31 4750073                    /usr/bin/cat",
386 "559748405000-559748406000 rw-p 00008000 00:31 4750073                    /usr/bin/cat",
387 "559749b0e000-559749b2f000 rw-p 00000000 00:00 0                          [heap]",
388 "7efd968d3000-7efd968f5000 rw-p 00000000 00:00 0",
389 "7efd968f5000-7efd9694a000 r--p 00000000 00:31 5004638                    /usr/lib/locale/en_US.utf8/LC_CTYPE",
390 "7efd9694a000-7efd96bc2000 r--p 00000000 00:31 5004373                    /usr/lib/locale/en_US.utf8/LC_COLLATE",
391 "7efd96bc2000-7efd96bc4000 rw-p 00000000 00:00 0",
392 "7efd96bc4000-7efd96bea000 r--p 00000000 00:31 4996104                    /lib64/libc-2.32.so",
393 "7efd96bea000-7efd96d39000 r-xp 00026000 00:31 4996104                    /lib64/libc-2.32.so",
394 "7efd96d39000-7efd96d85000 r--p 00175000 00:31 4996104                    /lib64/libc-2.32.so",
395 "7efd96d85000-7efd96d86000 ---p 001c1000 00:31 4996104                    /lib64/libc-2.32.so",
396 "7efd96d86000-7efd96d89000 r--p 001c1000 00:31 4996104                    /lib64/libc-2.32.so",
397 "7efd96d89000-7efd96d8c000 rw-p 001c4000 00:31 4996104                    /lib64/libc-2.32.so",
398 "7efd96d8c000-7efd96d92000 ---p 00000000 00:00 0",
399 "7efd96da0000-7efd96da1000 r--p 00000000 00:31 5004379                    /usr/lib/locale/en_US.utf8/LC_NUMERIC",
400 "7efd96da1000-7efd96da2000 r--p 00000000 00:31 5004382                    /usr/lib/locale/en_US.utf8/LC_TIME",
401 "7efd96da2000-7efd96da3000 r--p 00000000 00:31 5004377                    /usr/lib/locale/en_US.utf8/LC_MONETARY",
402 "7efd96da3000-7efd96da4000 r--p 00000000 00:31 5004376                    /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES",
403 "7efd96da4000-7efd96da5000 r--p 00000000 00:31 5004380                    /usr/lib/locale/en_US.utf8/LC_PAPER",
404 "7efd96da5000-7efd96da6000 r--p 00000000 00:31 5004378                    /usr/lib/locale/en_US.utf8/LC_NAME",
405 "7efd96da6000-7efd96da7000 r--p 00000000 00:31 5004372                    /usr/lib/locale/en_US.utf8/LC_ADDRESS",
406 "7efd96da7000-7efd96da8000 r--p 00000000 00:31 5004381                    /usr/lib/locale/en_US.utf8/LC_TELEPHONE",
407 "7efd96da8000-7efd96da9000 r--p 00000000 00:31 5004375                    /usr/lib/locale/en_US.utf8/LC_MEASUREMENT",
408 "7efd96da9000-7efd96db0000 r--s 00000000 00:31 5004639                    /usr/lib64/gconv/gconv-modules.cache",
409 "7efd96db0000-7efd96db1000 r--p 00000000 00:31 5004374                    /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION",
410 "7efd96db1000-7efd96db2000 r--p 00000000 00:31 4996100                    /lib64/ld-2.32.so",
411 "7efd96db2000-7efd96dd3000 r-xp 00001000 00:31 4996100                    /lib64/ld-2.32.so",
412 "7efd96dd3000-7efd96ddc000 r--p 00022000 00:31 4996100                    /lib64/ld-2.32.so",
413 "7efd96ddc000-7efd96ddd000 r--p 0002a000 00:31 4996100                    /lib64/ld-2.32.so",
414 "7efd96ddd000-7efd96ddf000 rw-p 0002b000 00:31 4996100                    /lib64/ld-2.32.so",
415 "7ffc6dfda000-7ffc6dffb000 rw-p 00000000 00:00 0                          [stack]",
416 "7ffc6e0f3000-7ffc6e0f7000 r--p 00000000 00:00 0                          [vvar]",
417 "7ffc6e0f7000-7ffc6e0f9000 r-xp 00000000 00:00 0                          [vdso]",
418 "ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]"
419         ], 0x7ffc6e0f7000)
420     }
421 
get_all_mappings() -> Vec<MappingInfo>422     fn get_all_mappings() -> Vec<MappingInfo> {
423         let mut mappings: Vec<MappingInfo> = Vec::new();
424         let (lines, linux_gate_loc) = get_lines_and_loc();
425         // Only /usr/bin/cat and [heap]
426         for line in lines {
427             match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) {
428                 Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map),
429                 Ok(MappingInfoParsingResult::SkipLine) => continue,
430                 Err(_) => assert!(false),
431             }
432         }
433         assert_eq!(mappings.len(), 23);
434         mappings
435     }
436 
437     #[test]
test_merged()438     fn test_merged() {
439         let mut mappings: Vec<MappingInfo> = Vec::new();
440         let (lines, linux_gate_loc) = get_lines_and_loc();
441         // Only /usr/bin/cat and [heap]
442         for line in lines[0..=6].iter() {
443             match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) {
444                 Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map),
445                 Ok(MappingInfoParsingResult::SkipLine) => continue,
446                 Err(_) => assert!(false),
447             }
448         }
449 
450         assert_eq!(mappings.len(), 3);
451         let cat_map = MappingInfo {
452             start_address: 0x5597483fc000,
453             size: 40960,
454             system_mapping_info: SystemMappingInfo {
455                 start_address: 0x5597483fc000,
456                 end_address: 0x559748406000,
457             },
458             offset: 0,
459             executable: true,
460             name: Some("/usr/bin/cat".to_string()),
461         };
462 
463         assert_eq!(mappings[0], cat_map);
464 
465         let heap_map = MappingInfo {
466             start_address: 0x559749b0e000,
467             size: 135168,
468             system_mapping_info: SystemMappingInfo {
469                 start_address: 0x559749b0e000,
470                 end_address: 0x559749b2f000,
471             },
472             offset: 0,
473             executable: false,
474             name: Some("[heap]".to_string()),
475         };
476 
477         assert_eq!(mappings[1], heap_map);
478 
479         let empty_map = MappingInfo {
480             start_address: 0x7efd968d3000,
481             size: 139264,
482             system_mapping_info: SystemMappingInfo {
483                 start_address: 0x7efd968d3000,
484                 end_address: 0x7efd968f5000,
485             },
486             offset: 0,
487             executable: false,
488             name: None,
489         };
490 
491         assert_eq!(mappings[2], empty_map);
492     }
493 
494     #[test]
test_linux_gate_parsing()495     fn test_linux_gate_parsing() {
496         let mappings = get_all_mappings();
497 
498         let gate_map = MappingInfo {
499             start_address: 0x7ffc6e0f7000,
500             size: 8192,
501             system_mapping_info: SystemMappingInfo {
502                 start_address: 0x7ffc6e0f7000,
503                 end_address: 0x7ffc6e0f9000,
504             },
505             offset: 0,
506             executable: true,
507             name: Some("linux-gate.so".to_string()),
508         };
509 
510         assert_eq!(mappings[21], gate_map);
511     }
512 
513     #[test]
test_reading_all()514     fn test_reading_all() {
515         let mappings = get_all_mappings();
516 
517         let found_items = vec![
518             Some("/usr/bin/cat".to_string()),
519             Some("[heap]".to_string()),
520             None,
521             Some("/usr/lib/locale/en_US.utf8/LC_CTYPE".to_string()),
522             Some("/usr/lib/locale/en_US.utf8/LC_COLLATE".to_string()),
523             None,
524             Some("/lib64/libc-2.32.so".to_string()),
525             // The original shows a None here, but this is an address ranges that the
526             // linker reserved but which a loaded library did not use. These
527             // appear as an anonymous private mapping with no access flags set
528             // and which directly follow an executable mapping.
529             Some("/usr/lib/locale/en_US.utf8/LC_NUMERIC".to_string()),
530             Some("/usr/lib/locale/en_US.utf8/LC_TIME".to_string()),
531             Some("/usr/lib/locale/en_US.utf8/LC_MONETARY".to_string()),
532             Some("/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES".to_string()),
533             Some("/usr/lib/locale/en_US.utf8/LC_PAPER".to_string()),
534             Some("/usr/lib/locale/en_US.utf8/LC_NAME".to_string()),
535             Some("/usr/lib/locale/en_US.utf8/LC_ADDRESS".to_string()),
536             Some("/usr/lib/locale/en_US.utf8/LC_TELEPHONE".to_string()),
537             Some("/usr/lib/locale/en_US.utf8/LC_MEASUREMENT".to_string()),
538             Some("/usr/lib64/gconv/gconv-modules.cache".to_string()),
539             Some("/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION".to_string()),
540             Some("/lib64/ld-2.32.so".to_string()),
541             Some("[stack]".to_string()),
542             Some("[vvar]".to_string()),
543             // This is rewritten from [vdso] to linux-gate.so
544             Some("linux-gate.so".to_string()),
545             Some("[vsyscall]".to_string()),
546         ];
547 
548         assert_eq!(
549             mappings.iter().map(|x| x.name.clone()).collect::<Vec<_>>(),
550             found_items
551         );
552     }
553 
554     #[test]
test_merged_reserved_mappings()555     fn test_merged_reserved_mappings() {
556         let mappings = get_all_mappings();
557 
558         let gate_map = MappingInfo {
559             start_address: 0x7efd96bc4000,
560             size: 1892352, // Merged the anonymous area after in this mapping, so its bigger..
561             system_mapping_info: SystemMappingInfo {
562                 start_address: 0x7efd96bc4000,
563                 end_address: 0x7efd96d8c000, // ..but this is not visible here
564             },
565             offset: 0,
566             executable: true,
567             name: Some("/lib64/libc-2.32.so".to_string()),
568         };
569 
570         assert_eq!(mappings[6], gate_map);
571     }
572 
573     #[test]
test_get_mapping_effective_name()574     fn test_get_mapping_effective_name() {
575         let lines = vec![
576 "7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458                   /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
577 "7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458                   /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
578 "7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458                   /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
579 "7f0b97b73000-7f0b97b74000 rw-p 00001000 00:3e 27136458                   /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
580         ];
581         let linux_gate_loc = 0x7ffe091bf000;
582         let mut mappings: Vec<MappingInfo> = Vec::new();
583         for line in lines {
584             match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) {
585                 Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map),
586                 Ok(MappingInfoParsingResult::SkipLine) => continue,
587                 Err(_) => assert!(false),
588             }
589         }
590         assert_eq!(mappings.len(), 1);
591 
592         let (file_path, file_name) = mappings[0]
593             .get_mapping_effective_name_and_path()
594             .expect("Couldn't get effective name for mapping");
595         assert_eq!(file_name, "libmozgtk.so");
596         assert_eq!(file_path, "/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so");
597     }
598 
599     #[test]
test_whitespaces_in_maps()600     fn test_whitespaces_in_maps() {
601         let lines = vec![
602 "   7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458                   libmozgtk.so",
603 "7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458                   libmozgtk.so    ",
604 "7f0b97b71000-7f0b97b73000     r--p 00000000 00:3e 27136458\t\t\tlibmozgtk.so",
605         ];
606         let linux_gate_loc = 0x7ffe091bf000;
607         let mut mappings: Vec<MappingInfo> = Vec::new();
608         for line in lines {
609             match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) {
610                 Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map),
611                 Ok(MappingInfoParsingResult::SkipLine) => continue,
612                 Err(x) => panic!("{:?}", x),
613             }
614         }
615         assert_eq!(mappings.len(), 1);
616 
617         let expected_map = MappingInfo {
618             start_address: 0x7f0b97b6f000,
619             size: 16384,
620             system_mapping_info: SystemMappingInfo {
621                 start_address: 0x7f0b97b6f000,
622                 end_address: 0x7f0b97b73000,
623             },
624             offset: 0,
625             executable: true,
626             name: Some("libmozgtk.so".to_string()),
627         };
628 
629         assert_eq!(expected_map, mappings[0]);
630     }
631 
632     #[test]
test_whitespaces_in_name()633     fn test_whitespaces_in_name() {
634         let lines = vec![
635 "10000000-20000000 r--p 00000000 00:3e 27136458                   libmoz    gtk.so",
636 "20000000-30000000 r--p 00000000 00:3e 27136458                   libmozgtk.so (deleted)",
637 "30000000-40000000 r--p 00000000 00:3e 27136458                   \"libmoz     gtk.so (deleted)\"",
638 "30000000-40000000 r--p 00000000 00:3e 27136458                   ",
639         ];
640         let linux_gate_loc = 0x7ffe091bf000;
641         let mut mappings: Vec<MappingInfo> = Vec::new();
642         for line in lines {
643             match MappingInfo::parse_from_line(&line, linux_gate_loc, mappings.last_mut()) {
644                 Ok(MappingInfoParsingResult::Success(map)) => mappings.push(map),
645                 Ok(MappingInfoParsingResult::SkipLine) => continue,
646                 Err(_) => assert!(false),
647             }
648         }
649         assert_eq!(mappings.len(), 4);
650         assert_eq!(mappings[0].name, Some("libmoz    gtk.so".to_string()));
651         assert_eq!(mappings[1].name, Some("libmozgtk.so (deleted)".to_string()));
652         assert_eq!(
653             mappings[2].name,
654             Some("\"libmoz     gtk.so (deleted)\"".to_string())
655         );
656         assert_eq!(mappings[3].name, None);
657     }
658 }
659