use crate::auxv_reader::AuxvType;
use crate::errors::MapsReaderError;
use crate::thread_info::Pid;
use byteorder::{NativeEndian, ReadBytesExt};
use goblin::elf;
use memmap2::{Mmap, MmapOptions};
use procfs_core::process::{MMPermissions, MMapPath, MemoryMaps};
use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::OsStrExt;
use std::{fs::File, mem::size_of, path::PathBuf};
pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
pub const DELETED_SUFFIX: &[u8] = b" (deleted)";
type Result<T> = std::result::Result<T, MapsReaderError>;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SystemMappingInfo {
pub start_address: usize,
pub end_address: usize,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MappingInfo {
pub start_address: usize,
pub size: usize,
pub system_mapping_info: SystemMappingInfo,
pub offset: usize, pub permissions: MMPermissions, pub name: Option<OsString>,
}
#[derive(Debug)]
pub struct MappingEntry {
pub mapping: MappingInfo,
pub identifier: Vec<u8>,
}
pub type MappingList = Vec<MappingEntry>;
#[derive(Debug)]
pub enum MappingInfoParsingResult {
SkipLine,
Success(MappingInfo),
}
fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
match pathname {
Some(x) => x.as_bytes().contains(&b'/'),
None => false,
}
}
impl MappingInfo {
pub fn name_is_path(&self) -> bool {
is_mapping_a_path(self.name.as_deref())
}
pub fn is_empty_page(&self) -> bool {
(self.offset == 0) && (self.permissions == MMPermissions::PRIVATE) && self.name.is_none()
}
pub fn end_address(&self) -> usize {
self.start_address + self.size
}
pub fn aggregate(memory_maps: MemoryMaps, linux_gate_loc: AuxvType) -> Result<Vec<Self>> {
let mut infos = Vec::<Self>::new();
for mm in memory_maps {
let start_address: usize = mm.address.0.try_into()?;
let end_address: usize = mm.address.1.try_into()?;
let mut offset: usize = mm.offset.try_into()?;
let mut pathname: Option<OsString> = match mm.pathname {
MMapPath::Path(p) => Some(p.into()),
MMapPath::Heap => Some("[heap]".into()),
MMapPath::Stack => Some("[stack]".into()),
MMapPath::TStack(i) => Some(format!("[stack:{i}]").into()),
MMapPath::Vdso => Some("[vdso]".into()),
MMapPath::Vvar => Some("[vvar]".into()),
MMapPath::Vsyscall => Some("[vsyscall]".into()),
MMapPath::Rollup => Some("[rollup]".into()),
MMapPath::Vsys(i) => Some(format!("/SYSV{i:x}").into()),
MMapPath::Other(n) => Some(format!("[{n}]").into()),
MMapPath::Anonymous => None,
};
let is_path = is_mapping_a_path(pathname.as_deref());
if !is_path && linux_gate_loc != 0 && start_address == linux_gate_loc.try_into()? {
pathname = Some(LINUX_GATE_LIBRARY_NAME.into());
offset = 0;
}
if let Some(prev_module) = infos.last_mut() {
if (start_address == prev_module.end_address())
&& pathname.is_some()
&& (pathname == prev_module.name)
{
prev_module.system_mapping_info.end_address = end_address;
prev_module.size = end_address - prev_module.start_address;
prev_module.permissions |= mm.perms;
continue;
} else if (start_address == prev_module.end_address())
&& prev_module.is_executable()
&& prev_module.name_is_path()
&& ((offset == 0) || (offset == prev_module.end_address()))
&& (mm.perms == MMPermissions::PRIVATE)
{
prev_module.size = end_address - prev_module.start_address;
continue;
}
}
if let Some(previous_modules) = infos.rchunks_exact_mut(2).next() {
let empty_page = if let Some(prev_module) = previous_modules.last() {
let prev_prev_module = previous_modules.first().unwrap();
prev_prev_module.name_is_path()
&& (prev_prev_module.end_address() == prev_module.start_address)
&& prev_module.is_empty_page()
&& (prev_module.end_address() == start_address)
} else {
false
};
if empty_page {
let prev_prev_module = previous_modules.first_mut().unwrap();
if pathname == prev_prev_module.name {
prev_prev_module.system_mapping_info.end_address = end_address;
prev_prev_module.size = end_address - prev_prev_module.start_address;
prev_prev_module.permissions |= mm.perms;
infos.pop();
continue;
}
}
}
infos.push(MappingInfo {
start_address,
size: end_address - start_address,
system_mapping_info: SystemMappingInfo {
start_address,
end_address,
},
offset,
permissions: mm.perms,
name: pathname,
});
}
Ok(infos)
}
pub fn get_mmap(name: &Option<OsString>, offset: usize) -> Result<Mmap> {
if !MappingInfo::is_mapped_file_safe_to_open(name) {
return Err(MapsReaderError::NotSafeToOpenMapping(
name.clone().unwrap_or_default(),
));
}
let filename = name.clone().unwrap_or_default();
let mapped_file = unsafe {
MmapOptions::new()
.offset(offset.try_into()?) .map(&File::open(filename)?)?
};
if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
return Err(MapsReaderError::MmapSanityCheckFailed);
}
Ok(mapped_file)
}
pub fn fixup_deleted_file(&self, pid: Pid) -> Result<(OsString, Option<&OsStr>)> {
let Some(path) = &self.name else {
return Err(MapsReaderError::AnonymousMapping);
};
let Some(old_path) = path.as_bytes().strip_suffix(DELETED_SUFFIX) else {
return Ok((path.clone(), None));
};
let exe_link = format!("/proc/{}/exe", pid);
let link_path = std::fs::read_link(&exe_link)?;
if &link_path != path {
return Err(MapsReaderError::SymlinkError(
PathBuf::from(path),
link_path,
));
}
Ok((exe_link.into(), Some(OsStr::from_bytes(old_path))))
}
pub fn stack_has_pointer_to_mapping(&self, stack_copy: &[u8], sp_offset: usize) -> bool {
let low_addr = self.system_mapping_info.start_address;
let high_addr = self.system_mapping_info.end_address;
let mut offset = (sp_offset + size_of::<usize>() - 1) & !(size_of::<usize>() - 1);
while offset <= stack_copy.len() - size_of::<usize>() {
let addr = match std::mem::size_of::<usize>() {
4 => stack_copy[offset..]
.as_ref()
.read_u32::<NativeEndian>()
.map(|u| u as usize),
8 => stack_copy[offset..]
.as_ref()
.read_u64::<NativeEndian>()
.map(|u| u as usize),
x => panic!("Unexpected type width: {}", x),
};
if let Ok(addr) = addr {
if low_addr <= addr && addr <= high_addr {
return true;
}
offset += size_of::<usize>();
} else {
break;
}
}
false
}
pub fn is_mapped_file_safe_to_open(name: &Option<OsString>) -> bool {
if let Some(name) = name {
if name.as_bytes().starts_with(b"/dev/") {
return false;
}
}
true
}
fn so_name(&self) -> Result<String> {
let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?;
let elf_obj = elf::Elf::parse(&mapped_file)?;
let soname = elf_obj.soname.ok_or_else(|| {
MapsReaderError::NoSoName(self.name.clone().unwrap_or_else(|| "None".into()))
})?;
Ok(soname.to_string())
}
#[inline]
fn so_version(&self) -> Option<SoVersion> {
SoVersion::parse(self.name.as_deref()?)
}
pub fn get_mapping_effective_path_name_and_version(
&self,
) -> Result<(PathBuf, String, Option<SoVersion>)> {
let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default());
let Ok(file_name) = self.so_name() else {
let file_name = file_path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
return Ok((file_path, file_name, self.so_version()));
};
if self.is_executable() && self.offset != 0 {
file_path.push(&file_name);
} else {
file_path.set_file_name(&file_name);
}
Ok((file_path, file_name, self.so_version()))
}
pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool {
for user in user_mapping_list {
if self.start_address >= user.mapping.start_address
&& (self.start_address + self.size)
<= (user.mapping.start_address + user.mapping.size)
{
return true;
}
}
false
}
pub fn is_interesting(&self) -> bool {
self.name.is_some() &&
(self.offset == 0 || self.is_executable()) &&
self.size >= 4096
}
pub fn contains_address(&self, address: usize) -> bool {
self.system_mapping_info.start_address <= address
&& address < self.system_mapping_info.end_address
}
pub fn is_executable(&self) -> bool {
self.permissions.contains(MMPermissions::EXECUTE)
}
pub fn is_readable(&self) -> bool {
self.permissions.contains(MMPermissions::READ)
}
pub fn is_writable(&self) -> bool {
self.permissions.contains(MMPermissions::WRITE)
}
}
#[cfg_attr(test, derive(Debug))]
pub struct SoVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
pub prerelease: u32,
}
impl SoVersion {
fn parse(so_path: &OsStr) -> Option<Self> {
let filename = std::path::Path::new(so_path).file_name()?;
let filename = filename.to_string_lossy();
let (_, version) = filename.split_once(".so.")?;
let mut sov = Self {
major: 0,
minor: 0,
patch: 0,
prerelease: 0,
};
let comps = [
&mut sov.major,
&mut sov.minor,
&mut sov.patch,
&mut sov.prerelease,
];
for (i, comp) in version.split('.').enumerate() {
if i <= 1 {
*comps[i] = comp.parse().unwrap_or_default();
} else if i >= 4 {
break;
} else {
if let Some(pend) = comp.find(|c: char| !c.is_ascii_digit()) {
if let Ok(patch) = comp[..pend].parse() {
*comps[i] = patch;
}
if i >= comps.len() - 1 {
break;
}
if let Some(pre) = comp.rfind(|c: char| !c.is_ascii_digit()) {
if let Ok(pre) = comp[pre + 1..].parse() {
*comps[i + 1] = pre;
break;
}
}
} else {
*comps[i] = comp.parse().unwrap_or_default();
}
}
}
Some(sov)
}
}
#[cfg(test)]
impl PartialEq<(u32, u32, u32, u32)> for SoVersion {
fn eq(&self, o: &(u32, u32, u32, u32)) -> bool {
self.major == o.0 && self.minor == o.1 && self.patch == o.2 && self.prerelease == o.3
}
}
#[cfg(test)]
#[cfg(target_pointer_width = "64")] mod tests {
use super::*;
use procfs_core::FromRead;
fn get_mappings_for(map: &str, linux_gate_loc: u64) -> Vec<MappingInfo> {
MappingInfo::aggregate(
MemoryMaps::from_read(map.as_bytes()).expect("failed to read mapping info"),
linux_gate_loc,
)
.unwrap_or_default()
}
const LINES: &str = "\
5597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat
5597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat
559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat
559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat
559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat
559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]
7efd968d3000-7efd968f5000 rw-p 00000000 00:00 0
7efd968f5000-7efd9694a000 r--p 00000000 00:31 5004638 /usr/lib/locale/en_US.utf8/LC_CTYPE
7efd9694a000-7efd96bc2000 r--p 00000000 00:31 5004373 /usr/lib/locale/en_US.utf8/LC_COLLATE
7efd96bc2000-7efd96bc4000 rw-p 00000000 00:00 0
7efd96bc4000-7efd96bea000 r--p 00000000 00:31 4996104 /lib64/libc-2.32.so
7efd96bea000-7efd96d39000 r-xp 00026000 00:31 4996104 /lib64/libc-2.32.so
7efd96d39000-7efd96d85000 r--p 00175000 00:31 4996104 /lib64/libc-2.32.so
7efd96d85000-7efd96d86000 ---p 001c1000 00:31 4996104 /lib64/libc-2.32.so
7efd96d86000-7efd96d89000 r--p 001c1000 00:31 4996104 /lib64/libc-2.32.so
7efd96d89000-7efd96d8c000 rw-p 001c4000 00:31 4996104 /lib64/libc-2.32.so
7efd96d8c000-7efd96d92000 ---p 00000000 00:00 0
7efd96da0000-7efd96da1000 r--p 00000000 00:31 5004379 /usr/lib/locale/en_US.utf8/LC_NUMERIC
7efd96da1000-7efd96da2000 r--p 00000000 00:31 5004382 /usr/lib/locale/en_US.utf8/LC_TIME
7efd96da2000-7efd96da3000 r--p 00000000 00:31 5004377 /usr/lib/locale/en_US.utf8/LC_MONETARY
7efd96da3000-7efd96da4000 r--p 00000000 00:31 5004376 /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES
7efd96da4000-7efd96da5000 r--p 00000000 00:31 5004380 /usr/lib/locale/en_US.utf8/LC_PAPER
7efd96da5000-7efd96da6000 r--p 00000000 00:31 5004378 /usr/lib/locale/en_US.utf8/LC_NAME
7efd96da6000-7efd96da7000 r--p 00000000 00:31 5004372 /usr/lib/locale/en_US.utf8/LC_ADDRESS
7efd96da7000-7efd96da8000 r--p 00000000 00:31 5004381 /usr/lib/locale/en_US.utf8/LC_TELEPHONE
7efd96da8000-7efd96da9000 r--p 00000000 00:31 5004375 /usr/lib/locale/en_US.utf8/LC_MEASUREMENT
7efd96da9000-7efd96db0000 r--s 00000000 00:31 5004639 /usr/lib64/gconv/gconv-modules.cache
7efd96db0000-7efd96db1000 r--p 00000000 00:31 5004374 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION
7efd96db1000-7efd96db2000 r--p 00000000 00:31 4996100 /lib64/ld-2.32.so
7efd96db2000-7efd96dd3000 r-xp 00001000 00:31 4996100 /lib64/ld-2.32.so
7efd96dd3000-7efd96ddc000 r--p 00022000 00:31 4996100 /lib64/ld-2.32.so
7efd96ddc000-7efd96ddd000 r--p 0002a000 00:31 4996100 /lib64/ld-2.32.so
7efd96ddd000-7efd96ddf000 rw-p 0002b000 00:31 4996100 /lib64/ld-2.32.so
7ffc6dfda000-7ffc6dffb000 rw-p 00000000 00:00 0 [stack]
7ffc6e0f3000-7ffc6e0f7000 r--p 00000000 00:00 0 [vvar]
7ffc6e0f7000-7ffc6e0f9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]";
const LINUX_GATE_LOC: u64 = 0x7ffc6e0f7000;
fn get_all_mappings() -> Vec<MappingInfo> {
get_mappings_for(LINES, LINUX_GATE_LOC)
}
#[test]
fn test_merged() {
let mappings = get_mappings_for(
"\
5597483fc000-5597483fe000 r--p 00000000 00:31 4750073 /usr/bin/cat
5597483fe000-559748402000 r-xp 00002000 00:31 4750073 /usr/bin/cat
559748402000-559748404000 r--p 00006000 00:31 4750073 /usr/bin/cat
559748404000-559748405000 r--p 00007000 00:31 4750073 /usr/bin/cat
559748405000-559748406000 rw-p 00008000 00:31 4750073 /usr/bin/cat
559749b0e000-559749b2f000 rw-p 00000000 00:00 0 [heap]
7efd968d3000-7efd968f5000 rw-p 00000000 00:00 0 ",
0x7ffc6e0f7000,
);
assert_eq!(mappings.len(), 3);
let cat_map = MappingInfo {
start_address: 0x5597483fc000,
size: 40960,
system_mapping_info: SystemMappingInfo {
start_address: 0x5597483fc000,
end_address: 0x559748406000,
},
offset: 0,
permissions: MMPermissions::READ
| MMPermissions::WRITE
| MMPermissions::EXECUTE
| MMPermissions::PRIVATE,
name: Some("/usr/bin/cat".into()),
};
assert_eq!(mappings[0], cat_map);
let heap_map = MappingInfo {
start_address: 0x559749b0e000,
size: 135168,
system_mapping_info: SystemMappingInfo {
start_address: 0x559749b0e000,
end_address: 0x559749b2f000,
},
offset: 0,
permissions: MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
name: Some("[heap]".into()),
};
assert_eq!(mappings[1], heap_map);
let empty_map = MappingInfo {
start_address: 0x7efd968d3000,
size: 139264,
system_mapping_info: SystemMappingInfo {
start_address: 0x7efd968d3000,
end_address: 0x7efd968f5000,
},
offset: 0,
permissions: MMPermissions::READ | MMPermissions::WRITE | MMPermissions::PRIVATE,
name: None,
};
assert_eq!(mappings[2], empty_map);
}
#[test]
fn test_linux_gate_parsing() {
let mappings = get_all_mappings();
let gate_map = MappingInfo {
start_address: 0x7ffc6e0f7000,
size: 8192,
system_mapping_info: SystemMappingInfo {
start_address: 0x7ffc6e0f7000,
end_address: 0x7ffc6e0f9000,
},
offset: 0,
permissions: MMPermissions::READ | MMPermissions::EXECUTE | MMPermissions::PRIVATE,
name: Some("linux-gate.so".into()),
};
assert_eq!(mappings[21], gate_map);
}
#[test]
fn test_reading_all() {
let mappings = get_all_mappings();
let found_items: Vec<Option<OsString>> = vec![
Some("/usr/bin/cat".into()),
Some("[heap]".into()),
None,
Some("/usr/lib/locale/en_US.utf8/LC_CTYPE".into()),
Some("/usr/lib/locale/en_US.utf8/LC_COLLATE".into()),
None,
Some("/lib64/libc-2.32.so".into()),
Some("/usr/lib/locale/en_US.utf8/LC_NUMERIC".into()),
Some("/usr/lib/locale/en_US.utf8/LC_TIME".into()),
Some("/usr/lib/locale/en_US.utf8/LC_MONETARY".into()),
Some("/usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES".into()),
Some("/usr/lib/locale/en_US.utf8/LC_PAPER".into()),
Some("/usr/lib/locale/en_US.utf8/LC_NAME".into()),
Some("/usr/lib/locale/en_US.utf8/LC_ADDRESS".into()),
Some("/usr/lib/locale/en_US.utf8/LC_TELEPHONE".into()),
Some("/usr/lib/locale/en_US.utf8/LC_MEASUREMENT".into()),
Some("/usr/lib64/gconv/gconv-modules.cache".into()),
Some("/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION".into()),
Some("/lib64/ld-2.32.so".into()),
Some("[stack]".into()),
Some("[vvar]".into()),
Some("linux-gate.so".into()),
Some("[vsyscall]".into()),
];
assert_eq!(
mappings.iter().map(|x| x.name.clone()).collect::<Vec<_>>(),
found_items
);
}
#[test]
fn test_merged_reserved_mappings() {
let mappings = get_all_mappings();
let gate_map = MappingInfo {
start_address: 0x7efd96bc4000,
size: 1892352, system_mapping_info: SystemMappingInfo {
start_address: 0x7efd96bc4000,
end_address: 0x7efd96d8c000, },
offset: 0,
permissions: MMPermissions::READ
| MMPermissions::WRITE
| MMPermissions::EXECUTE
| MMPermissions::PRIVATE,
name: Some("/lib64/libc-2.32.so".into()),
};
assert_eq!(mappings[6], gate_map);
}
#[test]
fn test_merged_reserved_mappings_within_module() {
let mappings = get_mappings_for(
"\
9b4a0000-9b931000 r--p 00000000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
9b931000-9bcae000 ---p 00000000 00:00 0
9bcae000-a116b000 r-xp 00490000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
a116b000-a4562000 r--p 0594d000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
a4562000-a4563000 ---p 00000000 00:00 0
a4563000-a4840000 r--p 08d44000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so
a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1/lib/x86/libxul.so",
0xa4876000,
);
let gate_map = MappingInfo {
start_address: 0x9b4a0000,
size: 155004928, system_mapping_info: SystemMappingInfo {
start_address: 0x9b4a0000,
end_address: 0xa4873000,
},
offset: 0,
permissions: MMPermissions::READ
| MMPermissions::WRITE
| MMPermissions::EXECUTE
| MMPermissions::PRIVATE,
name: Some("/data/app/org.mozilla.firefox-1/lib/x86/libxul.so".into()),
};
assert_eq!(mappings[0], gate_map);
}
#[test]
fn test_get_mapping_effective_name() {
let mappings = get_mappings_for(
"\
7f0b97b6f000-7f0b97b70000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7f0b97b70000-7f0b97b71000 r-xp 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7f0b97b71000-7f0b97b73000 r--p 00000000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so
7f0b97b73000-7f0b97b74000 rw-p 00001000 00:3e 27136458 /home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so",
0x7ffe091bf000,
);
assert_eq!(mappings.len(), 1);
let (file_path, file_name, _version) = mappings[0]
.get_mapping_effective_path_name_and_version()
.expect("Couldn't get effective name for mapping");
assert_eq!(file_name, "libmozgtk.so");
assert_eq!(file_path, PathBuf::from("/home/martin/Documents/mozilla/devel/mozilla-central/obj/widget/gtk/mozgtk/gtk3/libmozgtk.so"));
}
#[test]
fn test_elf_file_so_version() {
#[rustfmt::skip]
let test_cases = [
("/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.32", (6, 0, 32, 0)),
("/usr/lib/x86_64-linux-gnu/libcairo-gobject.so.2.11800.0", (2, 11800, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libm.so.6", (6, 0, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libpthread.so.0", (0, 0, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libgmodule-2.0.so.0.7800.0", (0, 7800, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libabsl_time_zone.so.20220623.0.0", (20220623, 0, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.2rc5", (3, 34, 2, 5)),
("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.2rc", (3, 34, 2, 0)),
("/usr/lib/x86_64-linux-gnu/libdbus-1.so.3.34.rc5", (3, 34, 0, 5)),
("/usr/lib/x86_64-linux-gnu/libtoto.so.AAA", (0, 0, 0, 0)),
("/usr/lib/x86_64-linux-gnu/libsemver-1.so.1.2.alpha.1", (1, 2, 0, 1)),
("/usr/lib/x86_64-linux-gnu/libboop.so.1.2.3.4.5", (1, 2, 3, 4)),
("/usr/lib/x86_64-linux-gnu/libboop.so.1.2.3pre4.5", (1, 2, 3, 4)),
];
assert!(SoVersion::parse(OsStr::new("/home/alex/bin/firefox/libmozsandbox.so")).is_none());
for (path, expected) in test_cases {
let actual = SoVersion::parse(OsStr::new(path)).unwrap();
assert_eq!(actual, expected);
}
}
#[test]
fn test_whitespaces_in_name() {
let mappings = get_mappings_for(
"\
10000000-20000000 r--p 00000000 00:3e 27136458 libmoz gtk.so
20000000-30000000 r--p 00000000 00:3e 27136458 libmozgtk.so (deleted)
30000000-40000000 r--p 00000000 00:3e 27136458 \"libmoz gtk.so (deleted)\"
30000000-40000000 r--p 00000000 00:3e 27136458 ",
0x7ffe091bf000,
);
assert_eq!(mappings.len(), 4);
assert_eq!(mappings[0].name, Some("libmoz gtk.so".into()));
assert_eq!(mappings[1].name, Some("libmozgtk.so (deleted)".into()));
assert_eq!(
mappings[2].name,
Some("\"libmoz gtk.so (deleted)\"".into())
);
assert_eq!(mappings[3].name, None);
}
}