diff --git a/src/linux/errors.rs b/src/linux/errors.rs index 86f27f36..982445a0 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -254,6 +254,12 @@ pub enum WriterError { #[derive(Debug, Error)] pub enum ModuleReaderError { + #[error("failed to read module file ({path}): {error}")] + MapFile { + path: std::path::PathBuf, + #[source] + error: std::io::Error, + }, #[error("failed to read module memory: {length} bytes at {offset}{}: {error}", .start_address.map(|addr| format!(" (start address: {addr})")).unwrap_or_default())] ReadModuleMemory { offset: u64, diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index fffa7514..08ee8189 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -152,6 +152,17 @@ fn section_header_with_name<'sc>( /// Types which can be read from ProcessMemory. pub trait ReadFromModule: Sized { fn read_from_module(module_memory: ProcessMemory<'_>) -> Result; + + fn read_from_file(path: &std::path::Path) -> Result { + let map = std::fs::File::open(path) + // Safety: the file is an executable binary (very likely read-only), and won't be changed. + .and_then(|file| unsafe { memmap2::Mmap::map(&file) }) + .map_err(|error| Error::MapFile { + path: path.to_owned(), + error, + })?; + Self::read_from_module(ProcessMemory::Slice(&map)) + } } /// The module build id. diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs index c0172084..6688387c 100644 --- a/src/linux/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -1,6 +1,6 @@ use super::*; use crate::linux::maps_reader::MappingInfo; -use crate::linux::module_reader::{BuildId, SoName}; +use crate::linux::module_reader::{BuildId, ReadFromModule, SoName}; /// Write information about the mappings in effect. Because we are using the /// minidump format, the information about the mappings is pretty limited. @@ -24,15 +24,40 @@ pub fn write( { continue; } + log::debug!("retrieving build id for {:?}", &dumper.mappings[map_idx]); let BuildId(identifier) = dumper .from_process_memory_for_index(map_idx) - .unwrap_or_else(|_| BuildId(Vec::new())); + .or_else(|e| { + // If the mapping has an associated name that is a file, try to read the build id + // from the file. If there is no note segment with the build id in + // the program headers, we can't get to the note section if the section header + // table isn't loaded. + if let Some(path) = &dumper.mappings[map_idx].name { + let path = std::path::Path::new(&path); + if path.exists() { + log::debug!("failed to get build id from process memory ({e}), attempting to retrieve from {}", path.display()); + return BuildId::read_from_file(path) + .map_err(errors::DumperError::ModuleReaderError); + } + log::debug!( + "not attempting to get build id from {}: path does not exist", + path.display() + ); + } + Err(e) + }) + .unwrap_or_else(|e| { + log::warn!("failed to get build id for mapping: {e}"); + BuildId(Vec::new()) + }); // If the identifier is all 0, its an uninteresting mapping (bmc#1676109) if identifier.is_empty() || identifier.iter().all(|&x| x == 0) { continue; } + // SONAME should always be accessible through program headers alone, so we don't really + // need to fall back to trying to read from the mapping file. let soname = dumper .from_process_memory_for_index(map_idx) .ok()