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