From 1b46f020bb55aa01f5737e22efa68d1a824c2c3e Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 15 Jul 2024 17:27:18 -0400 Subject: [PATCH 1/6] Move current auxv functionality into a new 'auxv' module --- src/linux.rs | 2 +- src/linux/auxv/mod.rs | 48 ++++++++++++++++++++ src/linux/{auxv_reader.rs => auxv/reader.rs} | 31 ++++--------- src/linux/dso_debug.rs | 2 +- src/linux/errors.rs | 13 ++---- src/linux/maps_reader.rs | 2 +- src/linux/ptrace_dumper.rs | 24 +--------- 7 files changed, 66 insertions(+), 56 deletions(-) create mode 100644 src/linux/auxv/mod.rs rename src/linux/{auxv_reader.rs => auxv/reader.rs} (84%) diff --git a/src/linux.rs b/src/linux.rs index 0b68c125..d1f666d9 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -4,7 +4,7 @@ #[cfg(target_os = "android")] mod android; pub mod app_memory; -pub(crate) mod auxv_reader; +pub(crate) mod auxv; pub mod crash_context; mod dso_debug; mod dumper_cpu_info; diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs new file mode 100644 index 00000000..05cdb624 --- /dev/null +++ b/src/linux/auxv/mod.rs @@ -0,0 +1,48 @@ +pub use reader::ProcfsAuxvIter; +use { + crate::linux::thread_info::Pid, + std::{collections::HashMap, fs::File, io::BufReader}, + thiserror::Error, +}; + +mod reader; + +/// The type used in auxv keys and values. +#[cfg(target_pointer_width = "32")] +pub type AuxvType = u32; +/// The type used in auxv keys and values. +#[cfg(target_pointer_width = "64")] +pub type AuxvType = u64; + +/// An auxv key-value pair. +#[derive(Debug, PartialEq, Eq)] +pub struct AuxvPair { + pub key: AuxvType, + pub value: AuxvType, +} + +pub fn read_auxv(pid: Pid) -> Result, AuxvError> { + let auxv_path = format!("/proc/{pid}/auxv"); + let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; + let auxv: HashMap = ProcfsAuxvIter::new(BufReader::new(auxv_file)) + .filter_map(Result::ok) + .map(|x| (x.key, x.value)) + .collect(); + if auxv.is_empty() { + Err(AuxvError::NoAuxvEntryFound(pid)) + } else { + Ok(auxv) + } +} + +#[derive(Debug, Error)] +pub enum AuxvError { + #[error("Failed to open file {0}")] + OpenError(String, #[source] std::io::Error), + #[error("No auxv entry found for PID {0}")] + NoAuxvEntryFound(Pid), + #[error("Invalid auxv format (should not hit EOF before AT_NULL)")] + InvalidFormat, + #[error("IO Error")] + IOError(#[from] std::io::Error), +} diff --git a/src/linux/auxv_reader.rs b/src/linux/auxv/reader.rs similarity index 84% rename from src/linux/auxv_reader.rs rename to src/linux/auxv/reader.rs index 3ed09e5a..dadb9979 100644 --- a/src/linux/auxv_reader.rs +++ b/src/linux/auxv/reader.rs @@ -4,26 +4,15 @@ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be in…substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -use crate::errors::AuxvReaderError; -use byteorder::{NativeEndian, ReadBytesExt}; -use std::fs::File; -use std::io::{BufReader, Read}; -pub type Result = std::result::Result; - -/// The type used in auxv keys and values. -#[cfg(target_pointer_width = "32")] -pub type AuxvType = u32; -/// The type used in auxv keys and values. -#[cfg(target_pointer_width = "64")] -pub type AuxvType = u64; - -/// An auxv key-value pair. -#[derive(Debug, PartialEq, Eq)] -pub struct AuxvPair { - pub key: AuxvType, - pub value: AuxvType, -} +use { + super::{AuxvError, AuxvPair, AuxvType}, + byteorder::{NativeEndian, ReadBytesExt}, + std::{ + fs::File, + io::{BufReader, Read}, + }, +}; /// An iterator across auxv pairs from procfs. pub struct ProcfsAuxvIter { @@ -48,7 +37,7 @@ impl ProcfsAuxvIter { } impl Iterator for ProcfsAuxvIter { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { if !self.keep_going { return None; @@ -65,7 +54,7 @@ impl Iterator for ProcfsAuxvIter { Ok(n) => { if n == 0 { // should not hit EOF before AT_NULL - return Some(Err(AuxvReaderError::InvalidFormat)); + return Some(Err(AuxvError::InvalidFormat)); } read_bytes += n; diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index 01c0a735..c3d94ed6 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -1,5 +1,5 @@ use crate::{ - linux::{auxv_reader::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, + linux::{auxv::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter}, minidump_format::*, }; diff --git a/src/linux/errors.rs b/src/linux/errors.rs index 29486e47..e94c0cce 100644 --- a/src/linux/errors.rs +++ b/src/linux/errors.rs @@ -1,3 +1,4 @@ +use crate::auxv::AuxvError; use crate::dir_section::FileWriterError; use crate::maps_reader::MappingInfo; use crate::mem_writer::MemoryWriterError; @@ -9,10 +10,10 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum InitError { + #[error("failed to read auxv")] + ReadAuxvFailed(AuxvError), #[error("IO error for file {0}")] IOError(String, #[source] std::io::Error), - #[error("No auxv entry found for PID {0}")] - NoAuxvEntryFound(Pid), #[error("crash thread does not reference principal mapping")] PrincipalMappingNotReferenced, #[error("Failed Android specific late init")] @@ -47,14 +48,6 @@ pub enum MapsReaderError { SymlinkError(std::path::PathBuf, std::path::PathBuf), } -#[derive(Debug, Error)] -pub enum AuxvReaderError { - #[error("Invalid auxv format (should not hit EOF before AT_NULL)")] - InvalidFormat, - #[error("IO Error")] - IOError(#[from] std::io::Error), -} - #[derive(Debug, Error)] pub enum CpuInfoError { #[error("IO error for file /proc/cpuinfo")] diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index 66fa2f46..8b89e873 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -1,4 +1,4 @@ -use crate::auxv_reader::AuxvType; +use crate::auxv::AuxvType; use crate::errors::MapsReaderError; use byteorder::{NativeEndian, ReadBytesExt}; use goblin::elf; diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index 4db81d3d..820edd93 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -1,7 +1,7 @@ #[cfg(target_os = "android")] use crate::linux::android::late_process_mappings; use crate::linux::{ - auxv_reader::{AuxvType, ProcfsAuxvIter}, + auxv::AuxvType, errors::{DumperError, InitError, ThreadInfoError}, maps_reader::MappingInfo, module_reader, @@ -20,7 +20,6 @@ use procfs_core::{ use std::{ collections::HashMap, ffi::c_void, - io::BufReader, path, result::Result, time::{Duration, Instant}, @@ -110,7 +109,7 @@ impl PtraceDumper { if let Err(e) = self.stop_process(stop_timeout) { log::warn!("failed to stop process {}: {e}", self.pid); } - self.read_auxv()?; + self.auxv = super::auxv::read_auxv(self.pid).map_err(InitError::ReadAuxvFailed)?; self.enumerate_threads()?; self.enumerate_mappings()?; self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)? @@ -295,25 +294,6 @@ impl PtraceDumper { Ok(()) } - fn read_auxv(&mut self) -> Result<(), InitError> { - let filename = format!("/proc/{}/auxv", self.pid); - let auxv_path = path::PathBuf::from(&filename); - let auxv_file = - std::fs::File::open(auxv_path).map_err(|e| InitError::IOError(filename, e))?; - let input = BufReader::new(auxv_file); - let reader = ProcfsAuxvIter::new(input); - self.auxv = reader - .filter_map(Result::ok) - .map(|x| (x.key, x.value)) - .collect(); - - if self.auxv.is_empty() { - Err(InitError::NoAuxvEntryFound(self.pid)) - } else { - 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 From 0d900ce6d77bfce6ae9a2de9d2a939e219dd85c2 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 15 Jul 2024 20:08:04 -0400 Subject: [PATCH 2/6] Add abstraction for getting needed auxv info --- src/bin/test.rs | 6 ++---- src/linux/auxv/mod.rs | 41 ++++++++++++++++++++++++++++++++++++-- src/linux/dso_debug.rs | 25 ++++++----------------- src/linux/ptrace_dumper.rs | 21 +++++-------------- 4 files changed, 52 insertions(+), 41 deletions(-) diff --git a/src/bin/test.rs b/src/bin/test.rs index d8ac8b60..a192cf69 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -8,9 +8,7 @@ pub type Result = std::result::Result; mod linux { use super::*; use minidump_writer::{ - minidump_writer::STOP_TIMEOUT, - module_reader, - ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR}, + minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper, LINUX_GATE_LIBRARY_NAME, }; use nix::{ @@ -153,7 +151,7 @@ mod linux { fn test_mappings_include_linux_gate() -> Result<()> { let ppid = getppid().as_raw(); let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; - let linux_gate_loc = dumper.auxv[&AT_SYSINFO_EHDR]; + let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap(); test!(linux_gate_loc != 0, "linux_gate_loc == 0")?; let mut found_linux_gate = false; for mapping in &dumper.mappings { diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index 05cdb624..0ac22cb8 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -14,6 +14,23 @@ pub type AuxvType = u32; #[cfg(target_pointer_width = "64")] pub type AuxvType = u64; +#[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))] +mod consts { + use super::AuxvType; + pub const AT_PHDR: AuxvType = 3; + pub const AT_PHNUM: AuxvType = 5; + pub const AT_ENTRY: AuxvType = 9; + pub const AT_SYSINFO_EHDR: AuxvType = 33; +} +#[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))] +mod consts { + use super::AuxvType; + pub const AT_PHDR: AuxvType = libc::AT_PHDR; + pub const AT_PHNUM: AuxvType = libc::AT_PHNUM; + pub const AT_ENTRY: AuxvType = libc::AT_ENTRY; + pub const AT_SYSINFO_EHDR: AuxvType = libc::AT_SYSINFO_EHDR; +} + /// An auxv key-value pair. #[derive(Debug, PartialEq, Eq)] pub struct AuxvPair { @@ -21,7 +38,27 @@ pub struct AuxvPair { pub value: AuxvType, } -pub fn read_auxv(pid: Pid) -> Result, AuxvError> { +#[derive(Debug, Default)] +pub struct AuxvDumpInfo { + map: HashMap, +} + +impl AuxvDumpInfo { + pub fn get_program_header_count(&self) -> Option { + self.map.get(&consts::AT_PHNUM).copied() + } + pub fn get_program_header_address(&self) -> Option { + self.map.get(&consts::AT_PHDR).copied() + } + pub fn get_linux_gate_address(&self) -> Option { + self.map.get(&consts::AT_SYSINFO_EHDR).copied() + } + pub fn get_entry_address(&self) -> Option { + self.map.get(&consts::AT_ENTRY).copied() + } +} + +pub fn read_auxv(pid: Pid) -> Result { let auxv_path = format!("/proc/{pid}/auxv"); let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; let auxv: HashMap = ProcfsAuxvIter::new(BufReader::new(auxv_file)) @@ -31,7 +68,7 @@ pub fn read_auxv(pid: Pid) -> Result, AuxvError> { if auxv.is_empty() { Err(AuxvError::NoAuxvEntryFound(pid)) } else { - Ok(auxv) + Ok(AuxvDumpInfo { map: auxv }) } } diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index c3d94ed6..0676c1b2 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -1,9 +1,8 @@ use crate::{ - linux::{auxv::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, + linux::{auxv::AuxvDumpInfo, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper}, mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter}, minidump_format::*, }; -use std::collections::HashMap; type Result = std::result::Result; @@ -77,26 +76,14 @@ pub struct RDebug { pub fn write_dso_debug_stream( buffer: &mut Buffer, blamed_thread: i32, - auxv: &HashMap, + auxv: &AuxvDumpInfo, ) -> Result { - let at_phnum; - let at_phdr; - #[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))] - { - at_phdr = 3; - at_phnum = 5; - } - #[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))] - { - at_phdr = libc::AT_PHDR; - at_phnum = libc::AT_PHNUM; - } - let phnum_max = *auxv - .get(&at_phnum) + let phnum_max = auxv + .get_program_header_count() .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? as usize; - let phdr = *auxv - .get(&at_phdr) + let phdr = auxv + .get_program_header_address() .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; let ph = PtraceDumper::copy_from_process( diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index 820edd93..3e1eff58 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -1,7 +1,7 @@ #[cfg(target_os = "android")] use crate::linux::android::late_process_mappings; use crate::linux::{ - auxv::AuxvType, + auxv::AuxvDumpInfo, errors::{DumperError, InitError, ThreadInfoError}, maps_reader::MappingInfo, module_reader, @@ -18,7 +18,6 @@ use procfs_core::{ FromRead, ProcError, }; use std::{ - collections::HashMap, ffi::c_void, path, result::Result, @@ -36,7 +35,7 @@ pub struct PtraceDumper { pub pid: Pid, threads_suspended: bool, pub threads: Vec, - pub auxv: HashMap, + pub auxv: AuxvDumpInfo, pub mappings: Vec, pub page_size: usize, } @@ -95,7 +94,7 @@ impl PtraceDumper { pid, threads_suspended: false, threads: Vec::new(), - auxv: HashMap::new(), + auxv: AuxvDumpInfo::default(), mappings: Vec::new(), page_size: 0, }; @@ -302,21 +301,11 @@ impl PtraceDumper { // case its entry when creating the list of mappings. // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more // information. - let linux_gate_loc = *self.auxv.get(&AT_SYSINFO_EHDR).unwrap_or(&0); + let linux_gate_loc = self.auxv.get_linux_gate_address().unwrap_or_default(); // Although the initial executable is usually the first mapping, it's not // guaranteed (see http://crosbug.com/25355); therefore, try to use the // actual entry point to find the mapping. - let at_entry; - #[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))] - { - at_entry = 9; - } - #[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))] - { - at_entry = libc::AT_ENTRY; - } - - let entry_point_loc = *self.auxv.get(&at_entry).unwrap_or(&0); + let entry_point_loc = self.auxv.get_entry_address().unwrap_or_default(); let filename = format!("/proc/{}/maps", self.pid); let errmap = |e| InitError::IOError(filename.clone(), e); let maps_path = path::PathBuf::from(&filename); From 6559c6f5aed5de55ba72fa5467e504c4609293de Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 15 Jul 2024 21:38:13 -0400 Subject: [PATCH 3/6] Add ability for auxv info to be passed from caller --- src/bin/test.rs | 16 +++---- src/linux/auxv/mod.rs | 86 ++++++++++++++++++++++++++++-------- src/linux/dso_debug.rs | 7 ++- src/linux/minidump_writer.rs | 19 +++++++- src/linux/ptrace_dumper.rs | 10 +++-- tests/ptrace_dumper.rs | 16 +++++-- 6 files changed, 116 insertions(+), 38 deletions(-) diff --git a/src/bin/test.rs b/src/bin/test.rs index a192cf69..82c31d31 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -28,13 +28,13 @@ mod linux { fn test_setup() -> Result<()> { let ppid = getppid(); - PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT)?; + PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?; Ok(()) } fn test_thread_list() -> Result<()> { let ppid = getppid(); - let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT)?; + let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?; test!(!dumper.threads.is_empty(), "No threads")?; test!( dumper @@ -50,7 +50,7 @@ mod linux { fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { let ppid = getppid().as_raw(); - let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; + let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?; dumper.suspend_threads()?; let stack_res = PtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?; @@ -72,7 +72,7 @@ mod linux { fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> { let ppid = getppid(); - let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT)?; + let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?; dumper .find_mapping(addr1) .ok_or("No mapping for addr1 found")?; @@ -89,7 +89,7 @@ mod linux { let ppid = getppid().as_raw(); let exe_link = format!("/proc/{}/exe", ppid); let exe_name = std::fs::read_link(exe_link)?.into_os_string(); - let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; + let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?; dumper.suspend_threads()?; let mut found_exe = None; for (idx, mapping) in dumper.mappings.iter().enumerate() { @@ -108,7 +108,7 @@ mod linux { fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> { // Now check that PtraceDumper interpreted the mappings properly. - let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT)?; + let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT, Default::default())?; let mut mapping_count = 0; for map in &dumper.mappings { if map @@ -130,7 +130,7 @@ mod linux { fn test_linux_gate_mapping_id() -> Result<()> { let ppid = getppid().as_raw(); - let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; + let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?; let mut found_linux_gate = false; for mapping in dumper.mappings.clone() { if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) { @@ -150,7 +150,7 @@ mod linux { fn test_mappings_include_linux_gate() -> Result<()> { let ppid = getppid().as_raw(); - let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?; + let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?; let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap(); test!(linux_gate_loc != 0, "linux_gate_loc == 0")?; let mut found_linux_gate = false; diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index 0ac22cb8..d072261a 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -38,37 +38,87 @@ pub struct AuxvPair { pub value: AuxvType, } +#[repr(C)] +#[derive(Clone, Debug)] +pub struct DirectAuxvDumpInfo { + program_header_count: AuxvType, + program_header_address: AuxvType, + linux_gate_address: AuxvType, + entry_address: AuxvType, +} + +impl From for AuxvDumpInfo { + fn from(f: DirectAuxvDumpInfo) -> AuxvDumpInfo { + AuxvDumpInfo { + program_header_count: (f.program_header_count > 0).then_some(f.program_header_count), + program_header_address: (f.program_header_address > 0) + .then_some(f.program_header_address), + linux_gate_address: (f.linux_gate_address > 0).then_some(f.linux_gate_address), + entry_address: (f.entry_address > 0).then_some(f.entry_address), + } + } +} + #[derive(Debug, Default)] pub struct AuxvDumpInfo { - map: HashMap, + program_header_count: Option, + program_header_address: Option, + linux_gate_address: Option, + entry_address: Option, } impl AuxvDumpInfo { + pub fn try_filling_missing_info(&mut self, pid: Pid) -> Result<(), AuxvError> { + if self.is_complete() { + return Ok(()); + } + + let auxv_path = format!("/proc/{pid}/auxv"); + let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; + let auxv: HashMap = ProcfsAuxvIter::new(BufReader::new(auxv_file)) + .filter_map(Result::ok) + .map(|x| (x.key, x.value)) + .collect(); + + if auxv.is_empty() { + return Err(AuxvError::NoAuxvEntryFound(pid)); + } + + if self.program_header_count.is_none() { + self.program_header_count = auxv.get(&consts::AT_PHNUM).copied(); + } + + if self.program_header_address.is_none() { + self.program_header_address = auxv.get(&consts::AT_PHDR).copied(); + } + + if self.linux_gate_address.is_none() { + self.linux_gate_address = auxv.get(&consts::AT_SYSINFO_EHDR).copied(); + } + + if self.entry_address.is_none() { + self.entry_address = auxv.get(&consts::AT_ENTRY).copied(); + } + + Ok(()) + } pub fn get_program_header_count(&self) -> Option { - self.map.get(&consts::AT_PHNUM).copied() + self.program_header_count } pub fn get_program_header_address(&self) -> Option { - self.map.get(&consts::AT_PHDR).copied() + self.program_header_address } pub fn get_linux_gate_address(&self) -> Option { - self.map.get(&consts::AT_SYSINFO_EHDR).copied() + self.linux_gate_address } pub fn get_entry_address(&self) -> Option { - self.map.get(&consts::AT_ENTRY).copied() + self.entry_address } -} - -pub fn read_auxv(pid: Pid) -> Result { - let auxv_path = format!("/proc/{pid}/auxv"); - let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; - let auxv: HashMap = ProcfsAuxvIter::new(BufReader::new(auxv_file)) - .filter_map(Result::ok) - .map(|x| (x.key, x.value)) - .collect(); - if auxv.is_empty() { - Err(AuxvError::NoAuxvEntryFound(pid)) - } else { - Ok(AuxvDumpInfo { map: auxv }) + pub fn is_complete(&self) -> bool { + self.program_header_count.is_some() + && self.program_header_address.is_some() + && self.linux_gate_address.is_some() + && self.entry_address.is_some() } } diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index 0676c1b2..c2f873b0 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -78,10 +78,9 @@ pub fn write_dso_debug_stream( blamed_thread: i32, auxv: &AuxvDumpInfo, ) -> Result { - let phnum_max = auxv - .get_program_header_count() - .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? - as usize; + let phnum_max = + auxv.get_program_header_count() + .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? as usize; let phdr = auxv .get_program_header_address() .ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize; diff --git a/src/linux/minidump_writer.rs b/src/linux/minidump_writer.rs index c83308f5..d184526d 100644 --- a/src/linux/minidump_writer.rs +++ b/src/linux/minidump_writer.rs @@ -1,7 +1,9 @@ use crate::{ + auxv::AuxvDumpInfo, dir_section::{DirSection, DumpBuf}, linux::{ app_memory::AppMemoryList, + auxv::DirectAuxvDumpInfo, crash_context::CrashContext, dso_debug, errors::{InitError, WriterError}, @@ -42,6 +44,7 @@ pub struct MinidumpWriter { pub crash_context: Option, pub crashing_thread_context: CrashingThreadContext, pub stop_timeout: Duration, + pub direct_auxv_dump_info: Option, } // This doesn't work yet: @@ -71,6 +74,7 @@ impl MinidumpWriter { crash_context: None, crashing_thread_context: CrashingThreadContext::None, stop_timeout: STOP_TIMEOUT, + direct_auxv_dump_info: None, } } @@ -117,10 +121,23 @@ impl MinidumpWriter { self } + pub fn set_direct_auxv_dump_info( + &mut self, + direct_auxv_dump_info: DirectAuxvDumpInfo, + ) -> &mut Self { + self.direct_auxv_dump_info = Some(direct_auxv_dump_info); + self + } + /// Generates a minidump and writes to the destination provided. Returns the in-memory /// version of the minidump as well. pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result> { - let mut dumper = PtraceDumper::new(self.process_id, self.stop_timeout)?; + let auxv = self + .direct_auxv_dump_info + .clone() + .map(AuxvDumpInfo::from) + .unwrap_or_default(); + let mut dumper = PtraceDumper::new(self.process_id, self.stop_timeout, auxv)?; dumper.suspend_threads()?; dumper.late_init()?; diff --git a/src/linux/ptrace_dumper.rs b/src/linux/ptrace_dumper.rs index 3e1eff58..e709d06a 100644 --- a/src/linux/ptrace_dumper.rs +++ b/src/linux/ptrace_dumper.rs @@ -89,12 +89,12 @@ fn ptrace_detach(child: Pid) -> Result<(), DumperError> { impl PtraceDumper { /// Constructs a dumper for extracting information of a given process /// with a process ID of |pid|. - pub fn new(pid: Pid, stop_timeout: Duration) -> Result { + pub fn new(pid: Pid, stop_timeout: Duration, auxv: AuxvDumpInfo) -> Result { let mut dumper = PtraceDumper { pid, threads_suspended: false, threads: Vec::new(), - auxv: AuxvDumpInfo::default(), + auxv, mappings: Vec::new(), page_size: 0, }; @@ -108,7 +108,11 @@ impl PtraceDumper { if let Err(e) = self.stop_process(stop_timeout) { log::warn!("failed to stop process {}: {e}", self.pid); } - self.auxv = super::auxv::read_auxv(self.pid).map_err(InitError::ReadAuxvFailed)?; + + if let Err(e) = self.auxv.try_filling_missing_info(self.pid) { + log::warn!("failed trying to fill in missing auxv info: {e}"); + } + self.enumerate_threads()?; self.enumerate_mappings()?; self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)? diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index 089d6542..dfc2976f 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -39,8 +39,12 @@ fn test_thread_list_from_parent() { let num_of_threads = 5; let mut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32; - let mut dumper = PtraceDumper::new(pid, minidump_writer::minidump_writer::STOP_TIMEOUT) - .expect("Couldn't init dumper"); + let mut dumper = PtraceDumper::new( + pid, + minidump_writer::minidump_writer::STOP_TIMEOUT, + Default::default(), + ) + .expect("Couldn't init dumper"); assert_eq!(dumper.threads.len(), num_of_threads); dumper.suspend_threads().expect("Could not suspend threads"); @@ -216,8 +220,12 @@ fn test_sanitize_stack_copy() { let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) .expect("unable to parse mmap_addr"); - let mut dumper = PtraceDumper::new(pid, minidump_writer::minidump_writer::STOP_TIMEOUT) - .expect("Couldn't init dumper"); + let mut dumper = PtraceDumper::new( + pid, + minidump_writer::minidump_writer::STOP_TIMEOUT, + Default::default(), + ) + .expect("Couldn't init dumper"); assert_eq!(dumper.threads.len(), num_of_threads); dumper.suspend_threads().expect("Could not suspend threads"); let thread_info = dumper From 443e389513a4942b44cd6bbf4f7118bd22cb2472 Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 22 Jul 2024 16:49:15 -0400 Subject: [PATCH 4/6] Add documention and improve auxv init from file --- src/linux/auxv/mod.rs | 58 +++++++++++++++++++----------------- src/linux/minidump_writer.rs | 9 +++++- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index d072261a..c5b2393b 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -1,7 +1,7 @@ pub use reader::ProcfsAuxvIter; use { crate::linux::thread_info::Pid, - std::{collections::HashMap, fs::File, io::BufReader}, + std::{fs::File, io::BufReader}, thiserror::Error, }; @@ -38,13 +38,24 @@ pub struct AuxvPair { pub value: AuxvType, } +/// Auxv info that can be passed from crashing process +/// +/// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this +/// information directly using the Linux `getauxval()` call (if possible). +/// +/// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques +/// to obtain it (like reading `/proc/{pid}/auxv`). #[repr(C)] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct DirectAuxvDumpInfo { - program_header_count: AuxvType, - program_header_address: AuxvType, - linux_gate_address: AuxvType, - entry_address: AuxvType, + /// The value of `getauxval(AT_PHNUM)` + pub program_header_count: AuxvType, + /// The value of `getauxval(AT_PHDR)` + pub program_header_address: AuxvType, + /// The value of `getauxval(AT_SYSINFO_EHDR)` + pub linux_gate_address: AuxvType, + /// The value of `getauxval(AT_ENTRY)` + pub entry_address: AuxvType, } impl From for AuxvDumpInfo { @@ -75,29 +86,20 @@ impl AuxvDumpInfo { let auxv_path = format!("/proc/{pid}/auxv"); let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?; - let auxv: HashMap = ProcfsAuxvIter::new(BufReader::new(auxv_file)) - .filter_map(Result::ok) - .map(|x| (x.key, x.value)) - .collect(); - if auxv.is_empty() { - return Err(AuxvError::NoAuxvEntryFound(pid)); - } - - if self.program_header_count.is_none() { - self.program_header_count = auxv.get(&consts::AT_PHNUM).copied(); - } - - if self.program_header_address.is_none() { - self.program_header_address = auxv.get(&consts::AT_PHDR).copied(); - } - - if self.linux_gate_address.is_none() { - self.linux_gate_address = auxv.get(&consts::AT_SYSINFO_EHDR).copied(); - } - - if self.entry_address.is_none() { - self.entry_address = auxv.get(&consts::AT_ENTRY).copied(); + for AuxvPair { key, value } in + ProcfsAuxvIter::new(BufReader::new(auxv_file)).filter_map(Result::ok) + { + let dest_field = match key { + consts::AT_PHNUM => &mut self.program_header_count, + consts::AT_PHDR => &mut self.program_header_address, + consts::AT_SYSINFO_EHDR => &mut self.linux_gate_address, + consts::AT_ENTRY => &mut self.entry_address, + _ => continue, + }; + if dest_field.is_none() { + *dest_field = Some(value); + } } Ok(()) diff --git a/src/linux/minidump_writer.rs b/src/linux/minidump_writer.rs index d184526d..aff337b6 100644 --- a/src/linux/minidump_writer.rs +++ b/src/linux/minidump_writer.rs @@ -1,9 +1,9 @@ +pub use crate::linux::auxv::{AuxvType, DirectAuxvDumpInfo}; use crate::{ auxv::AuxvDumpInfo, dir_section::{DirSection, DumpBuf}, linux::{ app_memory::AppMemoryList, - auxv::DirectAuxvDumpInfo, crash_context::CrashContext, dso_debug, errors::{InitError, WriterError}, @@ -121,6 +121,13 @@ impl MinidumpWriter { self } + /// Directly set important Auxv info determined by the crashing process + /// + /// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this + /// information directly using the Linux `getauxval()` call (if possible). + /// + /// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques + /// to obtain it (like reading `/proc/{pid}/auxv`). pub fn set_direct_auxv_dump_info( &mut self, direct_auxv_dump_info: DirectAuxvDumpInfo, From 67ccced2a9c180759c5ccbe38d653d3dc5bdcc4b Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 22 Jul 2024 18:19:39 -0400 Subject: [PATCH 5/6] Fix broken Android build --- src/linux/auxv/mod.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index c5b2393b..673e8390 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -14,20 +14,14 @@ pub type AuxvType = u32; #[cfg(target_pointer_width = "64")] pub type AuxvType = u64; -#[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))] -mod consts { - use super::AuxvType; - pub const AT_PHDR: AuxvType = 3; - pub const AT_PHNUM: AuxvType = 5; - pub const AT_ENTRY: AuxvType = 9; - pub const AT_SYSINFO_EHDR: AuxvType = 33; -} -#[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))] mod consts { use super::AuxvType; pub const AT_PHDR: AuxvType = libc::AT_PHDR; pub const AT_PHNUM: AuxvType = libc::AT_PHNUM; pub const AT_ENTRY: AuxvType = libc::AT_ENTRY; + #[cfg(target_os = "android")] + pub const AT_SYSINFO_EHDR: AuxvType = 33; + #[cfg(not(target_os = "android"))] pub const AT_SYSINFO_EHDR: AuxvType = libc::AT_SYSINFO_EHDR; } From 282ee6f5facfac0fa50ff741d330fd94787ce5ac Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Mon, 22 Jul 2024 18:31:40 -0400 Subject: [PATCH 6/6] Fix Android build error for real this time --- src/linux/auxv/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index 673e8390..c8ee248a 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -14,14 +14,20 @@ pub type AuxvType = u32; #[cfg(target_pointer_width = "64")] pub type AuxvType = u64; +#[cfg(target_os = "android")] +mod consts { + use super::AuxvType; + pub const AT_PHDR: AuxvType = 3; + pub const AT_PHNUM: AuxvType = 5; + pub const AT_ENTRY: AuxvType = 9; + pub const AT_SYSINFO_EHDR: AuxvType = 33; +} +#[cfg(not(target_os = "android"))] mod consts { use super::AuxvType; pub const AT_PHDR: AuxvType = libc::AT_PHDR; pub const AT_PHNUM: AuxvType = libc::AT_PHNUM; pub const AT_ENTRY: AuxvType = libc::AT_ENTRY; - #[cfg(target_os = "android")] - pub const AT_SYSINFO_EHDR: AuxvType = 33; - #[cfg(not(target_os = "android"))] pub const AT_SYSINFO_EHDR: AuxvType = libc::AT_SYSINFO_EHDR; }