diff --git a/.cargo/config.toml b/.cargo/config.toml index ee35b173..e32d5cfd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -11,4 +11,6 @@ # ] [target.x86_64-linux-android] linker = "x86_64-linux-android30-clang" +# By default the linker _doesn't_ generate a build-id, however we want one for our tests. +rustflags = ["-C", "link-args=-Wl,--build-id=sha1"] runner = [".cargo/android-runner.sh"] diff --git a/src/bin/test.rs b/src/bin/test.rs index 77a54081..d8ac8b60 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -9,6 +9,7 @@ mod linux { use super::*; use minidump_writer::{ minidump_writer::STOP_TIMEOUT, + module_reader, ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR}, LINUX_GATE_LIBRARY_NAME, }; @@ -100,7 +101,7 @@ mod linux { } } let idx = found_exe.unwrap(); - let id = dumper.elf_identifier_for_mapping_index(idx)?; + let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?; dumper.resume_threads()?; assert!(!id.is_empty()); assert!(id.iter().any(|&x| x > 0)); @@ -133,11 +134,12 @@ mod linux { let ppid = getppid().as_raw(); let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; let mut found_linux_gate = false; - for mut mapping in dumper.mappings.clone() { + for mapping in dumper.mappings.clone() { if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) { found_linux_gate = true; dumper.suspend_threads()?; - let id = PtraceDumper::elf_identifier_for_mapping(&mut mapping, ppid)?; + let module_reader::BuildId(id) = + dumper.from_process_memory_for_mapping(&mapping)?; test!(!id.is_empty(), "id-vec is empty")?; test!(id.iter().any(|&x| x > 0), "all id elements are 0")?; dumper.resume_threads()?; diff --git a/src/linux.rs b/src/linux.rs index 31e21c76..0b68c125 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -5,13 +5,13 @@ mod android; pub mod app_memory; pub(crate) mod auxv_reader; -pub mod build_id_reader; pub mod crash_context; mod dso_debug; mod dumper_cpu_info; pub mod errors; pub mod maps_reader; pub mod minidump_writer; +pub mod module_reader; pub mod ptrace_dumper; pub(crate) mod sections; pub mod thread_info; diff --git a/src/linux/build_id_reader.rs b/src/linux/build_id_reader.rs deleted file mode 100644 index 94288b24..00000000 --- a/src/linux/build_id_reader.rs +++ /dev/null @@ -1,319 +0,0 @@ -use crate::errors::BuildIdReaderError as Error; -use crate::minidump_format::GUID; -use goblin::{ - container::{Container, Ctx, Endian}, - elf, -}; - -const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; - -pub trait ModuleMemory { - type Memory: std::ops::Deref; - - fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result; -} - -impl<'a> ModuleMemory for &'a [u8] { - type Memory = Self; - - fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { - self.get(offset as usize..(offset + length) as usize) - .ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - format!("{} out of bounds", offset + length), - ) - }) - } -} - -fn read(mem: &T, offset: u64, length: u64) -> Result { - mem.read_module_memory(offset, length) - .map_err(|error| Error::ReadModuleMemory { - offset, - length, - error, - }) -} - -fn is_executable_section(header: &elf::SectionHeader) -> bool { - header.sh_type == elf::section_header::SHT_PROGBITS - && header.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0 - && header.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0 -} - -/// Return bytes to use as a build id, computed by hashing the given data. -/// -/// This provides `size_of::` bytes to keep identifiers produced by this function compatible -/// with other build ids. -fn build_id_from_bytes(data: &[u8]) -> Vec { - // Only provide mem::size_of(MDGUID) bytes to keep identifiers produced by this - // function backwards-compatible. - data.chunks(std::mem::size_of::()).fold( - vec![0u8; std::mem::size_of::()], - |mut bytes, chunk| { - bytes - .iter_mut() - .zip(chunk.iter()) - .for_each(|(b, c)| *b ^= *c); - bytes - }, - ) -} - -pub fn read_build_id(module_memory: impl ModuleMemory) -> Result, Error> { - let reader = ElfBuildIdReader::new(module_memory)?; - let program_headers = match reader.read_from_program_headers() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - let section = match reader.read_from_section() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - let generated = match reader.generate_from_text() { - Ok(v) => return Ok(v), - Err(e) => Box::new(e), - }; - Err(Error::Aggregate { - program_headers, - section, - generated, - }) -} - -pub struct ElfBuildIdReader { - module_memory: T, - header: elf::Header, - context: Ctx, -} - -impl ElfBuildIdReader { - pub fn new(module_memory: T) -> Result { - // We could use `Ctx::default()` (which defaults to the native system), however to be extra - // permissive we'll just use a 64-bit ("Big") context which would result in the largest - // possible header size. - let header_size = elf::Header::size(Ctx::new(Container::Big, Endian::default())); - let header_data = read(&module_memory, 0, header_size as u64)?; - let header = elf::Elf::parse_header(&header_data)?; - let context = Ctx::new(header.container()?, header.endianness()?); - Ok(ElfBuildIdReader { - module_memory, - header, - context, - }) - } - - /// Read the build id from a program header note. - pub fn read_from_program_headers(&self) -> Result, Error> { - if self.header.e_phoff == 0 { - return Err(Error::NoProgramHeaderNote); - } - let program_headers_data = read( - &self.module_memory, - self.header.e_phoff, - self.header.e_phentsize as u64 * self.header.e_phnum as u64, - )?; - let program_headers = elf::ProgramHeader::parse( - &program_headers_data, - 0, - self.header.e_phnum as usize, - self.context, - )?; - for header in program_headers { - if header.p_type != elf::program_header::PT_NOTE { - continue; - } - if let Ok(Some(result)) = - self.find_build_id_note(header.p_offset, header.p_filesz, header.p_align) - { - return Ok(result); - } - } - Err(Error::NoProgramHeaderNote) - } - - /// Read the build id from a notes section. - pub fn read_from_section(&self) -> Result, Error> { - let section_headers = self.read_section_headers()?; - - let strtab_section_header = section_headers - .get(self.header.e_shstrndx as usize) - .ok_or(Error::NoStrTab)?; - - for header in §ion_headers { - let sh_name = header.sh_name as u64; - if sh_name >= strtab_section_header.sh_size { - log::warn!("invalid sh_name offset"); - continue; - } - if sh_name + NOTE_SECTION_NAME.len() as u64 >= strtab_section_header.sh_size { - // This can't be a match. - continue; - } - let name = read( - &self.module_memory, - strtab_section_header.sh_offset + sh_name, - NOTE_SECTION_NAME.len() as u64, - )?; - if NOTE_SECTION_NAME == &*name { - return match self.find_build_id_note( - header.sh_offset, - header.sh_size, - header.sh_addralign, - ) { - Ok(Some(v)) => Ok(v), - Ok(None) => Err(Error::NoSectionNote), - Err(e) => Err(e), - }; - } - } - - Err(Error::NoSectionNote) - } - - /// Generate a build id by hashing the first page of the text section. - pub fn generate_from_text(&self) -> Result, Error> { - let Some(text_header) = self - .read_section_headers()? - .into_iter() - .find(is_executable_section) - else { - return Err(Error::NoTextSection); - }; - - // Take at most one page of the text section (we assume page size is 4096 bytes). - let len = std::cmp::min(4096, text_header.sh_size); - let text_data = read(&self.module_memory, text_header.sh_offset, len)?; - Ok(build_id_from_bytes(&text_data)) - } - - fn read_section_headers(&self) -> Result { - if self.header.e_shoff == 0 { - return Err(Error::NoSections); - } - - // FIXME Until a version following goblin 0.8.0 is published (with - // `SectionHeader::parse_from`), we read one extra byte preceding the sections so that - // `SectionHeader::parse` doesn't return immediately due to a 0 offset. - - let section_headers_data = read( - &self.module_memory, - self.header.e_shoff - 1, - self.header.e_shentsize as u64 * self.header.e_shnum as u64 + 1, - )?; - let section_headers = elf::SectionHeader::parse( - §ion_headers_data, - 1, - self.header.e_shnum as usize, - self.context, - )?; - Ok(section_headers) - } - - fn find_build_id_note( - &self, - offset: u64, - size: u64, - alignment: u64, - ) -> Result>, Error> { - let notes = read(&self.module_memory, offset, size)?; - for note in (elf::note::NoteDataIterator { - data: ¬es, - // Note that `NoteDataIterator::size` is poorly named, it is actually an end offset. In - // this case since our start offset is 0 we still set it to the size. - size: size as usize, - offset: 0, - ctx: (alignment as usize, self.context), - }) { - let Ok(note) = note else { break }; - if note.name == "GNU" && note.n_type == elf::note::NT_GNU_BUILD_ID { - return Ok(Some(note.desc.to_owned())); - } - } - Ok(None) - } -} - -#[cfg(test)] -mod test { - use super::*; - - /// This is a small (but valid) 64-bit little-endian elf executable with the following layout: - /// * ELF header - /// * program header: text segment - /// * program header: note - /// * section header: null - /// * section header: .text - /// * section header: .note.gnu.build-id - /// * section header: .shstrtab - /// * note header (build id note) - /// * shstrtab - /// * program (calls exit(0)) - const TINY_ELF: &[u8] = &[ - 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x02, 0x00, 0x40, 0x00, - 0x04, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xb0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xf4, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xd0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, - 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, 0x55, 0x00, 0x01, 0x02, - 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x00, - 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x6e, 0x6f, 0x74, 0x65, 0x2e, 0x67, 0x6e, 0x75, - 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e, 0x73, 0x68, 0x73, 0x74, - 0x72, 0x74, 0x61, 0x62, 0x00, 0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, - ]; - - #[test] - fn program_headers() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.read_from_program_headers().unwrap(); - assert_eq!( - id, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] - ); - } - - #[test] - fn section() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.read_from_section().unwrap(); - assert_eq!( - id, - vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] - ); - } - - #[test] - fn text_hash() { - let reader = ElfBuildIdReader::new(TINY_ELF).unwrap(); - let id = reader.generate_from_text().unwrap(); - assert_eq!( - id, - vec![0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ); - } -} diff --git a/src/linux/errors.rs b/src/linux/errors.rs index faf79f4d..29486e47 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -26,7 +26,7 @@ pub enum MapsReaderError { #[error("Couldn't parse as ELF file")] ELFParsingFailed(#[from] goblin::error::Error), #[error("No soname found (filename: {})", .0.to_string_lossy())] - NoSoName(OsString), + NoSoName(OsString, #[source] ModuleReaderError), // parse_from_line() #[error("Map entry malformed: No {0} found")] @@ -115,8 +115,8 @@ pub enum DumperError { TryFromSliceError(#[from] std::array::TryFromSliceError), #[error("Couldn't parse as ELF file")] ELFParsingFailed(#[from] goblin::error::Error), - #[error("No build-id found")] - NoBuildIDFound(#[from] BuildIdReaderError), + #[error("Could not read value from module")] + ModuleReaderError(#[from] ModuleReaderError), #[error("Not safe to open mapping: {}", .0.to_string_lossy())] NotSafeToOpenMapping(OsString), #[error("Failed integer conversion")] @@ -250,7 +250,7 @@ pub enum WriterError { } #[derive(Debug, Error)] -pub enum BuildIdReaderError { +pub enum ModuleReaderError { #[error("failed to read module memory: {length} bytes at {offset}: {error}")] ReadModuleMemory { offset: u64, @@ -266,9 +266,11 @@ pub enum BuildIdReaderError { NoStrTab, #[error("no build id note sections")] NoSectionNote, - #[error("the ELF file contains no sections")] + #[error("the ELF data contains no program headers")] + NoProgramHeaders, + #[error("the ELF data contains no sections")] NoSections, - #[error("the ELF file does not have a .text section from which to generate a build id")] + #[error("the ELF data does not have a .text section from which to generate a build id")] NoTextSection, #[error( "failed to calculate build id\n\ @@ -276,9 +278,26 @@ pub enum BuildIdReaderError { ... from sections: {section}\n\ ... from the text section: {section}" )] - Aggregate { + NoBuildId { program_headers: Box, section: Box, generated: Box, }, + #[error("no dynamic string table section")] + NoDynStrSection, + #[error("a string in the strtab did not have a terminating nul byte")] + StrTabNoNulByte, + #[error("no SONAME found in dynamic linking information")] + NoSoNameEntry, + #[error("no dynamic linking information section")] + NoDynamicSection, + #[error( + "failed to retrieve soname\n\ + ... from program headers: {program_headers}\n\ + ... from sections: {section}" + )] + NoSoName { + program_headers: Box, + section: Box, + }, } diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index eecabae8..66fa2f46 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -256,14 +256,13 @@ impl MappingInfo { /// Find the shared object name (SONAME) by examining the ELF information /// for the mapping. fn so_name(&self) -> Result { - let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; - - let elf_obj = elf::Elf::parse(&mapped_file)?; + use super::module_reader::{ReadFromModule, SoName}; - let soname = elf_obj.soname.ok_or_else(|| { - MapsReaderError::NoSoName(self.name.clone().unwrap_or_else(|| "None".into())) - })?; - Ok(soname.to_string()) + let mapped_file = MappingInfo::get_mmap(&self.name, self.offset)?; + Ok(SoName::read_from_module(&*mapped_file) + .map_err(|e| MapsReaderError::NoSoName(self.name.clone().unwrap_or_default(), e))? + .0 + .to_string()) } #[inline] @@ -273,6 +272,7 @@ impl MappingInfo { pub fn get_mapping_effective_path_name_and_version( &self, + soname: Option, ) -> Result<(PathBuf, String, Option)> { let mut file_path = PathBuf::from(self.name.clone().unwrap_or_default()); @@ -282,7 +282,7 @@ impl MappingInfo { // filesystem name of the module. // Just use the filesystem name if no SONAME is present. - let Ok(file_name) = self.so_name() else { + let Some(file_name) = soname.or_else(|| self.so_name().ok()) else { // file_path := /path/to/libname.so // file_name := libname.so let file_name = file_path @@ -689,7 +689,7 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1 assert_eq!(mappings.len(), 1); let (file_path, file_name, _version) = mappings[0] - .get_mapping_effective_path_name_and_version() + .get_mapping_effective_path_name_and_version(None) .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")); diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs new file mode 100644 index 00000000..a3c9a9ac --- /dev/null +++ b/src/linux/module_reader.rs @@ -0,0 +1,587 @@ +use crate::errors::ModuleReaderError as Error; +use crate::minidump_format::GUID; +use goblin::{ + container::{Container, Ctx, Endian}, + elf, +}; +use std::ffi::CStr; + +const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; + +pub trait ModuleMemory { + type Memory: std::ops::Deref; + + /// Read memory from the module. + fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result; + + /// The base address of the module in memory, if loaded in the address space of a program. + /// The default implementation returns None. + fn base_address(&self) -> Option { + None + } + + /// Whether the module memory is from a module loaded in the address space of a program. + /// The default implementation assumes this to be true if a base address is provided. + fn is_loaded_in_program(&self) -> bool { + self.base_address().is_some() + } +} + +impl<'a> ModuleMemory for &'a [u8] { + type Memory = Self; + + fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { + self.get(offset as usize..(offset + length) as usize) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + format!("{} out of bounds", offset + length), + ) + }) + } +} + +/// Indicate that a ModuleMemory implementation is read from the address space of a program with +/// the given base address. +pub struct ModuleMemoryAtAddress(pub T, pub u64); + +impl ModuleMemory for ModuleMemoryAtAddress { + type Memory = T::Memory; + + fn read_module_memory(&self, offset: u64, length: u64) -> std::io::Result { + self.0.read_module_memory(offset, length) + } + + fn base_address(&self) -> Option { + Some(self.1) + } +} + +fn read(mem: &T, offset: u64, length: u64) -> Result { + mem.read_module_memory(offset, length) + .map_err(|error| Error::ReadModuleMemory { + offset, + length, + error, + }) +} + +fn is_executable_section(header: &elf::SectionHeader) -> bool { + header.sh_type == elf::section_header::SHT_PROGBITS + && header.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0 + && header.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0 +} + +/// Return bytes to use as a build id, computed by hashing the given data. +/// +/// This provides `size_of::` bytes to keep identifiers produced by this function compatible +/// with other build ids. +fn build_id_from_bytes(data: &[u8]) -> Vec { + // Only provide mem::size_of(MDGUID) bytes to keep identifiers produced by this + // function backwards-compatible. + data.chunks(std::mem::size_of::()).fold( + vec![0u8; std::mem::size_of::()], + |mut bytes, chunk| { + bytes + .iter_mut() + .zip(chunk.iter()) + .for_each(|(b, c)| *b ^= *c); + bytes + }, + ) +} + +// `name` should be null-terminated +fn section_header_with_name<'a>( + section_headers: &'a elf::SectionHeaders, + strtab_index: usize, + name: &[u8], + module_memory: &impl ModuleMemory, +) -> Result, Error> { + let strtab_section_header = section_headers.get(strtab_index).ok_or(Error::NoStrTab)?; + for header in section_headers { + let sh_name = header.sh_name as u64; + if sh_name >= strtab_section_header.sh_size { + log::warn!("invalid sh_name offset for {:?}", name); + continue; + } + if sh_name + name.len() as u64 >= strtab_section_header.sh_size { + // This can't be a match. + continue; + } + let n = read( + module_memory, + strtab_section_header.sh_offset + sh_name, + name.len() as u64, + )?; + if name == &*n { + return Ok(Some(header)); + } + } + Ok(None) +} + +/// Types which can be read from an `impl ModuleMemory`. +pub trait ReadFromModule: Sized { + fn read_from_module(module_memory: impl ModuleMemory) -> Result; +} + +/// The module build id. +#[derive(Default, Clone, Debug)] +pub struct BuildId(pub Vec); + +impl ReadFromModule for BuildId { + fn read_from_module(module_memory: impl ModuleMemory) -> Result { + let reader = ModuleReader::new(module_memory)?; + let program_headers = match reader.build_id_from_program_headers() { + Ok(v) => return Ok(BuildId(v)), + Err(e) => Box::new(e), + }; + let section = match reader.build_id_from_section() { + Ok(v) => return Ok(BuildId(v)), + Err(e) => Box::new(e), + }; + let generated = match reader.build_id_generate_from_text() { + Ok(v) => return Ok(BuildId(v)), + Err(e) => Box::new(e), + }; + Err(Error::NoBuildId { + program_headers, + section, + generated, + }) + } +} + +struct DynIter<'a> { + data: &'a [u8], + offset: usize, + ctx: Ctx, +} + +impl<'a> DynIter<'a> { + pub fn new(data: &'a [u8], ctx: Ctx) -> Self { + DynIter { + data, + offset: 0, + ctx, + } + } +} + +impl<'a> Iterator for DynIter<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + use scroll::Pread; + let dyn_: elf::dynamic::Dyn = match self.data.gread_with(&mut self.offset, self.ctx) { + Ok(v) => v, + Err(e) => return Some(Err(e.into())), + }; + if dyn_.d_tag == elf::dynamic::DT_NULL { + None + } else { + Some(Ok(dyn_)) + } + } +} + +/// The module SONAME. +#[derive(Default, Clone, Debug)] +pub struct SoName(pub String); + +impl ReadFromModule for SoName { + fn read_from_module(module_memory: impl ModuleMemory) -> Result { + let reader = ModuleReader::new(module_memory)?; + let program_headers = match reader.soname_from_program_headers() { + Ok(v) => return Ok(SoName(v)), + Err(e) => Box::new(e), + }; + let section = match reader.soname_from_sections() { + Ok(v) => return Ok(SoName(v)), + Err(e) => Box::new(e), + }; + Err(Error::NoSoName { + program_headers, + section, + }) + } +} + +pub struct ModuleReader { + module_memory: T, + header: elf::Header, + context: Ctx, +} + +impl ModuleReader { + pub fn new(module_memory: T) -> Result { + // We could use `Ctx::default()` (which defaults to the native system), however to be extra + // permissive we'll just use a 64-bit ("Big") context which would result in the largest + // possible header size. + let header_size = elf::Header::size(Ctx::new(Container::Big, Endian::default())); + let header_data = read(&module_memory, 0, header_size as u64)?; + let header = elf::Elf::parse_header(&header_data)?; + let context = Ctx::new(header.container()?, header.endianness()?); + Ok(ModuleReader { + module_memory, + header, + context, + }) + } + + /// Read the SONAME using program headers to locate dynamic library information. + pub fn soname_from_program_headers(&self) -> Result { + let program_headers = self.read_program_headers()?; + + let dynamic_segment_header = program_headers + .iter() + .find(|h| h.p_type == elf::program_header::PT_DYNAMIC) + .ok_or(Error::NoDynamicSection)?; + + let dynamic_section: &[u8] = &self.read_segment(dynamic_segment_header)?; + + let mut soname_strtab_offset = None; + let mut strtab_addr = None; + let mut strtab_size = None; + for dyn_ in DynIter::new(dynamic_section, self.context) { + let dyn_ = dyn_?; + match dyn_.d_tag { + elf::dynamic::DT_SONAME => soname_strtab_offset = Some(dyn_.d_val), + elf::dynamic::DT_STRTAB => strtab_addr = Some(dyn_.d_val), + elf::dynamic::DT_STRSZ => strtab_size = Some(dyn_.d_val), + _ => (), + } + } + + match (strtab_addr, strtab_size, soname_strtab_offset) { + (None, _, _) | (_, None, _) => Err(Error::NoDynStrSection), + (_, _, None) => Err(Error::NoSoNameEntry), + (Some(mut addr), Some(size), Some(offset)) => { + if self.module_memory.is_loaded_in_program() { + if let Some(base) = self.module_memory.base_address() { + // If loaded in memory, the address will be altered to be absolute. + if let Some(r) = addr.checked_sub(base) { + addr = r; + } + } + } + self.read_name_from_strtab(addr, size, offset) + } + } + } + + /// Read the SONAME using section headers to locate dynamic library information. + pub fn soname_from_sections(&self) -> Result { + let section_headers = self.read_section_headers()?; + + let dynamic_section_header = section_headers + .iter() + .find(|h| h.sh_type == elf::section_header::SHT_DYNAMIC) + .ok_or(Error::NoDynamicSection)?; + + let dynstr_section_header = + match section_headers.get(dynamic_section_header.sh_link as usize) { + Some(header) if header.sh_type == elf::section_header::SHT_STRTAB => header, + _ => section_header_with_name( + §ion_headers, + self.header.e_shstrndx as usize, + b".dynstr\0", + &self.module_memory, + )? + .ok_or(Error::NoDynStrSection)?, + }; + + let dynamic_section: &[u8] = &read( + &self.module_memory, + self.section_offset(dynamic_section_header), + dynamic_section_header.sh_size, + )?; + + for dyn_ in DynIter::new(dynamic_section, self.context) { + let dyn_ = dyn_?; + if dyn_.d_tag == elf::dynamic::DT_SONAME { + let name_offset = dyn_.d_val; + if name_offset < dynstr_section_header.sh_size { + return self.read_name_from_strtab( + self.section_offset(dynstr_section_header), + dynstr_section_header.sh_size, + name_offset, + ); + } + } + } + + Err(Error::NoSoNameEntry) + } + + /// Read the build id from a program header note. + pub fn build_id_from_program_headers(&self) -> Result, Error> { + let program_headers = self.read_program_headers()?; + for header in program_headers { + if header.p_type != elf::program_header::PT_NOTE { + continue; + } + if let Ok(Some(result)) = + self.find_build_id_note(header.p_offset, header.p_filesz, header.p_align) + { + return Ok(result); + } + } + Err(Error::NoProgramHeaderNote) + } + + /// Read the build id from a notes section. + pub fn build_id_from_section(&self) -> Result, Error> { + let section_headers = self.read_section_headers()?; + + let header = section_header_with_name( + §ion_headers, + self.header.e_shstrndx as usize, + NOTE_SECTION_NAME, + &self.module_memory, + )? + .ok_or(Error::NoSectionNote)?; + + match self.find_build_id_note(header.sh_offset, header.sh_size, header.sh_addralign) { + Ok(Some(v)) => Ok(v), + Ok(None) => Err(Error::NoSectionNote), + Err(e) => Err(e), + } + } + + /// Generate a build id by hashing the first page of the text section. + pub fn build_id_generate_from_text(&self) -> Result, Error> { + let Some(text_header) = self + .read_section_headers()? + .into_iter() + .find(is_executable_section) + else { + return Err(Error::NoTextSection); + }; + + // Take at most one page of the text section (we assume page size is 4096 bytes). + let len = std::cmp::min(4096, text_header.sh_size); + let text_data = read(&self.module_memory, text_header.sh_offset, len)?; + Ok(build_id_from_bytes(&text_data)) + } + + fn read_segment(&self, header: &elf::ProgramHeader) -> Result { + let (offset, size) = if self.module_memory.is_loaded_in_program() { + (header.p_vaddr, header.p_memsz) + } else { + (header.p_offset, header.p_filesz) + }; + + read(&self.module_memory, offset, size) + } + + fn read_name_from_strtab( + &self, + strtab_offset: u64, + strtab_size: u64, + name_offset: u64, + ) -> Result { + let name = read( + &self.module_memory, + strtab_offset + name_offset, + strtab_size - name_offset, + )?; + return CStr::from_bytes_until_nul(&name) + .map(|s| s.to_string_lossy().into_owned()) + .map_err(|_| Error::StrTabNoNulByte); + } + + fn section_offset(&self, header: &elf::SectionHeader) -> u64 { + if self.module_memory.is_loaded_in_program() { + header.sh_addr + } else { + header.sh_offset + } + } + + fn read_program_headers(&self) -> Result { + if self.header.e_phoff == 0 { + return Err(Error::NoProgramHeaders); + } + let program_headers_data = read( + &self.module_memory, + self.header.e_phoff, + self.header.e_phentsize as u64 * self.header.e_phnum as u64, + )?; + let program_headers = elf::ProgramHeader::parse( + &program_headers_data, + 0, + self.header.e_phnum as usize, + self.context, + )?; + Ok(program_headers) + } + + fn read_section_headers(&self) -> Result { + if self.header.e_shoff == 0 { + return Err(Error::NoSections); + } + + // FIXME Until a version following goblin 0.8.0 is published (with + // `SectionHeader::parse_from`), we read one extra byte preceding the sections so that + // `SectionHeader::parse` doesn't return immediately due to a 0 offset. + + let section_headers_data = read( + &self.module_memory, + self.header.e_shoff - 1, + self.header.e_shentsize as u64 * self.header.e_shnum as u64 + 1, + )?; + let section_headers = elf::SectionHeader::parse( + §ion_headers_data, + 1, + self.header.e_shnum as usize, + self.context, + )?; + Ok(section_headers) + } + + fn find_build_id_note( + &self, + offset: u64, + size: u64, + alignment: u64, + ) -> Result>, Error> { + let notes = read(&self.module_memory, offset, size)?; + for note in (elf::note::NoteDataIterator { + data: ¬es, + // Note that `NoteDataIterator::size` is poorly named, it is actually an end offset. In + // this case since our start offset is 0 we still set it to the size. + size: size as usize, + offset: 0, + ctx: (alignment as usize, self.context), + }) { + let Ok(note) = note else { break }; + if note.name == "GNU" && note.n_type == elf::note::NT_GNU_BUILD_ID { + return Ok(Some(note.desc.to_owned())); + } + } + Ok(None) + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// This is a small (but valid) 64-bit little-endian elf executable with the following layout: + /// * ELF header + /// * program header: text segment + /// * program header: note + /// * program header: dynamic + /// * section header: null + /// * section header: .text + /// * section header: .note.gnu.build-id + /// * section header: .shstrtab + /// * section header: .dynamic + /// * section header: .dynstr + /// * note header (build id note) + /// * shstrtab + /// * dynamic (SONAME/STRTAB/STRSZ) + /// * dynstr (SONAME string = libfoo.so.1) + /// * program (calls exit(0)) + const TINY_ELF: &[u8] = &[ + 0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x03, 0x00, 0x40, 0x00, + 0x06, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x68, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x02, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xbd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x47, 0x4e, + 0x55, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x00, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x00, 0x2e, 0x6e, 0x6f, 0x74, 0x65, + 0x2e, 0x67, 0x6e, 0x75, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2d, 0x69, 0x64, 0x00, 0x2e, + 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x61, 0x6d, + 0x69, 0x63, 0x00, 0x2e, 0x64, 0x79, 0x6e, 0x73, 0x74, 0x72, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x6c, 0x69, 0x62, 0x66, 0x6f, 0x6f, 0x2e, 0x73, 0x6f, 0x2e, 0x31, 0x00, 0x6a, 0x3c, + 0x58, 0x31, 0xff, 0x0f, 0x05, + ]; + + #[test] + fn build_id_program_headers() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_from_program_headers().unwrap(); + assert_eq!( + id, + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + ); + } + + #[test] + fn build_id_section() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_from_section().unwrap(); + assert_eq!( + id, + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + ); + } + + #[test] + fn build_id_text_hash() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let id = reader.build_id_generate_from_text().unwrap(); + assert_eq!( + id, + vec![0x6a, 0x3c, 0x58, 0x31, 0xff, 0x0f, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[test] + fn soname_program_headers() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let soname = reader.soname_from_program_headers().unwrap(); + assert_eq!(soname, "libfoo.so.1"); + } + + #[test] + fn soname_section() { + let reader = ModuleReader::new(TINY_ELF).unwrap(); + let soname = reader.soname_from_sections().unwrap(); + assert_eq!(soname, "libfoo.so.1"); + } +} diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index bcb0a8e4..4db81d3d 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -2,9 +2,9 @@ use crate::linux::android::late_process_mappings; use crate::linux::{ auxv_reader::{AuxvType, ProcfsAuxvIter}, - build_id_reader, errors::{DumperError, InitError, ThreadInfoError}, maps_reader::MappingInfo, + module_reader, thread_info::{Pid, ThreadInfo}, }; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -557,28 +557,38 @@ impl PtraceDumper { }) } - pub fn elf_identifier_for_mapping_index(&mut self, idx: usize) -> Result, DumperError> { + pub fn from_process_memory_for_index( + &self, + idx: usize, + ) -> Result { assert!(idx < self.mappings.len()); - Self::elf_identifier_for_mapping(&mut self.mappings[idx], self.pid) + self.from_process_memory_for_mapping(&self.mappings[idx]) } - pub fn elf_identifier_for_mapping( - mapping: &mut MappingInfo, - pid: Pid, - ) -> Result, DumperError> { - let result = if pid == std::process::id().try_into()? { + pub fn from_process_memory_for_mapping( + &self, + mapping: &MappingInfo, + ) -> Result { + if std::process::id() + .try_into() + .map(|v: Pid| v == self.pid) + .unwrap_or(false) + { let mem_slice = unsafe { std::slice::from_raw_parts(mapping.start_address as *const u8, mapping.size) }; - build_id_reader::read_build_id(mem_slice) + T::read_from_module(module_reader::ModuleMemoryAtAddress( + mem_slice, + mapping.start_address as u64, + )) } else { - struct ProcessReader { + struct ProcessModuleMemory { pid: Pid, start_address: u64, } - impl build_id_reader::ModuleMemory for ProcessReader { + impl module_reader::ModuleMemory for ProcessModuleMemory { type Memory = Vec; fn read_module_memory( @@ -594,14 +604,17 @@ impl PtraceDumper { ) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) } + + fn base_address(&self) -> Option { + Some(self.start_address) + } } - build_id_reader::read_build_id(ProcessReader { - pid, + T::read_from_module(ProcessModuleMemory { + pid: self.pid, start_address: mapping.start_address as u64, }) - }; - - result.map_err(|e| e.into()) + } + .map_err(|e| e.into()) } } diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs index 9012ae35..2c45d1e4 100644 --- a/src/linux/sections/mappings.rs +++ b/src/linux/sections/mappings.rs @@ -1,5 +1,6 @@ use super::*; use crate::linux::maps_reader::MappingInfo; +use crate::linux::module_reader::{BuildId, SoName}; /// Write information about the mappings in effect. Because we are using the /// minidump format, the information about the mappings is pretty limited. @@ -23,9 +24,8 @@ pub fn write( { continue; } - // Note: elf_identifier_for_mapping_index() can manipulate the |mapping.name|. - let identifier = dumper - .elf_identifier_for_mapping_index(map_idx) + let BuildId(identifier) = dumper + .from_process_memory_for_index(map_idx) .unwrap_or_default(); // If the identifier is all 0, its an uninteresting mapping (bmc#1676109) @@ -33,14 +33,19 @@ pub fn write( continue; } - let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier)?; + let soname = dumper + .from_process_memory_for_index(map_idx) + .ok() + .map(|SoName(n)| n); + + let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier, soname)?; modules.push(module); } // Next write all the mappings provided by the caller for user in &config.user_mapping_list { // GUID was provided by caller. - let module = fill_raw_module(buffer, &user.mapping, &user.identifier)?; + let module = fill_raw_module(buffer, &user.mapping, &user.identifier, None)?; modules.push(module); } @@ -63,6 +68,7 @@ fn fill_raw_module( buffer: &mut DumpBuf, mapping: &MappingInfo, identifier: &[u8], + soname: Option, ) -> Result { let cv_record = if identifier.is_empty() { // Just zeroes @@ -84,7 +90,7 @@ fn fill_raw_module( }; let (file_path, _, so_version) = mapping - .get_mapping_effective_path_name_and_version() + .get_mapping_effective_path_name_and_version(soname) .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?; let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?; diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs index 6cf75e4b..5983e12b 100644 --- a/tests/linux_minidump_writer.rs +++ b/tests/linux_minidump_writer.rs @@ -5,11 +5,11 @@ use minidump::*; use minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*}; use minidump_writer::{ app_memory::AppMemory, - build_id_reader::read_build_id, crash_context::CrashContext, errors::*, maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, minidump_writer::MinidumpWriter, + module_reader::{BuildId, ReadFromModule}, ptrace_dumper::PtraceDumper, thread_info::Pid, }; @@ -705,7 +705,8 @@ fn with_deleted_binary() { let pid = child.id() as i32; - let mut build_id = read_build_id(mem_slice.as_slice()).expect("Failed to get build_id"); + let BuildId(mut build_id) = + BuildId::read_from_module(mem_slice.as_slice()).expect("Failed to get build_id"); std::fs::remove_file(&binary_copy).expect("Failed to remove binary");