summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/linux/maps_reader.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/src/linux/maps_reader.rs')
-rw-r--r--third_party/rust/minidump-writer/src/linux/maps_reader.rs147
1 files changed, 135 insertions, 12 deletions
diff --git a/third_party/rust/minidump-writer/src/linux/maps_reader.rs b/third_party/rust/minidump-writer/src/linux/maps_reader.rs
index 4d0d3b5aaa..b5b7fb23e6 100644
--- a/third_party/rust/minidump-writer/src/linux/maps_reader.rs
+++ b/third_party/rust/minidump-writer/src/linux/maps_reader.rs
@@ -289,10 +289,9 @@ impl MappingInfo {
true
}
- fn elf_file_so_name(&self) -> Result<String> {
- // Find the shared object name (SONAME) by examining the ELF information
- // for |mapping|. If the SONAME is found copy it into the passed buffer
- // |soname| and return true. The size of the buffer is |soname_size|.
+ /// Find the shared object name (SONAME) by examining the ELF information
+ /// for the mapping.
+ fn so_name(&self) -> Result<String> {
let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?;
let elf_obj = elf::Elf::parse(&mapped_file)?;
@@ -303,7 +302,14 @@ impl MappingInfo {
Ok(soname.to_string())
}
- pub fn get_mapping_effective_path_and_name(&self) -> Result<(PathBuf, 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());
// Tools such as minidump_stackwalk use the name of the module to look up
@@ -312,16 +318,15 @@ impl MappingInfo {
// filesystem name of the module.
// Just use the filesystem name if no SONAME is present.
- let file_name = if let Ok(name) = self.elf_file_so_name() {
- name
- } else {
+ let Ok(file_name) = self.so_name() else {
// file_path := /path/to/libname.so
// file_name := libname.so
let file_name = file_path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
- return Ok((file_path, file_name));
+
+ return Ok((file_path, file_name, self.so_version()));
};
if self.is_executable() && self.offset != 0 {
@@ -337,7 +342,7 @@ impl MappingInfo {
file_path.set_file_name(&file_name);
}
- Ok((file_path, file_name))
+ Ok((file_path, file_name, self.so_version()))
}
pub fn is_contained_in(&self, user_mapping_list: &MappingList) -> bool {
@@ -382,6 +387,97 @@ impl MappingInfo {
}
}
+/// Version metadata retrieved from an .so filename
+///
+/// There is no standard for .so version numbers so this implementation just
+/// does a best effort to pull as much data as it can based on real .so schemes
+/// seen
+///
+/// That being said, the [libtool](https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html)
+/// versioning scheme is fairly common
+#[cfg_attr(test, derive(Debug))]
+pub struct SoVersion {
+ /// Might be non-zero if there is at least one non-zero numeric component after .so.
+ ///
+ /// Equivalent to `current` in libtool versions
+ pub major: u32,
+ /// The numeric component after the major version, if any
+ ///
+ /// Equivalent to `revision` in libtool versions
+ pub minor: u32,
+ /// The numeric component after the minor version, if any
+ ///
+ /// Equivalent to `age` in libtool versions
+ pub patch: u32,
+ /// The patch component may contain additional non-numeric metadata similar
+ /// to a semver prelease, this is any numeric data that suffixes that prerelease
+ /// string
+ pub prerelease: u32,
+}
+
+impl SoVersion {
+ /// Attempts to retrieve the .so version of the elf path via its filename
+ fn parse(so_path: &OsStr) -> Option<Self> {
+ let filename = std::path::Path::new(so_path).file_name()?;
+
+ // Avoid an allocation unless the string contains non-utf8
+ 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 {
+ // In some cases the release/patch version is alphanumeric (eg. '2rc5'),
+ // so try to parse either a single or two numbers
+ 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")] // All addresses are 64 bit and I'm currently too lazy to adjust it to work for both
mod tests {
@@ -628,14 +724,41 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1
);
assert_eq!(mappings.len(), 1);
- let (file_path, file_name) = mappings[0]
- .get_mapping_effective_path_and_name()
+ 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(
"\