From fd462faaaac7cf8082e1b1ed3063993acd81cbcd Mon Sep 17 00:00:00 2001
From: Chris Martin
Date: Thu, 17 Jul 2025 10:58:23 -0400
Subject: [PATCH 1/2] Use debugger rendez-vous to obtain memory mappings
---
Cargo.lock | 1 +
Cargo.toml | 1 +
src/lib.rs | 6 +
src/linux/maps_reader.rs | 277 +++++++++++++++++++++++-
src/linux/mem_reader.rs | 95 ++++++--
src/linux/minidump_writer/errors.rs | 12 +-
src/linux/minidump_writer/mod.rs | 112 +++++++---
tests/linux_minidump_writer.rs | 23 --
tests/linux_minidump_writer_failspot.rs | 40 ++++
9 files changed, 493 insertions(+), 74 deletions(-)
create mode 100644 tests/linux_minidump_writer_failspot.rs
diff --git a/Cargo.lock b/Cargo.lock
index d27a5b3e..0a2781b4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1466,6 +1466,7 @@ dependencies = [
"minidump-common",
"minidump-unwind",
"nix",
+ "plain",
"procfs-core",
"scroll 0.12.0",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index b37595af..3aa32f72 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ thiserror = "2.0"
libc = "0.2"
goblin = "0.9.2"
memmap2 = "0.9"
+plain = { version = "0.2.3", default-features = false }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = { version = "0.29", default-features = false, features = [
diff --git a/src/lib.rs b/src/lib.rs
index 67181b4a..a4154941 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,8 @@
+// Because of the nature of this crate, there are lots of times we cast aliased types to `u64`
+// Often, on 64-bit platforms, it's already that, so Clippy gets upset at the u64-to-u64
+// conversion.
+#![allow(clippy::useless_conversion)]
+
cfg_if::cfg_if! {
if #[cfg(any(target_os = "linux", target_os = "android"))] {
mod linux;
@@ -28,5 +33,6 @@ failspot::failspot_name! {
ThreadName,
SuspendThreads,
CpuInfoFileOpen,
+ EnumerateMappingsFromProc,
}
}
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index 7f6e81e2..d05867cc 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -1,9 +1,17 @@
use {
- super::{auxv::AuxvType, module_reader::ModuleReaderError, serializers::*},
+ super::{
+ auxv::AuxvType, mem_reader::MemReader, module_reader::ModuleReaderError, serializers::*,
+ Pid,
+ },
crate::serializers::*,
byteorder::{NativeEndian, ReadBytesExt},
- goblin::elf,
+ elf::{
+ dynamic::{Dyn, DT_DEBUG},
+ header::Header as ElfHeader,
+ program_header::{ProgramHeader, PF_R, PF_W, PF_X, PT_DYNAMIC, PT_LOAD, PT_PHDR},
+ },
memmap2::{Mmap, MmapOptions},
+ plain::Plain,
procfs_core::process::{MMPermissions, MMapPath, MemoryMaps},
std::{
ffi::{OsStr, OsString},
@@ -14,6 +22,11 @@ use {
},
};
+#[cfg(not(target_pointer_width = "64"))]
+use goblin::elf32 as elf;
+#[cfg(target_pointer_width = "64")]
+use goblin::elf64 as elf;
+
pub const LINUX_GATE_LIBRARY_NAME: &str = "linux-gate.so";
pub const DELETED_SUFFIX: &[u8] = b" (deleted)";
@@ -98,6 +111,68 @@ pub enum MapsReaderError {
SymlinkError(std::path::PathBuf, std::path::PathBuf),
}
+#[allow(non_camel_case_types)]
+#[repr(C)]
+#[derive(Debug)]
+struct r_debug {
+ r_version: std::ffi::c_int,
+ r_map: usize,
+ r_brk: usize,
+ r_state: u8,
+ r_ldbase: usize,
+}
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+#[derive(Debug)]
+struct link_map {
+ l_addr: usize,
+ l_name: usize,
+ l_ld: usize,
+ l_next: usize,
+ l_prev: usize,
+}
+
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum FromDebuggerRendezvousError {
+ #[error("failed to read program header table")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadProgramHeaderTableFailed(#[source] std::io::Error),
+ #[error("program header table missing self-pointer")]
+ ProgramHeaderTableNoSelf,
+ #[error("program header table missing dynamic section")]
+ ProgramHeaderTableNoDynamic,
+ #[error("failed to read ELF header")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadElfHeaderFailed(#[source] std::io::Error),
+ #[error("ELF header had invalid identifer bytes: `{0:?}`")]
+ InvalidElfSignature([u8; 4]),
+ #[error("unexpected size for dynamic section `{0}`")]
+ InvalidDynamicSectionSize(usize),
+ #[error("dynamic section missing NULL entry")]
+ DynamicSectionMissingTerminator,
+ #[error("failed to read dynamic section")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadDynamicSectionFailed(#[source] std::io::Error),
+ #[error("failed to find DT_DEBUG entry in dynamic section")]
+ MissingDebugEntry,
+ #[error("failed to read debugger rendezvous address")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadDebuggerRendezvousAddressFailed(#[source] std::io::Error),
+ #[error("unexpected debugger rendezvous version '{0}'")]
+ UnexpectedDebuggerRendezvousVersion(i32),
+ #[error("an error occurred iterating the link_map linked list")]
+ IterateLinkMapFailed(#[source] Box),
+ #[error("failed reading link_map entry")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadLinkMapEntryFailed(#[source] std::io::Error),
+ #[error("invalid link entry")]
+ InvalidLinkEntry,
+ #[error("failed reading module name")]
+ #[serde(serialize_with = "serialize_io_error")]
+ ReadModuleNameFailed(#[source] std::io::Error),
+}
+
fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
match pathname {
Some(x) => x.as_bytes().contains(&b'/'),
@@ -130,6 +205,187 @@ impl MappingInfo {
self.start_address + self.size
}
+ pub fn from_debugger_rendezvous(
+ pid: Pid,
+ program_header_table_address: usize,
+ program_header_count: usize,
+ ) -> std::result::Result, FromDebuggerRendezvousError> {
+ use FromDebuggerRendezvousError as E;
+
+ let mut memory_reader = MemReader::new(pid);
+
+ let program_headers: Vec = memory_reader
+ .read_pod_vec(program_header_table_address, program_header_count)
+ .map_err(E::ReadProgramHeaderTableFailed)?;
+
+ let program_header_table_virtual_address = program_headers
+ .iter()
+ .find_map(|hdr| (hdr.p_type == PT_PHDR).then_some(hdr.p_vaddr))
+ .map(|a| usize::try_from(a).unwrap())
+ .ok_or(E::ProgramHeaderTableNoSelf)?;
+
+ let (dynamic_segment_virtual_address, dynamic_segment_size) = program_headers
+ .iter()
+ .find_map(|hdr| (hdr.p_type == PT_DYNAMIC).then_some((hdr.p_vaddr, hdr.p_memsz)))
+ .map(|(a, b)| (usize::try_from(a).unwrap(), usize::try_from(b).unwrap()))
+ .ok_or(E::ProgramHeaderTableNoDynamic)?;
+
+ let elf_header_address =
+ program_header_table_address - program_header_table_virtual_address;
+ let dynamic_segment_address = elf_header_address + dynamic_segment_virtual_address;
+
+ // Let's make sure we can locate and parse the ELF header to ensure we didn't somehow read garbage.
+ let elf_header: ElfHeader = memory_reader
+ .read_pod(elf_header_address)
+ .map_err(E::ReadElfHeaderFailed)?;
+
+ validate_elf_signature(&elf_header)?;
+
+ // Check that the dynamic section size is a multiple of the size of a dynamic entry
+ if dynamic_segment_size % std::mem::size_of::() != 0 {
+ return Err(E::InvalidDynamicSectionSize(dynamic_segment_size));
+ }
+
+ let dynamic_section: Vec = memory_reader
+ .read_pod_vec(
+ dynamic_segment_address,
+ dynamic_segment_size / std::mem::size_of::(),
+ )
+ .map_err(E::ReadDynamicSectionFailed)?;
+
+ if dynamic_section.last() != Some(&Dyn::default()) {
+ return Err(E::DynamicSectionMissingTerminator);
+ }
+
+ let debugger_rendezvous_address = dynamic_section
+ .iter()
+ .find_map(|d| (u64::from(d.d_tag) == DT_DEBUG).then_some(d.d_val))
+ .map(|a| usize::try_from(a).unwrap())
+ .ok_or(E::MissingDebugEntry)?;
+
+ let debugger_rendezvous: r_debug = memory_reader
+ .read_pod(debugger_rendezvous_address)
+ .map_err(E::ReadDebuggerRendezvousAddressFailed)?;
+
+ if debugger_rendezvous.r_version != 1 {
+ return Err(E::UnexpectedDebuggerRendezvousVersion(
+ debugger_rendezvous.r_version,
+ ));
+ }
+
+ Self::read_link_map(&mut memory_reader, debugger_rendezvous.r_map)
+ .map_err(|e| E::IterateLinkMapFailed(Box::new(e)))
+ }
+
+ fn read_link_map(
+ memory_reader: &mut MemReader,
+ link_map_address: usize,
+ ) -> std::result::Result, FromDebuggerRendezvousError> {
+ use FromDebuggerRendezvousError as E;
+
+ let mut link: link_map = memory_reader
+ .read_pod(link_map_address)
+ .map_err(E::ReadLinkMapEntryFailed)?;
+
+ if link.l_prev != 0 {
+ return Err(E::InvalidLinkEntry);
+ }
+
+ let mut result: Vec = Vec::new();
+
+ loop {
+ let name = {
+ let mut buf = Vec::new();
+ memory_reader
+ .read_until(link.l_name, 0, &mut buf)
+ .map_err(E::ReadModuleNameFailed)?;
+ buf.pop();
+ OsString::from(String::from_utf8(buf).unwrap())
+ };
+
+ let module_elf_header_address = link.l_addr;
+ let module_elf_header: ElfHeader = memory_reader
+ .read_pod(module_elf_header_address)
+ .map_err(E::ReadElfHeaderFailed)?;
+ validate_elf_signature(&module_elf_header)?;
+
+ let module_program_header_virtual_address =
+ usize::try_from(module_elf_header.e_phoff).unwrap();
+ let module_program_header_address =
+ module_elf_header_address + module_program_header_virtual_address;
+ let module_program_header_len = usize::from(module_elf_header.e_phnum);
+ let module_program_headers: Vec = memory_reader
+ .read_pod_vec(module_program_header_address, module_program_header_len)
+ .map_err(E::ReadProgramHeaderTableFailed)?;
+
+ for module_program_header in module_program_headers
+ .iter()
+ .filter(|x| x.p_type == PT_LOAD)
+ {
+ let segment_virtual_address =
+ usize::try_from(module_program_header.p_vaddr).unwrap();
+ let segment_address = module_elf_header_address + segment_virtual_address;
+ let segment_len = usize::try_from(module_program_header.p_memsz).unwrap();
+ let segment_end_address = segment_address + segment_len;
+ let segment_offset = usize::try_from(module_program_header.p_offset).unwrap();
+
+ // Round each of these down or up to the nearest page
+ let segment_address = segment_address / 4096 * 4096;
+ let segment_offset = segment_offset / 4096 * 4096;
+ let segment_end_address = segment_end_address.div_ceil(4096) * 4096;
+ let segment_len = segment_end_address - segment_address;
+
+ let mut permissions = MMPermissions::empty();
+ if module_program_header.p_flags & PF_R != 0 {
+ permissions.insert(MMPermissions::READ);
+ }
+ if module_program_header.p_flags & PF_W != 0 {
+ permissions.insert(MMPermissions::WRITE);
+ }
+ if module_program_header.p_flags & PF_X != 0 {
+ permissions.insert(MMPermissions::EXECUTE);
+ }
+
+ if let Some(prev_mapping) = result.last_mut() {
+ if prev_mapping.end_address() == segment_address
+ && prev_mapping.name.is_some()
+ && prev_mapping.name.as_deref() == Some(&name)
+ {
+ prev_mapping.system_mapping_info.end_address = segment_end_address;
+ prev_mapping.size = segment_end_address - prev_mapping.start_address;
+ prev_mapping.permissions |= permissions;
+ continue;
+ }
+ }
+
+ let mapping_info = MappingInfo {
+ start_address: segment_address,
+ size: segment_len,
+ // When Android relocation packing causes |start_addr| and |size| to
+ // be modified with a load bias, we need to remember the unbiased
+ // address range. The following structure holds the original mapping
+ // address range as reported by the operating system.
+ system_mapping_info: SystemMappingInfo {
+ start_address: segment_address,
+ end_address: segment_end_address,
+ },
+ offset: segment_offset,
+ permissions,
+ name: Some(name.clone()),
+ };
+ result.push(mapping_info);
+ }
+
+ if link.l_next == 0 {
+ break;
+ }
+ link = memory_reader
+ .read_pod(link.l_next)
+ .map_err(E::ReadLinkMapEntryFailed)?;
+ }
+ Ok(result)
+ }
+
pub fn aggregate(
memory_maps: MemoryMaps,
linux_gate_loc: Option,
@@ -248,7 +504,7 @@ impl MappingInfo {
.map(&File::open(filename)?)?
};
- if mapped_file.is_empty() || mapped_file.len() < elf::header::SELFMAG {
+ if mapped_file.is_empty() || mapped_file.len() < goblin::elf::header::SELFMAG {
return Err(MapsReaderError::MmapSanityCheckFailed);
}
Ok(mapped_file)
@@ -489,6 +745,21 @@ impl PartialEq<(u32, u32, u32, u32)> for SoVersion {
}
}
+unsafe impl Plain for link_map {}
+unsafe impl Plain for r_debug {}
+
+fn validate_elf_signature(
+ elf_header: &ElfHeader,
+) -> std::result::Result<(), FromDebuggerRendezvousError> {
+ if elf_header.e_ident[0..4] == [0x7f, 0x45, 0x4c, 0x46] {
+ Ok(())
+ } else {
+ Err(FromDebuggerRendezvousError::InvalidElfSignature(
+ elf_header.e_ident[0..4].try_into().unwrap(),
+ ))
+ }
+}
+
#[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 {
diff --git a/src/linux/mem_reader.rs b/src/linux/mem_reader.rs
index 4ca9ff34..bae312e5 100644
--- a/src/linux/mem_reader.rs
+++ b/src/linux/mem_reader.rs
@@ -2,7 +2,8 @@
use {
super::{minidump_writer::MinidumpWriter, serializers::*, Pid},
- std::sync::OnceLock,
+ plain::Plain,
+ std::{io, sync::OnceLock},
};
#[derive(Debug)]
@@ -33,10 +34,10 @@ enum Style {
}
#[derive(Debug, thiserror::Error, serde::Serialize)]
-#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")]
+#[error("Copy from process {child} failed (address {address}, offset: {offset}, length: {length})")]
pub struct CopyFromProcessError {
pub child: Pid,
- pub src: usize,
+ pub address: usize,
pub offset: usize,
pub length: usize,
#[serde(serialize_with = "serialize_nix_error")]
@@ -120,18 +121,76 @@ impl MemReader {
Ok(output)
}
- pub fn read(&self, src: usize, dst: &mut [u8]) -> Result {
+ pub fn read_pod(&mut self, address: usize) -> io::Result {
+ fn as_bytes_mut(obj: &mut T) -> &mut [u8] {
+ unsafe {
+ std::slice::from_raw_parts_mut(obj as *mut _ as *mut u8, std::mem::size_of::())
+ }
+ }
+ // Safety: All of this is safe to do because `Plain` is an unsafe trait that may only be
+ // implemented on types that are valid for every possible bit pattern, so there is nothing
+ // that we could read from the other process that isn't a valid value for our type.
+ let mut pod_obj: T = unsafe { std::mem::zeroed() };
+ let bytes = as_bytes_mut(&mut pod_obj);
+ self.read_exact(address, bytes)?;
+ Ok(pod_obj)
+ }
+
+ pub fn read_pod_vec(
+ &mut self,
+ mut address: usize,
+ count: usize,
+ ) -> io::Result> {
+ let mut v = Vec::with_capacity(count);
+ for _ in 0..count {
+ v.push(self.read_pod(address)?);
+ address += std::mem::size_of::();
+ }
+ Ok(v)
+ }
+
+ pub fn read_until(
+ &mut self,
+ mut address: usize,
+ terminator: u8,
+ buf: &mut Vec,
+ ) -> io::Result {
+ let start_len = buf.len();
+ let mut b = [0u8];
+ while self.read(address, &mut b).map_err(io::Error::other)? > 0 {
+ buf.push(b[0]);
+ if b[0] == terminator {
+ break;
+ }
+ address += 1;
+ }
+ Ok(buf.len() - start_len)
+ }
+
+ pub fn read_exact(&mut self, mut address: usize, mut dst: &mut [u8]) -> io::Result<()> {
+ while !dst.is_empty() {
+ let bytes_read = self.read(address, dst).map_err(io::Error::other)?;
+ if bytes_read == 0 {
+ return Err(io::ErrorKind::UnexpectedEof.into());
+ }
+ address += bytes_read;
+ dst = &mut dst[bytes_read..];
+ }
+ Ok(())
+ }
+
+ pub fn read(&self, address: usize, dst: &mut [u8]) -> Result {
if let Some(rs) = self.style.get() {
let res = match rs {
- Style::VirtualMem => Self::vmem(self.pid, src, dst).map_err(|s| (s, 0)),
- Style::File(file) => Self::file(file, src, dst).map_err(|s| (s, 0)),
- Style::Ptrace => Self::ptrace(self.pid, src, dst),
+ Style::VirtualMem => Self::vmem(self.pid, address, dst).map_err(|s| (s, 0)),
+ Style::File(file) => Self::file(file, address, dst).map_err(|s| (s, 0)),
+ Style::Ptrace => Self::ptrace(self.pid, address, dst),
Style::Unavailable { ptrace, .. } => Err((*ptrace, 0)),
};
return res.map_err(|(source, offset)| CopyFromProcessError {
child: self.pid.as_raw(),
- src,
+ address,
offset,
length: dst.len(),
source,
@@ -141,7 +200,7 @@ impl MemReader {
const DOUBLE_INIT_MSG: &str = "somehow MemReader initialized twice";
// Attempt to read in order of speed
- let vmem = match Self::vmem(self.pid, src, dst) {
+ let vmem = match Self::vmem(self.pid, address, dst) {
Ok(len) => {
self.style.set(Style::VirtualMem).expect(DOUBLE_INIT_MSG);
return Ok(len);
@@ -150,7 +209,7 @@ impl MemReader {
};
let file = match std::fs::File::open(format!("/proc/{}/mem", self.pid)) {
- Ok(file) => match Self::file(&file, src, dst) {
+ Ok(file) => match Self::file(&file, address, dst) {
Ok(len) => {
self.style.set(Style::File(file)).expect(DOUBLE_INIT_MSG);
return Ok(len);
@@ -162,7 +221,7 @@ impl MemReader {
)),
};
- let ptrace = match Self::ptrace(self.pid, src, dst) {
+ let ptrace = match Self::ptrace(self.pid, address, dst) {
Ok(len) => {
self.style.set(Style::Ptrace).expect(DOUBLE_INIT_MSG);
return Ok(len);
@@ -175,7 +234,7 @@ impl MemReader {
.expect(DOUBLE_INIT_MSG);
Err(CopyFromProcessError {
child: self.pid.as_raw(),
- src,
+ address,
offset: 0,
length: dst.len(),
source: ptrace,
@@ -183,9 +242,9 @@ impl MemReader {
}
#[inline]
- fn vmem(pid: nix::unistd::Pid, src: usize, dst: &mut [u8]) -> Result {
+ fn vmem(pid: nix::unistd::Pid, address: usize, dst: &mut [u8]) -> Result {
let remote = &[nix::sys::uio::RemoteIoVec {
- base: src,
+ base: address,
len: dst.len(),
}];
nix::sys::uio::process_vm_readv(pid, &mut [std::io::IoSliceMut::new(dst)], remote)
@@ -209,14 +268,14 @@ impl MemReader {
#[inline]
fn ptrace(
pid: nix::unistd::Pid,
- src: usize,
+ address: usize,
dst: &mut [u8],
) -> Result {
let mut offset = 0;
let mut chunks = dst.chunks_exact_mut(std::mem::size_of::());
for chunk in chunks.by_ref() {
- let word = nix::sys::ptrace::read(pid, (src + offset) as *mut std::ffi::c_void)
+ let word = nix::sys::ptrace::read(pid, (address + offset) as *mut std::ffi::c_void)
.map_err(|err| (err, offset))?;
chunk.copy_from_slice(&word.to_ne_bytes());
offset += std::mem::size_of::();
@@ -225,7 +284,7 @@ impl MemReader {
// I don't think there would ever be a case where we would not read on word boundaries, but just in case...
let last = chunks.into_remainder();
if !last.is_empty() {
- let word = nix::sys::ptrace::read(pid, (src + offset) as *mut std::ffi::c_void)
+ let word = nix::sys::ptrace::read(pid, (address + offset) as *mut std::ffi::c_void)
.map_err(|err| (err, offset))?;
last.copy_from_slice(&word.to_ne_bytes()[..last.len()]);
}
@@ -244,7 +303,7 @@ impl MinidumpWriter {
length: usize,
) -> Result, CopyFromProcessError> {
let length = std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError {
- src,
+ address: src,
child: pid,
offset: 0,
length,
diff --git a/src/linux/minidump_writer/errors.rs b/src/linux/minidump_writer/errors.rs
index d73a9ce3..b9b240a5 100644
--- a/src/linux/minidump_writer/errors.rs
+++ b/src/linux/minidump_writer/errors.rs
@@ -2,7 +2,7 @@ use {
super::super::{
auxv::AuxvError,
dso_debug::SectionDsoDebugError,
- maps_reader::MapsReaderError,
+ maps_reader::{FromDebuggerRendezvousError, MapsReaderError},
minidump_writer::{
app_memory::SectionAppMemoryError, exception_stream::SectionExceptionStreamError,
handle_data_stream::SectionHandleDataStreamError, mappings::SectionMappingsError,
@@ -216,6 +216,16 @@ pub enum InitError {
SuspendNoThreadsLeft(usize),
#[error("Crash thread does not reference principal mapping")]
PrincipalMappingNotReferenced,
+ #[error("Failed to read module list through Debugger Rendez-Vous: {0}")]
+ ReadModuleViaRendezVousFailed(String),
+ #[error("no program header table address in auxiliary vector")]
+ MissingProgramHeaderTableAddress,
+ #[error("no program header count in auxiliary vector")]
+ MissingProgramHeaderCount,
+ #[error("failed to suspend main thread")]
+ SuspendMainThreadFailed(Box),
+ #[error("failed to obtain mappings from debugger rendez-vous")]
+ MappingsFromDebuggerRendezvousFailed(FromDebuggerRendezvousError),
}
#[derive(Debug, thiserror::Error, serde::Serialize)]
diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs
index 8666e689..8fa3fe7f 100644
--- a/src/linux/minidump_writer/mod.rs
+++ b/src/linux/minidump_writer/mod.rs
@@ -59,7 +59,13 @@ pub mod thread_names_stream;
/// The default timeout after a `SIGSTOP` after which minidump writing proceeds
/// regardless of the process state
-pub const STOP_TIMEOUT: Duration = Duration::from_millis(100);
+pub const STOP_TIMEOUT: Duration = if cfg!(target_os = "android") {
+ // For whatever reason, Android can be terribly slow for stopping processes
+ // This often leads to our tests failing intermittently
+ Duration::from_secs(5)
+} else {
+ Duration::from_millis(100)
+};
#[cfg(target_pointer_width = "32")]
pub const AT_SYSINFO_EHDR: u32 = 33;
@@ -276,15 +282,6 @@ impl MinidumpWriter {
soft_errors.push(InitError::EnumerateThreadsFailed(Box::new(e)));
}
- // Same with mappings -- Some information is still better than no information!
- if let Err(e) = self.enumerate_mappings() {
- soft_errors.push(InitError::EnumerateMappingsFailed(Box::new(e)));
- }
-
- self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)?
- .expect("page size apparently unlimited: doesn't make sense.")
- as usize;
-
let threads_count = self.threads.len();
self.suspend_threads(soft_errors.subwriter(InitError::SuspendThreadsErrors));
@@ -293,6 +290,15 @@ impl MinidumpWriter {
soft_errors.push(InitError::SuspendNoThreadsLeft(threads_count));
}
+ // Same with mappings -- Some information is still better than no information!
+ if let Err(e) = self.enumerate_mappings(&mut soft_errors) {
+ soft_errors.push(InitError::EnumerateMappingsFailed(Box::new(e)));
+ }
+
+ self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)?
+ .expect("page size apparently unlimited: doesn't make sense.")
+ as usize;
+
#[cfg(target_os = "android")]
{
late_process_mappings(self.process_id, &mut self.mappings)?;
@@ -753,23 +759,14 @@ impl MinidumpWriter {
Ok(())
}
- fn enumerate_mappings(&mut self) -> Result<(), InitError> {
- // linux_gate_loc is the beginning of the kernel's mapping of
- // linux-gate.so in the process. It doesn't actually show up in the
- // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
- // aux vector entry, which gives the information necessary to special
- // case its entry when creating the list of mappings.
- // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
- // information.
- let maps_path = format!("/proc/{}/maps", self.process_id);
- let maps_file =
- std::fs::File::open(&maps_path).map_err(|e| InitError::IOError(maps_path, e))?;
-
- let maps = procfs_core::process::MemoryMaps::from_read(maps_file)
- .map_err(InitError::ReadProcessMapFileFailed)?;
-
- self.mappings = MappingInfo::aggregate(maps, self.auxv.get_linux_gate_address())
- .map_err(InitError::AggregateMappingsFailed)?;
+ fn enumerate_mappings(
+ &mut self,
+ mut soft_errors: impl WriteErrorList,
+ ) -> Result<(), InitError> {
+ let mut mappings = self.enumerate_mappings_via_proc_maps().or_else(|e| {
+ soft_errors.push(e);
+ self.enumerate_mappings_via_debugger_rendezvous()
+ })?;
// Although the initial executable is usually the first mapping, it's not
// guaranteed (see http://crosbug.com/25355); therefore, try to use the
@@ -784,16 +781,73 @@ impl MinidumpWriter {
// format assumes the first module is the one that corresponds to the main
// executable (as codified in
// processor/minidump.cc:MinidumpModuleList::GetMainModule()).
- if let Some(entry_mapping_idx) = self.mappings.iter().position(|mapping| {
+ if let Some(entry_mapping_idx) = mappings.iter().position(|mapping| {
(mapping.start_address..mapping.start_address + mapping.size)
.contains(&entry_point_loc)
}) {
- self.mappings.swap(0, entry_mapping_idx);
+ mappings.swap(0, entry_mapping_idx);
}
}
+
+ self.mappings = mappings;
+
Ok(())
}
+ fn enumerate_mappings_via_debugger_rendezvous(
+ &mut self,
+ ) -> Result, InitError> {
+ let Some(program_header_table_address) = self
+ .auxv
+ .get_program_header_address()
+ .map(|x| usize::try_from(x).unwrap())
+ else {
+ return Err(InitError::MissingProgramHeaderTableAddress);
+ };
+
+ let Some(program_header_count) = self
+ .auxv
+ .get_program_header_count()
+ .map(|x| usize::try_from(x).unwrap())
+ else {
+ return Err(InitError::MissingProgramHeaderCount);
+ };
+
+ let mappings = MappingInfo::from_debugger_rendezvous(
+ self.process_id,
+ program_header_table_address,
+ program_header_count,
+ )
+ .map_err(InitError::MappingsFromDebuggerRendezvousFailed)?;
+
+ Ok(mappings)
+ }
+
+ fn enumerate_mappings_via_proc_maps(&mut self) -> Result, InitError> {
+ // linux_gate_loc is the beginning of the kernel's mapping of
+ // linux-gate.so in the process. It doesn't actually show up in the
+ // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR
+ // aux vector entry, which gives the information necessary to special
+ // case its entry when creating the list of mappings.
+ // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
+ // information.
+ let maps_path = format!("/proc/{}/maps", self.process_id);
+ let maps_file = failspot!(if EnumerateMappingsFromProc {
+ Err(std::io::Error::other("fake I/O error reading maps file"))
+ } else {
+ std::fs::File::open(&maps_path)
+ })
+ .map_err(|e| InitError::IOError(maps_path, e))?;
+
+ let maps = procfs_core::process::MemoryMaps::from_read(maps_file)
+ .map_err(InitError::ReadProcessMapFileFailed)?;
+
+ let mappings = MappingInfo::aggregate(maps, self.auxv.get_linux_gate_address())
+ .map_err(InitError::AggregateMappingsFailed)?;
+
+ Ok(mappings)
+ }
+
/// Read thread info from /proc/$pid/status.
/// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable,
/// these members are set to -1. Returns true if all three members are
diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs
index 0c7a1884..306e15a8 100644
--- a/tests/linux_minidump_writer.rs
+++ b/tests/linux_minidump_writer.rs
@@ -785,26 +785,3 @@ fn with_deleted_binary() {
// The 'age'/appendix, always 0 on non-windows targets
assert_eq!(did.appendix(), 0);
}
-
-#[test]
-fn memory_info_list_stream() {
- let mut child = start_child_and_wait_for_threads(1);
- let pid = child.id() as i32;
-
- let mut tmpfile = tempfile::Builder::new()
- .prefix("memory_info_list_stream")
- .tempfile()
- .unwrap();
-
- // Write a minidump
- MinidumpWriterConfig::new(pid, pid)
- .write(&mut tmpfile)
- .expect("cound not write minidump");
- child.kill().expect("Failed to kill process");
- child.wait().expect("Failed to wait on killed process");
-
- // Ensure the minidump has a MemoryInfoListStream present and has at least one entry.
- let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
- let list: MinidumpMemoryInfoList = dump.get_stream().expect("no memory info list");
- assert!(list.iter().count() > 1);
-}
diff --git a/tests/linux_minidump_writer_failspot.rs b/tests/linux_minidump_writer_failspot.rs
new file mode 100644
index 00000000..332f3864
--- /dev/null
+++ b/tests/linux_minidump_writer_failspot.rs
@@ -0,0 +1,40 @@
+#![cfg(any(target_os = "linux", target_os = "android"))]
+
+use {
+ common::*,
+ minidump::*,
+ minidump_writer::{minidump_writer::MinidumpWriterConfig, FailSpotName},
+};
+
+mod common;
+
+#[test]
+fn memory_info_list_stream() {
+ let mut failspot_client = FailSpotName::testing_client();
+ failspot_client.set_enabled(FailSpotName::EnumerateMappingsFromProc, false);
+ memory_info_list_stream_inner();
+ failspot_client.set_enabled(FailSpotName::EnumerateMappingsFromProc, true);
+ memory_info_list_stream_inner();
+}
+
+fn memory_info_list_stream_inner() {
+ let mut child = start_child_and_wait_for_threads(1);
+ let pid = child.id() as i32;
+
+ let mut tmpfile = tempfile::Builder::new()
+ .prefix("memory_info_list_stream")
+ .tempfile()
+ .unwrap();
+
+ // Write a minidump
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
+ .expect("cound not write minidump");
+ child.kill().expect("Failed to kill process");
+ child.wait().expect("Failed to wait on killed process");
+
+ // Ensure the minidump has a MemoryInfoListStream present and has at least one entry.
+ let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
+ let list: MinidumpMemoryInfoList = dump.get_stream().expect("no memory info list");
+ assert!(list.iter().count() > 1);
+}
From 87483da5345a92c86c195a56009a95e5c5b3ed52 Mon Sep 17 00:00:00 2001
From: Chris Martin
Date: Mon, 8 Sep 2025 15:43:56 -0400
Subject: [PATCH 2/2] Address review from Gabriele
---
src/linux/maps_reader.rs | 357 +++++++++++++++++++++++++++------------
1 file changed, 251 insertions(+), 106 deletions(-)
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index d05867cc..e96a680a 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -111,25 +111,50 @@ pub enum MapsReaderError {
SymlinkError(std::path::PathBuf, std::path::PathBuf),
}
+/// Shared object loading information for the debugger
+///
+/// The Linux dynamic linker fills this info in the Dynamic section of the ELF headers at
+/// runtime. It is known as the "debugger rendez-vous" point and is a legacy structure to assist
+/// debuggers in locating loaded shared modules.
+///
+/// (But we're going to use it for minidump generation purposes.)
+///
+/// https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/link.h;h=b645760402514c4839686aaeade20dd5bb7725dd;hb=HEAD#l40
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
struct r_debug {
+ /// Version number of the protocol
r_version: std::ffi::c_int,
+ /// Address of the first link_map structure
r_map: usize,
+ /// Address of callback function for module map/unmap
r_brk: usize,
+ /// Argument to r_brk on whether mapping is being added, subtracted, or is done
r_state: u8,
+ /// Base address the linker is loaded at
r_ldbase: usize,
}
+/// Information for a single dynamically loaded module
+///
+/// This structure contains important information about a dynamically-loaded module, like its
+/// name and virtual address. It is also a node in a doubly-linked list of such modules.
+///
+/// https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/link.h;h=b645760402514c4839686aaeade20dd5bb7725dd;hb=HEAD#l101
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug)]
struct link_map {
+ /// Base address module was loaded at
l_addr: usize,
+ /// Name of file for module
l_name: usize,
+ /// Address of the Dynamic section
l_ld: usize,
+ /// Address of next node in list
l_next: usize,
+ /// Address of previous node in list
l_prev: usize,
}
@@ -210,182 +235,265 @@ impl MappingInfo {
program_header_table_address: usize,
program_header_count: usize,
) -> std::result::Result, FromDebuggerRendezvousError> {
- use FromDebuggerRendezvousError as E;
-
let mut memory_reader = MemReader::new(pid);
- let program_headers: Vec = memory_reader
- .read_pod_vec(program_header_table_address, program_header_count)
- .map_err(E::ReadProgramHeaderTableFailed)?;
+ let program_headers = Self::read_program_headers(
+ &mut memory_reader,
+ program_header_table_address,
+ program_header_count,
+ )?;
- let program_header_table_virtual_address = program_headers
- .iter()
- .find_map(|hdr| (hdr.p_type == PT_PHDR).then_some(hdr.p_vaddr))
- .map(|a| usize::try_from(a).unwrap())
- .ok_or(E::ProgramHeaderTableNoSelf)?;
-
- let (dynamic_segment_virtual_address, dynamic_segment_size) = program_headers
- .iter()
- .find_map(|hdr| (hdr.p_type == PT_DYNAMIC).then_some((hdr.p_vaddr, hdr.p_memsz)))
- .map(|(a, b)| (usize::try_from(a).unwrap(), usize::try_from(b).unwrap()))
- .ok_or(E::ProgramHeaderTableNoDynamic)?;
+ let program_header_table_virtual_address =
+ Self::find_segment_in_program_headers(&program_headers, PT_PHDR)?.0;
let elf_header_address =
program_header_table_address - program_header_table_virtual_address;
- let dynamic_segment_address = elf_header_address + dynamic_segment_virtual_address;
// Let's make sure we can locate and parse the ELF header to ensure we didn't somehow read garbage.
+ Self::read_elf_header(&mut memory_reader, elf_header_address)?;
+
+ let (dynamic_segment_virtual_address, dynamic_segment_size) =
+ Self::find_segment_in_program_headers(&program_headers, PT_DYNAMIC)?;
+ let dynamic_segment_address = elf_header_address + dynamic_segment_virtual_address;
+
+ let dynamic_section = Self::read_dynamic_section(
+ &mut memory_reader,
+ dynamic_segment_address,
+ dynamic_segment_size,
+ )?;
+
+ let debugger_rendezvous_address = Self::get_rendezvous_address(&dynamic_section)?;
+ let debugger_rendezvous =
+ Self::read_debugger_rendezvous(&mut memory_reader, debugger_rendezvous_address)?;
+
+ Self::read_link_map(&mut memory_reader, debugger_rendezvous.r_map)
+ .map_err(|e| FromDebuggerRendezvousError::IterateLinkMapFailed(Box::new(e)))
+ }
+
+ fn read_program_headers(
+ memory_reader: &mut MemReader,
+ program_header_table_address: usize,
+ program_header_count: usize,
+ ) -> std::result::Result, FromDebuggerRendezvousError> {
+ memory_reader
+ .read_pod_vec(program_header_table_address, program_header_count)
+ .map_err(FromDebuggerRendezvousError::ReadProgramHeaderTableFailed)
+ }
+
+ fn find_segment_in_program_headers(
+ program_headers: &[ProgramHeader],
+ segment_type: u32,
+ ) -> std::result::Result<(usize, usize), FromDebuggerRendezvousError> {
+ program_headers
+ .iter()
+ .find_map(|hdr| (hdr.p_type == segment_type).then_some((hdr.p_vaddr, hdr.p_memsz)))
+ .map(|(a, b)| (usize::try_from(a).unwrap(), usize::try_from(b).unwrap()))
+ .ok_or(FromDebuggerRendezvousError::ProgramHeaderTableNoDynamic)
+ }
+
+ fn read_elf_header(
+ memory_reader: &mut MemReader,
+ elf_header_address: usize,
+ ) -> std::result::Result {
let elf_header: ElfHeader = memory_reader
.read_pod(elf_header_address)
- .map_err(E::ReadElfHeaderFailed)?;
+ .map_err(FromDebuggerRendezvousError::ReadElfHeaderFailed)?;
validate_elf_signature(&elf_header)?;
+ Ok(elf_header)
+ }
+
+ fn read_dynamic_section(
+ memory_reader: &mut MemReader,
+ dynamic_segment_address: usize,
+ dynamic_segment_size: usize,
+ ) -> std::result::Result, FromDebuggerRendezvousError> {
// Check that the dynamic section size is a multiple of the size of a dynamic entry
if dynamic_segment_size % std::mem::size_of::() != 0 {
- return Err(E::InvalidDynamicSectionSize(dynamic_segment_size));
+ return Err(FromDebuggerRendezvousError::InvalidDynamicSectionSize(
+ dynamic_segment_size,
+ ));
}
- let dynamic_section: Vec = memory_reader
+ let dynamic_section = memory_reader
.read_pod_vec(
dynamic_segment_address,
dynamic_segment_size / std::mem::size_of::(),
)
- .map_err(E::ReadDynamicSectionFailed)?;
+ .map_err(FromDebuggerRendezvousError::ReadDynamicSectionFailed)?;
if dynamic_section.last() != Some(&Dyn::default()) {
- return Err(E::DynamicSectionMissingTerminator);
+ return Err(FromDebuggerRendezvousError::DynamicSectionMissingTerminator);
}
- let debugger_rendezvous_address = dynamic_section
+ Ok(dynamic_section)
+ }
+
+ fn get_rendezvous_address(
+ dynamic_section: &[Dyn],
+ ) -> std::result::Result {
+ dynamic_section
.iter()
.find_map(|d| (u64::from(d.d_tag) == DT_DEBUG).then_some(d.d_val))
.map(|a| usize::try_from(a).unwrap())
- .ok_or(E::MissingDebugEntry)?;
+ .ok_or(FromDebuggerRendezvousError::MissingDebugEntry)
+ }
+ fn read_debugger_rendezvous(
+ memory_reader: &mut MemReader,
+ debugger_rendezvous_address: usize,
+ ) -> std::result::Result {
let debugger_rendezvous: r_debug = memory_reader
.read_pod(debugger_rendezvous_address)
- .map_err(E::ReadDebuggerRendezvousAddressFailed)?;
-
+ .map_err(FromDebuggerRendezvousError::ReadDebuggerRendezvousAddressFailed)?;
if debugger_rendezvous.r_version != 1 {
- return Err(E::UnexpectedDebuggerRendezvousVersion(
- debugger_rendezvous.r_version,
- ));
+ return Err(
+ FromDebuggerRendezvousError::UnexpectedDebuggerRendezvousVersion(
+ debugger_rendezvous.r_version,
+ ),
+ );
}
+ Ok(debugger_rendezvous)
+ }
- Self::read_link_map(&mut memory_reader, debugger_rendezvous.r_map)
- .map_err(|e| E::IterateLinkMapFailed(Box::new(e)))
+ fn read_c_string(
+ memory_reader: &mut MemReader,
+ address: usize,
+ ) -> std::result::Result {
+ let mut buf = Vec::new();
+ memory_reader
+ .read_until(address, 0, &mut buf)
+ .map_err(FromDebuggerRendezvousError::ReadModuleNameFailed)?;
+ buf.pop();
+ Ok(String::from_utf8(buf).unwrap())
}
fn read_link_map(
memory_reader: &mut MemReader,
link_map_address: usize,
) -> std::result::Result, FromDebuggerRendezvousError> {
- use FromDebuggerRendezvousError as E;
-
- let mut link: link_map = memory_reader
- .read_pod(link_map_address)
- .map_err(E::ReadLinkMapEntryFailed)?;
-
- if link.l_prev != 0 {
- return Err(E::InvalidLinkEntry);
- }
+ let mut link_maps = LinkMaps::new(link_map_address);
let mut result: Vec = Vec::new();
- loop {
- let name = {
- let mut buf = Vec::new();
- memory_reader
- .read_until(link.l_name, 0, &mut buf)
- .map_err(E::ReadModuleNameFailed)?;
- buf.pop();
- OsString::from(String::from_utf8(buf).unwrap())
- };
+ while let Some(link) = link_maps.next(memory_reader)? {
+ let name = OsString::from(Self::read_c_string(memory_reader, link.l_name)?);
let module_elf_header_address = link.l_addr;
- let module_elf_header: ElfHeader = memory_reader
- .read_pod(module_elf_header_address)
- .map_err(E::ReadElfHeaderFailed)?;
- validate_elf_signature(&module_elf_header)?;
+ let module_elf_header =
+ Self::read_elf_header(memory_reader, module_elf_header_address)?;
let module_program_header_virtual_address =
usize::try_from(module_elf_header.e_phoff).unwrap();
let module_program_header_address =
module_elf_header_address + module_program_header_virtual_address;
let module_program_header_len = usize::from(module_elf_header.e_phnum);
- let module_program_headers: Vec = memory_reader
- .read_pod_vec(module_program_header_address, module_program_header_len)
- .map_err(E::ReadProgramHeaderTableFailed)?;
+
+ let module_program_headers = Self::read_program_headers(
+ memory_reader,
+ module_program_header_address,
+ module_program_header_len,
+ )?;
for module_program_header in module_program_headers
.iter()
.filter(|x| x.p_type == PT_LOAD)
{
- let segment_virtual_address =
- usize::try_from(module_program_header.p_vaddr).unwrap();
- let segment_address = module_elf_header_address + segment_virtual_address;
- let segment_len = usize::try_from(module_program_header.p_memsz).unwrap();
- let segment_end_address = segment_address + segment_len;
- let segment_offset = usize::try_from(module_program_header.p_offset).unwrap();
-
- // Round each of these down or up to the nearest page
- let segment_address = segment_address / 4096 * 4096;
- let segment_offset = segment_offset / 4096 * 4096;
- let segment_end_address = segment_end_address.div_ceil(4096) * 4096;
- let segment_len = segment_end_address - segment_address;
-
- let mut permissions = MMPermissions::empty();
- if module_program_header.p_flags & PF_R != 0 {
- permissions.insert(MMPermissions::READ);
- }
- if module_program_header.p_flags & PF_W != 0 {
- permissions.insert(MMPermissions::WRITE);
- }
- if module_program_header.p_flags & PF_X != 0 {
- permissions.insert(MMPermissions::EXECUTE);
- }
+ let mapping_info = Self::calculate_mapping_info(
+ module_program_header,
+ &name,
+ module_elf_header_address,
+ );
if let Some(prev_mapping) = result.last_mut() {
- if prev_mapping.end_address() == segment_address
+ if prev_mapping.end_address() == mapping_info.start_address
&& prev_mapping.name.is_some()
- && prev_mapping.name.as_deref() == Some(&name)
+ && prev_mapping.name == mapping_info.name
{
- prev_mapping.system_mapping_info.end_address = segment_end_address;
- prev_mapping.size = segment_end_address - prev_mapping.start_address;
- prev_mapping.permissions |= permissions;
+ prev_mapping.system_mapping_info.end_address =
+ mapping_info.system_mapping_info.end_address;
+ prev_mapping.size = prev_mapping.system_mapping_info.end_address
+ - prev_mapping.start_address;
+ prev_mapping.permissions |= mapping_info.permissions;
continue;
}
}
- let mapping_info = MappingInfo {
- start_address: segment_address,
- size: segment_len,
- // When Android relocation packing causes |start_addr| and |size| to
- // be modified with a load bias, we need to remember the unbiased
- // address range. The following structure holds the original mapping
- // address range as reported by the operating system.
- system_mapping_info: SystemMappingInfo {
- start_address: segment_address,
- end_address: segment_end_address,
- },
- offset: segment_offset,
- permissions,
- name: Some(name.clone()),
- };
result.push(mapping_info);
}
-
- if link.l_next == 0 {
- break;
- }
- link = memory_reader
- .read_pod(link.l_next)
- .map_err(E::ReadLinkMapEntryFailed)?;
}
Ok(result)
}
+ fn calculate_mapping_info(
+ module_program_header: &ProgramHeader,
+ name: &OsString,
+ module_elf_header_address: usize,
+ ) -> MappingInfo {
+ let segment_virtual_address = usize::try_from(module_program_header.p_vaddr).unwrap();
+ let segment_address = module_elf_header_address + segment_virtual_address;
+ let segment_len = usize::try_from(module_program_header.p_memsz).unwrap();
+ let segment_end_address = segment_address + segment_len;
+ let segment_offset = usize::try_from(module_program_header.p_offset).unwrap();
+
+ let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
+ assert!(page_size > 0);
+ let page_size = usize::try_from(page_size).unwrap();
+
+ // Round each of these down or up to the nearest page
+ let segment_address = Self::align_down(segment_address, page_size);
+ let segment_offset = Self::align_down(segment_offset, page_size);
+ let segment_end_address = Self::align_up(segment_end_address, page_size);
+ let segment_len = segment_end_address - segment_address;
+
+ let permissions =
+ Self::permissions_from_program_header_flags(module_program_header.p_flags);
+
+ MappingInfo {
+ start_address: segment_address,
+ size: segment_len,
+ // When Android relocation packing causes |start_addr| and |size| to
+ // be modified with a load bias, we need to remember the unbiased
+ // address range. The following structure holds the original mapping
+ // address range as reported by the operating system.
+ system_mapping_info: SystemMappingInfo {
+ start_address: segment_address,
+ end_address: segment_end_address,
+ },
+ offset: segment_offset,
+ permissions,
+ name: Some(name.clone()),
+ }
+ }
+
+ fn permissions_from_program_header_flags(flags: u32) -> MMPermissions {
+ let mut permissions = MMPermissions::empty();
+ if flags & PF_R != 0 {
+ permissions.insert(MMPermissions::READ);
+ }
+ if flags & PF_W != 0 {
+ permissions.insert(MMPermissions::WRITE);
+ }
+ if flags & PF_X != 0 {
+ permissions.insert(MMPermissions::EXECUTE);
+ }
+ permissions
+ }
+
+ fn align_down(val: usize, align: usize) -> usize {
+ val / align * align
+ }
+
+ fn align_up(val: usize, align: usize) -> usize {
+ let result = val / align * align;
+ if val % align != 0 {
+ result + align
+ } else {
+ result
+ }
+ }
+
pub fn aggregate(
memory_maps: MemoryMaps,
linux_gate_loc: Option,
@@ -738,6 +846,43 @@ impl SoVersion {
}
}
+#[derive(Debug)]
+struct LinkMaps {
+ address: usize,
+ first_node: bool,
+}
+
+impl LinkMaps {
+ fn new(first_address: usize) -> LinkMaps {
+ LinkMaps {
+ address: first_address,
+ first_node: true,
+ }
+ }
+ fn next(
+ &mut self,
+ memory_reader: &mut MemReader,
+ ) -> std::result::Result