diff --git a/Cargo.lock b/Cargo.lock index ba06cd2..b9f31b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1432,6 +1432,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "uuid", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6976ffc..9a005f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ license = "MIT" bitflags = "2.10" cfg-if = "1.0" crash-context = "0.6" +error-graph = { version = "0.1.1", features = ["serde"] } +failspot = "0.2.0" +# goblin >= 0.10 uses scroll 0.13 +goblin = "0.9.2" log = "0.4" # Type definitions and utilities for working with the Minidump format minidump-common = "0.26" @@ -24,8 +28,6 @@ thiserror = "2.0" [target.'cfg(unix)'.dependencies] libc = "0.2" -# goblin >= 0.10 uses scroll 0.13 -goblin = "0.9" memmap2 = "0.9" [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] @@ -46,6 +48,11 @@ procfs-core = { version = "0.18", default-features = false, features = ["serde1" [target.'cfg(target_os = "windows")'.dependencies] bitflags = "2.4" +windows-sys = { version = ">=0.52", features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", + "Win32_System_ProcessStatus", +] } [target.'cfg(target_os = "macos")'.dependencies] # Binds some additional mac specifics not in libc, note that other dependents diff --git a/src/bin/test.rs b/src/bin/test.rs index c42ff7f..2681343 100644 --- a/src/bin/test.rs +++ b/src/bin/test.rs @@ -77,7 +77,7 @@ mod linux { } fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> { - use minidump_writer::mem_reader::MemReader; + use minidump_writer::process_reader::ProcessReader; let ppid = getppid().as_raw(); let dumper = fail_on_soft_error!( @@ -91,7 +91,7 @@ mod linux { let expected_stack = 0x11223344usize.to_ne_bytes(); let expected_heap = 0x55667788usize.to_ne_bytes(); - let validate = |reader: &mut MemReader| -> Result<()> { + let validate = |reader: &mut ProcessReader| -> Result<()> { let mut val = [0u8; std::mem::size_of::()]; let read = reader.read(stack_var, &mut val)?; assert_eq!(read, val.len()); @@ -106,14 +106,14 @@ mod linux { // virtual mem { - let mut mr = MemReader::for_virtual_mem(ppid); + let mut mr = ProcessReader::for_virtual_mem(ppid); validate(&mut mr) .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?; } // file { - let mut mr = MemReader::for_file(ppid) + let mut mr = ProcessReader::for_file(ppid) .map_err(|err| format!("failed to open `/proc/{ppid}/mem`: {err}"))?; validate(&mut mr) .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?; @@ -121,7 +121,7 @@ mod linux { // ptrace { - let mut mr = MemReader::for_ptrace(ppid); + let mut mr = ProcessReader::for_ptrace(ppid); validate(&mut mr) .map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?; } diff --git a/src/lib.rs b/src/lib.rs index baeb1fc..f941625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,5 +28,7 @@ pub mod dir_section; pub mod mem_writer; pub mod minidump_cpu; pub mod minidump_format; +pub mod module_reader; +pub mod process_reader; mod serializers; diff --git a/src/linux/android.rs b/src/linux/android.rs index 94c89ef..799d349 100644 --- a/src/linux/android.rs +++ b/src/linux/android.rs @@ -1,7 +1,7 @@ use { super::{ - Pid, maps_reader::MappingInfo, mem_reader::CopyFromProcessError, - minidump_writer::MinidumpWriter, + Pid, maps_reader::MappingInfo, minidump_writer::MinidumpWriter, + process_reader::CopyFromProcessError, }, goblin::elf, }; diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs index ab84da4..47e530d 100644 --- a/src/linux/dso_debug.rs +++ b/src/linux/dso_debug.rs @@ -1,6 +1,6 @@ use { super::{ - auxv::AuxvDumpInfo, mem_reader::CopyFromProcessError, minidump_writer::MinidumpWriter, + auxv::AuxvDumpInfo, minidump_writer::MinidumpWriter, process_reader::CopyFromProcessError, serializers::*, }, crate::{ diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index d0370c8..af8001f 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -4,7 +4,10 @@ use { byteorder::{NativeEndian, ReadBytesExt}, goblin::elf, memmap2::{Mmap, MmapOptions}, - procfs_core::process::{MMPermissions, MMapPath, MemoryMaps}, + procfs_core::{ + FromRead, + process::{MMPermissions, MMapPath, MemoryMaps}, + }, std::{ ffi::{OsStr, OsString}, fs::File, @@ -96,6 +99,14 @@ pub enum MapsReaderError { MmapSanityCheckFailed, #[error("Symlink does not match ({0} vs. {1})")] SymlinkError(std::path::PathBuf, std::path::PathBuf), + #[error("Mappings file missing for pid {pid} (is the process still alive?)")] + MappingFileMissing { pid: i32 }, + #[error("Failed to parse memory maps file")] + ParsingError( + #[from] + #[serde(serialize_with = "serialize_proc_error")] + procfs_core::ProcError, + ), } fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool { @@ -117,6 +128,20 @@ fn sanitize_path(pathname: OsString) -> OsString { } impl MappingInfo { + /// Get the mappings for the given process. + pub fn for_pid(pid: i32, linux_gate_loc: Option) -> Result> { + let maps_path = format!("/proc/{}/maps", pid); + let maps_file = File::open(&maps_path).map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + MapsReaderError::MappingFileMissing { pid } + } else { + e.into() + } + })?; + let maps = MemoryMaps::from_read(maps_file)?; + Self::aggregate(maps, linux_gate_loc) + } + /// Return whether the `name` field is a path (contains a `/`). pub fn name_is_path(&self) -> bool { is_mapping_a_path(self.name.as_deref()) diff --git a/src/linux/minidump_writer/errors.rs b/src/linux/minidump_writer/errors.rs index 6bea5ff..f77f13b 100644 --- a/src/linux/minidump_writer/errors.rs +++ b/src/linux/minidump_writer/errors.rs @@ -200,12 +200,6 @@ pub enum InitError { EnumerateThreadsErrors(#[source] ErrorList), #[error("Failed to enumerate threads")] EnumerateThreadsFailed(#[source] Box), - #[error("Failed to read process map file")] - ReadProcessMapFileFailed( - #[source] - #[serde(serialize_with = "serialize_proc_error")] - ProcError, - ), #[error("Failed to aggregate process mappings")] AggregateMappingsFailed(#[source] MapsReaderError), #[error("Failed to enumerate process mappings")] diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index f388b2e..b86593c 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -7,8 +7,7 @@ use { dso_debug, dumper_cpu_info::CpuInfoError, maps_reader::{MappingInfo, MappingList, MapsReaderError}, - mem_reader::CopyFromProcessError, - module_reader, + process_reader::{CopyFromProcessError, ProcessReader}, serializers::*, thread_info::{ThreadInfo, ThreadInfoError}, }, @@ -18,6 +17,7 @@ use { Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError, write_string_to_location, }, minidump_format::*, + module_reader, serializers::*, }, error_graph::{ErrorList, WriteErrorList}, @@ -720,14 +720,7 @@ impl MinidumpWriter { // 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()) + self.mappings = MappingInfo::for_pid(self.process_id, self.auxv.get_linux_gate_address()) .map_err(InitError::AggregateMappingsFailed)?; // Although the initial executable is usually the first mapping, it's not @@ -953,10 +946,34 @@ impl MinidumpWriter { mapping: &MappingInfo, pid: Pid, ) -> Result { + let reader = ProcessReader::new(pid); Ok(T::read_from_module( - module_reader::ProcessReader::new(pid, mapping.start_address).into(), + module_reader::ModuleMemory::from_process(&reader, mapping.start_address), )?) } + + /// Copies a block of bytes from the target process, returning the heap + /// allocated copy + #[inline] + pub fn copy_from_process( + pid: Pid, + src: usize, + length: usize, + ) -> Result, CopyFromProcessError> { + let length = std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError { + src, + child: pid, + offset: 0, + length, + // TODO: We should make copy_from_process also take a NonZero, + // as EINVAL could also come from the syscalls that actually read + // memory as well which could be confusing + source: nix::errno::Errno::EINVAL, + })?; + + let mem = ProcessReader::new(pid); + mem.read_to_vec(src, length) + } } impl Drop for MinidumpWriter { diff --git a/src/linux/minidump_writer/thread_list_stream.rs b/src/linux/minidump_writer/thread_list_stream.rs index 9fa573a..0e1b525 100644 --- a/src/linux/minidump_writer/thread_list_stream.rs +++ b/src/linux/minidump_writer/thread_list_stream.rs @@ -96,8 +96,9 @@ impl MinidumpWriter { // we used the actual state of the thread we would find it running in the // signal handler with the alternative stack, which would be deeply // unhelpful. - if self.crash_context.is_some() && thread.thread_id == self.blamed_thread as u32 { - let crash_context = self.crash_context.as_ref().unwrap(); + if let Some(crash_context) = &self.crash_context + && thread.thread_id == self.blamed_thread as u32 + { let instruction_ptr = crash_context.get_instruction_pointer(); let stack_pointer = crash_context.get_stack_pointer(); self.fill_thread_stack( diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 9e7b46b..68b870b 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -9,9 +9,9 @@ pub mod crash_context; mod dso_debug; mod dumper_cpu_info; pub mod maps_reader; -pub mod mem_reader; pub mod minidump_writer; pub mod module_reader; +pub mod process_reader; mod serializers; pub mod thread_info; diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index 2eb521e..c0f7aef 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -1,5 +1,6 @@ use { - super::{mem_reader::MemReader, serializers::*}, + super::serializers::*, + crate::module_reader::{ModuleMemory, ModuleMemoryReadError}, crate::{minidump_format::GUID, serializers::*}, goblin::{ container::{Container, Ctx, Endian}, @@ -8,16 +9,10 @@ use { std::{borrow::Cow, ffi::CStr}, }; -type Buf<'buf> = Cow<'buf, [u8]>; type Error = ModuleReaderError; const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0"; -pub struct ProcessReader { - inner: MemReader, - start_address: u64, -} - #[derive(Debug, thiserror::Error, serde::Serialize)] pub enum ModuleReaderError { #[error("failed to read module file ({path}): {error}")] @@ -27,15 +22,8 @@ pub enum ModuleReaderError { #[serde(serialize_with = "serialize_io_error")] error: std::io::Error, }, - #[error("failed to read module memory: {length} bytes at {offset}{}: {error}", .start_address.map(|addr| format!(" (start address: {addr})")).unwrap_or_default())] - ReadModuleMemory { - offset: u64, - length: u64, - start_address: Option, - #[source] - #[serde(serialize_with = "serialize_nix_error")] - error: nix::Error, - }, + #[error(transparent)] + ReadModuleMemory(#[from] ModuleMemoryReadError), #[error("failed to parse ELF memory: {0}")] Parsing( #[from] @@ -84,80 +72,9 @@ pub enum ModuleReaderError { }, } -impl ProcessReader { - pub fn new(pid: i32, start_address: usize) -> Self { - Self { - inner: MemReader::new(pid), - start_address: start_address as u64, - } - } -} - -pub enum ProcessMemory<'buf> { - Slice(&'buf [u8]), - Process(ProcessReader), -} - -impl<'buf> From<&'buf [u8]> for ProcessMemory<'buf> { - fn from(value: &'buf [u8]) -> Self { - Self::Slice(value) - } -} - -impl From for ProcessMemory<'_> { - fn from(value: ProcessReader) -> Self { - Self::Process(value) - } -} - -impl<'buf> ProcessMemory<'buf> { - #[inline] - fn read(&mut self, offset: u64, length: u64) -> Result, Error> { - let error = move |start_address, error| Error::ReadModuleMemory { - start_address, - offset, - length, - error, - }; - - match self { - Self::Process(pr) => { - let error = |e| error(Some(pr.start_address), e); - let len = std::num::NonZeroUsize::new(length as usize) - .ok_or_else(|| error(nix::Error::EINVAL))?; - let proc_offset = pr - .start_address - .checked_add(offset) - .ok_or_else(|| error(nix::Error::EOVERFLOW))?; - pr.inner - .read_to_vec(proc_offset as _, len) - .map(Cow::Owned) - .map_err(|err| error(err.source)) - } - Self::Slice(s) => { - let error = |e| error(None, e); - let end = offset - .checked_add(length) - .ok_or_else(|| error(nix::Error::EOVERFLOW))?; - s.get(offset as usize..end as usize) - .map(Cow::Borrowed) - .ok_or_else(|| error(nix::Error::EACCES)) - } - } - } - - /// Calculates the absolute address of the specified relative address - #[inline] - fn absolute(&self, addr: u64) -> u64 { - let Self::Process(pr) = self else { - return addr; - }; - addr.checked_sub(pr.start_address).unwrap_or(addr) - } - - #[inline] - fn is_process_memory(&self) -> bool { - matches!(self, Self::Process(_)) +impl crate::module_reader::ModuleMemory<'_> { + pub fn read_from_module(self) -> Result { + T::read_from_module(self) } } @@ -192,7 +109,7 @@ fn section_header_with_name<'sc>( section_headers: &'sc elf::SectionHeaders, strtab_index: usize, name: &[u8], - module_memory: &mut ProcessMemory<'_>, + module_memory: &ModuleMemory<'_>, ) -> Result, Error> { let strtab_section_header = section_headers .get(strtab_index) @@ -217,9 +134,9 @@ fn section_header_with_name<'sc>( Ok(None) } -/// Types which can be read from ProcessMemory. +/// Types which can be read from ModuleMemory. pub trait ReadFromModule: Sized { - fn read_from_module(module_memory: ProcessMemory<'_>) -> Result; + fn read_from_module(module_memory: ModuleMemory<'_>) -> Result; fn read_from_file(path: &std::path::Path) -> Result { let map = std::fs::File::open(path) @@ -229,7 +146,7 @@ pub trait ReadFromModule: Sized { path: path.to_owned(), error, })?; - Self::read_from_module(ProcessMemory::Slice(&map)) + Self::read_from_module(ModuleMemory::Slice(&map)) } } @@ -237,8 +154,8 @@ pub trait ReadFromModule: Sized { pub struct BuildId(pub Vec); impl ReadFromModule for BuildId { - fn read_from_module(module_memory: ProcessMemory<'_>) -> Result { - let mut reader = ModuleReader::new(module_memory)?; + fn read_from_module(module_memory: 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), @@ -297,8 +214,8 @@ impl Iterator for DynIter<'_> { pub struct SoName(pub String); impl ReadFromModule for SoName { - fn read_from_module(module_memory: ProcessMemory<'_>) -> Result { - let mut reader = ModuleReader::new(module_memory)?; + fn read_from_module(module_memory: 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), @@ -315,13 +232,13 @@ impl ReadFromModule for SoName { } pub struct ModuleReader<'buf> { - module_memory: ProcessMemory<'buf>, + module_memory: ModuleMemory<'buf>, header: elf::Header, context: Ctx, } impl<'buf> ModuleReader<'buf> { - pub fn new(mut module_memory: ProcessMemory<'buf>) -> Result { + pub fn new(module_memory: ModuleMemory<'buf>) -> 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. @@ -337,8 +254,38 @@ impl<'buf> ModuleReader<'buf> { }) } + /// Find a note referenced by the program headers. + pub fn find_program_note( + &self, + note_type: u32, + note_size: usize, + note_name: &str, + ) -> Result>, Error> { + let program_headers = self.read_program_headers()?; + for header in program_headers { + if header.p_type != elf::program_header::PT_NOTE + || (header.p_flags & elf::program_header::PF_R) == 0 + || (header.p_memsz as usize) < note_size + { + continue; + } + + if let Some(data) = self.find_note( + header.p_offset, + header.p_filesz, + header.p_align, + note_type, + note_size, + note_name, + )? { + return Ok(Some(data)); + } + } + Ok(None) + } + /// Read the SONAME using program headers to locate dynamic library information. - pub fn soname_from_program_headers(&mut self) -> Result { + pub fn soname_from_program_headers(&self) -> Result { let program_headers = self.read_program_headers()?; let dynamic_segment_header = program_headers @@ -367,7 +314,13 @@ impl<'buf> ModuleReader<'buf> { (Some(addr), Some(size), Some(offset)) => { // If loaded in memory, the address will be altered to be absolute. if offset < size { - self.read_name_from_strtab(self.module_memory.absolute(addr), size, offset) + self.read_name_from_strtab( + self.module_memory + .absolute_to_relative(addr) + .unwrap_or(addr), + size, + offset, + ) } else { log::warn!("soname strtab offset ({offset}) exceeds strtab size ({size})"); Err(Error::NoSoNameEntry) @@ -377,7 +330,7 @@ impl<'buf> ModuleReader<'buf> { } /// Read the SONAME using section headers to locate dynamic library information. - pub fn soname_from_sections(&mut self) -> Result { + pub fn soname_from_sections(&self) -> Result { let section_headers = self.read_section_headers()?; let dynamic_section_header = section_headers @@ -392,7 +345,7 @@ impl<'buf> ModuleReader<'buf> { §ion_headers, self.header.e_shstrndx as usize, b".dynstr\0", - &mut self.module_memory, + &self.module_memory, )? .ok_or(Error::NoDynStrSection)?, }; @@ -425,7 +378,7 @@ impl<'buf> ModuleReader<'buf> { } /// Read the build id from a program header note. - pub fn build_id_from_program_headers(&mut self) -> Result, Error> { + 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 { @@ -441,14 +394,14 @@ impl<'buf> ModuleReader<'buf> { } /// Read the build id from a notes section. - pub fn build_id_from_section(&mut self) -> Result, Error> { + 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, - &mut self.module_memory, + &self.module_memory, )? .ok_or(Error::NoSectionNote)?; @@ -460,7 +413,7 @@ impl<'buf> ModuleReader<'buf> { } /// Generate a build id by hashing the first page of the text section. - pub fn build_id_generate_from_text(&mut self) -> Result, Error> { + pub fn build_id_generate_from_text(&self) -> Result, Error> { let Some(text_header) = self .read_section_headers()? .into_iter() @@ -475,18 +428,18 @@ impl<'buf> ModuleReader<'buf> { Ok(build_id_from_bytes(&text_data)) } - fn read_segment(&mut self, header: &elf::ProgramHeader) -> Result, Error> { + fn read_segment(&self, header: &elf::ProgramHeader) -> Result, Error> { let (offset, size) = if self.module_memory.is_process_memory() { (header.p_vaddr, header.p_memsz) } else { (header.p_offset, header.p_filesz) }; - self.module_memory.read(offset, size) + self.module_memory.read(offset, size).map_err(|e| e.into()) } fn read_name_from_strtab( - &mut self, + &self, strtab_offset: u64, strtab_size: u64, name_offset: u64, @@ -508,7 +461,7 @@ impl<'buf> ModuleReader<'buf> { } } - fn read_program_headers(&mut self) -> Result { + fn read_program_headers(&self) -> Result { if self.header.e_phoff == 0 { return Err(Error::NoProgramHeaders); } @@ -525,7 +478,7 @@ impl<'buf> ModuleReader<'buf> { Ok(program_headers) } - fn read_section_headers(&mut self) -> Result { + fn read_section_headers(&self) -> Result { if self.header.e_shoff == 0 { return Err(Error::NoSections); } @@ -545,10 +498,29 @@ impl<'buf> ModuleReader<'buf> { } fn find_build_id_note( - &mut self, + &self, + offset: u64, + size: u64, + alignment: u64, + ) -> Result>, Error> { + self.find_note( + offset, + size, + alignment, + elf::note::NT_GNU_BUILD_ID, + 0, + "GNU", + ) + } + + fn find_note( + &self, offset: u64, size: u64, alignment: u64, + note_type: u32, + note_min_size: usize, + note_name: &str, ) -> Result>, Error> { let notes = self.module_memory.read(offset, size)?; for note in (elf::note::NoteDataIterator { @@ -560,10 +532,136 @@ impl<'buf> ModuleReader<'buf> { 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 { + if note.name == note_name + && note.n_type == note_type + && note.desc.len() >= note_min_size + { 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.into()).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.into()).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.into()).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.into()).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.into()).unwrap(); + let soname = reader.soname_from_sections().unwrap(); + assert_eq!(soname, "libfoo.so.1"); + } +} diff --git a/src/linux/process_reader.rs b/src/linux/process_reader.rs new file mode 100644 index 0000000..9bfc63a --- /dev/null +++ b/src/linux/process_reader.rs @@ -0,0 +1,266 @@ +use { + super::{Pid, maps_reader::MappingInfo, serializers::*}, + crate::module_reader::ModuleMemory, + std::sync::OnceLock, +}; + +#[cfg(target_os = "android")] +use super::module_reader::SoName; + +pub type ProcessHandle = libc::pid_t; + +#[derive(Debug)] +enum Style { + /// Uses [`process_vm_readv`](https://linux.die.net/man/2/process_vm_readv) + /// to read the memory. + /// + /// This is not available on old <3.2 (really, ancient) kernels, and requires + /// the same permissions as ptrace + VirtualMem, + /// Reads the memory from `/proc//mem` + /// + /// Available on basically all versions of Linux, but could fail if the process + /// has insufficient privileges, ie ptrace + File(std::fs::File), + /// Reads the memory with [ptrace (`PTRACE_PEEKDATA`)](https://man7.org/linux/man-pages/man2/ptrace.2.html) + /// + /// Reads data one word at a time, so slow, but fairly reliable, as long as + /// the process can be ptraced + Ptrace, + /// No methods succeeded, generally there isn't a case where failing a syscall + /// will work if called again + Unavailable { + vmem: nix::Error, + file: nix::Error, + ptrace: nix::Error, + }, +} + +#[derive(Debug, thiserror::Error, serde::Serialize)] +#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")] +pub struct CopyFromProcessError { + pub child: Pid, + pub src: usize, + pub offset: usize, + pub length: usize, + #[serde(serialize_with = "serialize_nix_error")] + pub source: nix::Error, +} + +#[derive(Debug, thiserror::Error, serde::Serialize)] +pub enum FindModuleError { + #[error("Module not found")] + ModuleNotFound, + #[error("Failed to read process module mappings")] + MappingError(#[from] super::maps_reader::MapsReaderError), +} + +pub struct ProcessReader { + /// The pid of the child to read + pid: nix::unistd::Pid, + style: OnceLock