From 4204f723d06ad10375101e09db9b856b26dc8efe Mon Sep 17 00:00:00 2001 From: Chris Martin Date: Wed, 5 Aug 2026 18:14:17 -0400 Subject: [PATCH] Isolated Processes 12: Refactor Result, visiblity, wrappers There are about to be a lot of methods returning Result, so let's just start using some specialized results to avoid repeating ourselves. Also, some of the visiblity on things could be cleaned up. And let's centralize some of the wrappers for things like FDs so the remote end can reuse them. There's not much to this, but it makes other reviews noisy so I thought it best to separate it out. --- crates/linux/process-backend/src/lib.rs | 7 +- crates/linux/process-backend/src/local/mod.rs | 138 +++++-------- .../src/local/module_reader.rs | 50 +++-- .../src/local/syscall_invoker.rs | 11 +- crates/linux/process-backend/src/wrapper.rs | 43 ++++ src/linux/auxv/mod.rs | 2 +- src/linux/dumper_cpu_info/arm.rs | 8 +- src/linux/dumper_cpu_info/x86.rs | 2 +- src/linux/maps_reader.rs | 4 +- .../minidump_writer/handle_data_stream.rs | 6 +- .../memory_info_list_stream.rs | 2 +- src/linux/minidump_writer/mod.rs | 17 +- src/linux/module_reader.rs | 4 +- src/linux/process_inspection/mod.rs | 188 +++++++++++------- .../process_inspection/process_reader.rs | 9 +- src/linux/thread_info/x86.rs | 2 +- tests/common/mod.rs | 29 ++- tests/linux_minidump_writer_soft_error.rs | 109 ++++++---- tests/ptrace_dumper.rs | 26 +++ 19 files changed, 390 insertions(+), 267 deletions(-) create mode 100644 crates/linux/process-backend/src/wrapper.rs diff --git a/crates/linux/process-backend/src/lib.rs b/crates/linux/process-backend/src/lib.rs index 6c9c72ce..d52ec9b6 100644 --- a/crates/linux/process-backend/src/lib.rs +++ b/crates/linux/process-backend/src/lib.rs @@ -9,9 +9,10 @@ mod drop_fail_handler; pub mod local; pub mod regs; -/// This is the longest path length we guarantee we can handle, since we won't be able to allocate -/// in the fork of the crashed process. We can increase if necessary. -pub const MAX_PATH_LEN: usize = 256; +mod wrapper; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub const PTRACE_DATA_LEN: usize = core::mem::size_of::(); #[derive(Debug, serde::Deserialize, serde::Serialize)] pub enum ProcessReaderKind { diff --git a/crates/linux/process-backend/src/local/mod.rs b/crates/linux/process-backend/src/local/mod.rs index 288f4e06..77086186 100644 --- a/crates/linux/process-backend/src/local/mod.rs +++ b/crates/linux/process-backend/src/local/mod.rs @@ -1,4 +1,8 @@ -use crate::{ProcessReaderKind, regs::*}; +use crate::{ + ProcessReaderKind, + regs::*, + wrapper::{OwnedFd, errno, set_errno}, +}; use core::{ cell::RefCell, ffi::{CStr, c_int, c_long, c_void}, @@ -7,12 +11,15 @@ use core::{ use libc::pid_t; use syscall_invoker::SyscallInvoker; -pub use self::{error::Error, module_reader::MappedModuleMemoryReader}; +pub use error::Error; +pub use module_reader::MappedModuleMemoryReader; mod error; mod module_reader; mod syscall_invoker; +pub type Result = core::result::Result; + #[cfg(target_env = "gnu")] type PtraceRequestType = core::ffi::c_uint; @@ -34,22 +41,25 @@ impl Backend { syscall_invoker: Default::default(), } } + pub fn pid(&self) -> libc::pid_t { + self.pid + } pub fn process_reader(&self) -> ProcessReader<'_> { ProcessReader(&self.process_reader) } - pub fn stop_process(&self) -> Result<(), Error> { + pub fn stop_process(&self) -> Result<()> { self.standard_syscall(|| unsafe { libc::kill(self.pid, libc::SIGSTOP) }) .map_err(Error::SigStopFailed)?; Ok(()) } - pub fn continue_process(&self) -> Result<(), Error> { + pub fn continue_process(&self) -> Result<()> { self.standard_syscall(|| unsafe { libc::kill(self.pid, libc::SIGCONT) }) .map_err(Error::SigContFailed)?; Ok(()) } - pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<()> { self.standard_syscall(|| unsafe { ptrace(libc::PTRACE_ATTACH, tid, ptr::null_mut(), ptr::null_mut()) }) @@ -91,7 +101,7 @@ impl Backend { Ok(()) } - pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + pub fn resume_thread(&self, tid: libc::pid_t) -> Result<()> { self.ptrace_detach(tid) } @@ -99,22 +109,22 @@ impl Backend { &self, path: &CStr, offset: u64, - ) -> Result { + ) -> Result { MappedModuleMemoryReader::new(&mut self.syscall_invoker.borrow_mut(), path, offset) } - pub fn stat_file(&self, path: &CStr) -> Result { + pub fn stat_file(&self, path: &CStr) -> Result { let mut output = unsafe { mem::zeroed::() }; self.standard_syscall(|| unsafe { libc::stat(path.as_ptr(), &mut output) }) .map_err(Error::StatFailed)?; Ok(output) } - pub fn read_file(&self, path: &CStr) -> Result { + pub fn read_file(&self, path: &CStr) -> Result { self.open_file(path).map(FileReader) } - pub fn read_dir(&self, path: &CStr) -> Result { + pub fn read_dir(&self, path: &CStr) -> Result { self.special_syscall(|| unsafe { let dirp = libc::opendir(path.as_ptr()); if dirp.is_null() { @@ -126,7 +136,7 @@ impl Backend { .map_err(Error::OpenDirFailed) } - pub fn read_link(&self, path: &CStr, buf: &mut [u8]) -> Result { + pub fn read_link(&self, path: &CStr, buf: &mut [u8]) -> Result { let bytes_read = self .standard_syscall(|| unsafe { libc::readlink(path.as_ptr(), buf.as_mut_ptr().cast(), buf.len()) @@ -141,31 +151,27 @@ impl Backend { Ok(bytes_read) } - pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { self.getregset(tid).or_else(|_| self.getregs(tid)) } - pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { self.getfpregset(tid).or_else(|_| self.getfpregs(tid)) } #[cfg(target_arch = "x86")] - pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { const PTRACE_GETFPXREGS: PtraceRequestType = 18; unsafe { self.ptrace_getregs::(PTRACE_GETFPXREGS, tid) } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - pub fn ptrace_peekuser( - &self, - pid: libc::pid_t, - addr: usize, - ) -> Result<[u8; mem::size_of::()], Error> { + pub fn ptrace_peekuser(&self, addr: usize) -> Result<[u8; crate::PTRACE_DATA_LEN]> { self.special_syscall(|| unsafe { set_errno(0); let rv = ptrace( libc::PTRACE_PEEKUSER, - pid, + self.pid, addr as *mut _, core::ptr::null_mut(), ); @@ -177,7 +183,7 @@ impl Backend { .map_err(Error::PtracePeekUserFailed) } - pub fn force_process_reader_kind(&mut self, kind: ProcessReaderKind) -> Result<(), Error> { + pub fn force_process_reader_kind(&mut self, kind: ProcessReaderKind) -> Result<()> { use ProcessReaderKind as K; self.process_reader = match kind { K::Unspecified => process_reader::ProcessReader::new(self.pid), @@ -190,7 +196,7 @@ impl Backend { Ok(()) } - fn open_file(&self, path: &CStr) -> Result { + fn open_file(&self, path: &CStr) -> Result { self.standard_syscall(|| unsafe { libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) }) @@ -198,7 +204,7 @@ impl Backend { .map_err(Error::OpenFileFailed) } - fn getregset(&self, _pid: libc::pid_t) -> Result { + fn getregset(&self, _tid: libc::pid_t) -> Result { #[cfg(target_arch = "arm")] { Err(Error::NotSupported) @@ -206,29 +212,29 @@ impl Backend { #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] { const NT_PRSTATUS: usize = 1; - self.ptrace_getregset(NT_PRSTATUS, _pid) + self.ptrace_getregset(NT_PRSTATUS, _tid) } } - fn getregs(&self, pid: libc::pid_t) -> Result { + fn getregs(&self, tid: libc::pid_t) -> Result { const PTRACE_GETREGS: PtraceRequestType = 12; - unsafe { self.ptrace_getregs::(PTRACE_GETREGS, pid) } + unsafe { self.ptrace_getregs::(PTRACE_GETREGS, tid) } } - fn getfpregset(&self, pid: libc::pid_t) -> Result { + fn getfpregset(&self, tid: libc::pid_t) -> Result { #[cfg(target_arch = "arm")] { const NT_ARM_VFP: usize = 0x400; - self.ptrace_getregset(NT_ARM_VFP, pid) + self.ptrace_getregset(NT_ARM_VFP, tid) } #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] { const NT_PRFPREGSET: usize = 2; - self.ptrace_getregset(NT_PRFPREGSET, pid) + self.ptrace_getregset(NT_PRFPREGSET, tid) } } - fn getfpregs(&self, _pid: libc::pid_t) -> Result { + fn getfpregs(&self, _tid: libc::pid_t) -> Result { #[cfg(target_arch = "arm")] { Err(Error::NotSupported) @@ -236,21 +242,17 @@ impl Backend { #[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] { const PTRACE_GETFPREGS: PtraceRequestType = 14; - unsafe { self.ptrace_getregs::(PTRACE_GETFPREGS, _pid) } + unsafe { self.ptrace_getregs::(PTRACE_GETFPREGS, _tid) } } } /// Safety: RequestType and T must agree on the size of the returned type - unsafe fn ptrace_getregs( - &self, - request: PtraceRequestType, - pid: libc::pid_t, - ) -> Result { + unsafe fn ptrace_getregs(&self, request: PtraceRequestType, tid: libc::pid_t) -> Result { let mut output = mem::MaybeUninit::::uninit(); self.standard_syscall(|| unsafe { ptrace( request, - pid, + tid, core::ptr::null_mut(), output.as_mut_ptr().cast(), ) @@ -259,7 +261,7 @@ impl Backend { Ok(unsafe { output.assume_init() }) } - fn ptrace_getregset(&self, regset_type: usize, pid: libc::pid_t) -> Result { + fn ptrace_getregset(&self, regset_type: usize, tid: libc::pid_t) -> Result { let mut output = mem::MaybeUninit::::uninit(); let mut io = libc::iovec { iov_base: output.as_mut_ptr().cast(), @@ -269,7 +271,7 @@ impl Backend { self.standard_syscall(|| unsafe { ptrace( libc::PTRACE_GETREGSET, - pid, + tid, regset_type as *mut _, (&raw mut io).cast(), ) @@ -285,7 +287,7 @@ impl Backend { Ok(unsafe { output.assume_init() }) } - fn ptrace_detach(&self, tid: libc::pid_t) -> Result<(), Error> { + fn ptrace_detach(&self, tid: libc::pid_t) -> Result<()> { self.standard_syscall(|| unsafe { ptrace(libc::PTRACE_DETACH, tid, ptr::null_mut(), ptr::null_mut()) }) @@ -293,7 +295,7 @@ impl Backend { Ok(()) } - fn standard_syscall(&self, f: F) -> Result + fn standard_syscall(&self, f: F) -> core::result::Result where F: FnOnce() -> T, T: From + core::cmp::PartialEq, @@ -301,9 +303,9 @@ impl Backend { self.syscall_invoker.borrow_mut().invoke_standard(f) } - fn special_syscall(&self, f: F) -> Result + fn special_syscall(&self, f: F) -> core::result::Result where - F: FnOnce() -> Result, + F: FnOnce() -> core::result::Result, { self.syscall_invoker.borrow_mut().invoke(f) } @@ -320,14 +322,14 @@ impl Backend { pub struct FileReader(OwnedFd); impl FileReader { - pub fn read(&mut self, buf: &mut [u8]) -> Result { + pub fn read(&mut self, buf: &mut [u8]) -> Result { let rv = unsafe { libc::read(self.0.as_raw_fd(), buf.as_mut_ptr().cast(), buf.len()) }; if rv == -1 { return Err(Error::ReadFileFailed(errno())); } Ok(rv.try_into().unwrap()) } - pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result { + pub fn read_at(&self, buf: &mut [u8], offset: u64) -> Result { let rv = unsafe { libc::pread( self.0.as_raw_fd(), @@ -350,7 +352,7 @@ pub struct DirReader { } impl DirReader { - pub fn read_name(&mut self) -> Result, Error> { + pub fn read_next_name(&mut self) -> Result> { if self.eof { return Ok(None); } @@ -396,33 +398,11 @@ impl Drop for DirReader { pub struct ProcessReader<'a>(&'a process_reader::ProcessReader); impl<'a> ProcessReader<'a> { - pub fn read_at(&self, address: usize, buf: &mut [u8]) -> Result { + pub fn read_at(&self, address: usize, buf: &mut [u8]) -> Result { self.0.read_at(address, buf).map_err(Error::ProcessReader) } } -#[derive(Debug)] -struct OwnedFd(c_int); - -impl OwnedFd { - // SAFETY: Must be a valid fd - pub unsafe fn new(fd: c_int) -> Self { - Self(fd) - } - pub fn as_raw_fd(&self) -> c_int { - self.0 - } -} - -impl Drop for OwnedFd { - fn drop(&mut self) { - let rv = unsafe { libc::close(self.0) }; - if rv == -1 { - report_drop_failed!("failed to close file: {}", errno()); - } - } -} - /// This is just a typesafe wrapper around ptrace(), which is vararg... But this is Rust, and /// playing loosey-goosey with types is really more of a C thing ;) unsafe fn ptrace( @@ -433,23 +413,3 @@ unsafe fn ptrace( ) -> c_long { unsafe { libc::ptrace(request, pid, addr, data) } } - -fn errno() -> c_int { - unsafe { *errno_location() } -} - -fn set_errno(value: c_int) { - unsafe { - *errno_location() = value; - } -} - -#[cfg(target_os = "android")] -fn errno_location() -> *mut c_int { - unsafe { libc::__errno() } -} - -#[cfg(not(target_os = "android"))] -fn errno_location() -> *mut c_int { - unsafe { libc::__errno_location() } -} diff --git a/crates/linux/process-backend/src/local/module_reader.rs b/crates/linux/process-backend/src/local/module_reader.rs index dfcb5762..e3c5e2b4 100644 --- a/crates/linux/process-backend/src/local/module_reader.rs +++ b/crates/linux/process-backend/src/local/module_reader.rs @@ -1,10 +1,10 @@ -use { - super::{Error, OwnedFd, SyscallInvoker, errno}, - core::{ - ffi::{CStr, c_void}, - mem, ptr, - }, +use super as local; +use crate::wrapper::{OwnedFd, errno}; +use core::{ + ffi::{CStr, c_void}, + mem, ptr, }; +use local::{Error, Result, SyscallInvoker}; #[derive(Debug)] pub struct MappedModuleMemoryReader { @@ -14,11 +14,24 @@ pub struct MappedModuleMemoryReader { } impl MappedModuleMemoryReader { - pub fn new( + pub fn read_at(&self, offset: usize, length: usize) -> Result<&[u8]> { + let s = self.as_slice(); + let requested_end = offset.checked_add(length).ok_or(Error::IndexOutOfBounds)?; + let maximum_end = s.len(); + let end = usize::min(requested_end, maximum_end); + s.get(offset..end).ok_or(Error::IndexOutOfBounds) + } + pub fn len(&self) -> usize { + self.as_slice().len() + } + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + pub(crate) fn new( syscall_invoker: &mut SyscallInvoker, path: &CStr, start_position: u64, - ) -> Result { + ) -> Result { let fd = Self::open_file(syscall_invoker, path)?; // So far, we only ever map files from the start position to EOF - We never specify a @@ -51,22 +64,7 @@ impl MappedModuleMemoryReader { Ok(MappedModuleMemoryReader { mapped, ptr, len }) } - pub fn read(&self, offset: u64, length: u64) -> Result<&[u8], Error> { - (|| { - let offset = usize::try_from(offset).ok()?; - let length = usize::try_from(length).ok()?; - let end = offset.checked_add(length)?; - self.as_slice().get(offset..end) - })() - .ok_or(Error::IndexOutOfBounds) - } - pub fn len(&self) -> Result { - Ok(self.as_slice().len()) - } - pub fn is_empty(&self) -> Result { - self.len().map(|l| l == 0) - } - fn open_file(syscall_invoker: &mut SyscallInvoker, path: &CStr) -> Result { + fn open_file(syscall_invoker: &mut SyscallInvoker, path: &CStr) -> Result { syscall_invoker .invoke_standard(|| unsafe { libc::open(path.as_ptr(), libc::O_RDONLY | libc::O_CLOEXEC, 0) @@ -74,7 +72,7 @@ impl MappedModuleMemoryReader { .map(|fd| unsafe { OwnedFd::new(fd) }) .map_err(Error::OpenFileFailed) } - fn get_file_size(syscall_invoker: &mut SyscallInvoker, fd: &OwnedFd) -> Result { + fn get_file_size(syscall_invoker: &mut SyscallInvoker, fd: &OwnedFd) -> Result { let mut stat: libc::stat = unsafe { mem::zeroed() }; syscall_invoker @@ -93,7 +91,7 @@ impl MappedModuleMemoryReader { fd: &OwnedFd, page_aligned_start_position: u64, len: usize, - ) -> Result { + ) -> Result { // Linux requires the mapping length to be non-zero, even though we want to support // zero-length mappings -- So we just make it a one-byte mapping (and ignore the byte). let len = usize::max(len, 1); diff --git a/crates/linux/process-backend/src/local/syscall_invoker.rs b/crates/linux/process-backend/src/local/syscall_invoker.rs index f0e7596f..15a013f3 100644 --- a/crates/linux/process-backend/src/local/syscall_invoker.rs +++ b/crates/linux/process-backend/src/local/syscall_invoker.rs @@ -1,7 +1,8 @@ -use {super::errno, core::ffi::c_int}; +use crate::wrapper::errno; +use core::ffi::c_int; #[derive(Debug, Default)] -pub struct SyscallInvoker(Option); +pub(crate) struct SyscallInvoker(Option); impl SyscallInvoker { /// Helper function to invoke a syscall and capture errno if it fails @@ -12,7 +13,7 @@ impl SyscallInvoker { /// /// If testing requests a failure, will never actually make the syscall and just returns /// the errno requested by testing - pub fn invoke(&mut self, f: F) -> Result + pub(crate) fn invoke(&mut self, f: F) -> Result where F: FnOnce() -> Result, { @@ -24,7 +25,7 @@ impl SyscallInvoker { } /// Ergonomics for `invoke` for the standard case where `-1` indicates the syscall failed - pub fn invoke_standard(&mut self, f: F) -> Result + pub(crate) fn invoke_standard(&mut self, f: F) -> Result where F: FnOnce() -> T, T: From + core::cmp::PartialEq, @@ -40,7 +41,7 @@ impl SyscallInvoker { /// Force the next syscall to fail with the given errno #[cfg(feature = "testing")] - pub fn fail_one_syscall_with(&mut self, errno: c_int) { + pub(crate) fn fail_one_syscall_with(&mut self, errno: c_int) { self.0 = Some(errno); } } diff --git a/crates/linux/process-backend/src/wrapper.rs b/crates/linux/process-backend/src/wrapper.rs new file mode 100644 index 00000000..26a8de7e --- /dev/null +++ b/crates/linux/process-backend/src/wrapper.rs @@ -0,0 +1,43 @@ +use core::ffi::c_int; + +#[derive(Debug)] +pub(crate) struct OwnedFd(c_int); + +impl OwnedFd { + // SAFETY: Must be a valid fd + pub(crate) unsafe fn new(fd: c_int) -> Self { + Self(fd) + } + pub(crate) fn as_raw_fd(&self) -> c_int { + self.0 + } +} + +impl Drop for OwnedFd { + fn drop(&mut self) { + let rv = unsafe { libc::close(self.0) }; + if rv == -1 { + report_drop_failed!("failed to close file: {}", errno()); + } + } +} + +pub(crate) fn errno() -> c_int { + unsafe { *errno_location() } +} + +pub(crate) fn set_errno(value: c_int) { + unsafe { + *errno_location() = value; + } +} + +#[cfg(target_os = "android")] +fn errno_location() -> *mut c_int { + unsafe { libc::__errno() } +} + +#[cfg(not(target_os = "android"))] +fn errno_location() -> *mut c_int { + unsafe { libc::__errno_location() } +} diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs index 9eb0a897..7c804dcd 100644 --- a/src/linux/auxv/mod.rs +++ b/src/linux/auxv/mod.rs @@ -97,7 +97,7 @@ impl AuxvDumpInfo { let auxv_path = format!("/proc/{pid}/auxv"); let auxv_file = process_inspector - .read_file(&auxv_path) + .read_file(auxv_path.clone().into()) .map_err(|e| AuxvError::OpenError(auxv_path, e))?; for pair_result in ProcfsAuxvIter::new(BufReader::new(auxv_file)) { diff --git a/src/linux/dumper_cpu_info/arm.rs b/src/linux/dumper_cpu_info/arm.rs index 0bc1290f..1a331d4b 100644 --- a/src/linux/dumper_cpu_info/arm.rs +++ b/src/linux/dumper_cpu_info/arm.rs @@ -171,12 +171,14 @@ pub fn write_cpu_information( // because the content of /proc/cpuinfo will only mirror the number // of 'online' cores, and thus will vary with time. // See http://www.kernel.org/doc/Documentation/cputopology.txt - if let Ok(mut present_file) = process_inspector.read_file("/sys/devices/system/cpu/present") { + if let Ok(mut present_file) = + process_inspector.read_file("/sys/devices/system/cpu/present".into()) + { // Ignore unparsable content let cpus_present = parse_cpus_from_sysfile(&mut present_file).unwrap_or_default(); if let Ok(mut possible_file) = - process_inspector.read_file("/sys/devices/system/cpu/possible") + process_inspector.read_file("/sys/devices/system/cpu/possible".into()) { // Ignore unparsable content let cpus_possible = parse_cpus_from_sysfile(&mut possible_file).unwrap_or_default(); @@ -197,7 +199,7 @@ pub fn write_cpu_information( } let cpuinfo_file = process_inspector - .read_file("/proc/cpuinfo") + .read_file("/proc/cpuinfo".into()) .map_err(CpuInfoError::ReadFileError)?; let mut cpuid = 0; diff --git a/src/linux/dumper_cpu_info/x86.rs b/src/linux/dumper_cpu_info/x86.rs index 5bfb678d..b1e8ddcc 100644 --- a/src/linux/dumper_cpu_info/x86.rs +++ b/src/linux/dumper_cpu_info/x86.rs @@ -50,7 +50,7 @@ pub fn write_cpu_information( } let cpuinfo_file = process_inspector - .read_file("/proc/cpuinfo") + .read_file("/proc/cpuinfo".into()) .map_err(CpuInfoError::ReadFileError)?; let mut vendor_id = String::new(); diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs index 57986db1..0a19072a 100644 --- a/src/linux/maps_reader.rs +++ b/src/linux/maps_reader.rs @@ -142,7 +142,7 @@ impl MappingInfo { ) -> Result> { let maps_path = format!("/proc/{}/maps", pid); let maps_file = process_inspector - .read_file(&maps_path) + .read_file(maps_path.into()) .map_err(MapsReaderError::ReadFileFailed)?; let maps = MemoryMaps::from_read(maps_file)?; Self::aggregate(maps, linux_gate_loc) @@ -728,7 +728,7 @@ a4840000-a4873000 rw-p 09021000 08:12 393449 /data/app/org.mozilla.firefox-1 ); assert_eq!(mappings.len(), 1); - let process_inspector = ProcessInspector::local(0); + let process_inspector = process_inspection::local(0); let (file_path, file_name, _version) = mappings[0] .get_mapping_effective_path_name_and_version(&process_inspector, None) diff --git a/src/linux/minidump_writer/handle_data_stream.rs b/src/linux/minidump_writer/handle_data_stream.rs index b715bd11..0e1db981 100644 --- a/src/linux/minidump_writer/handle_data_stream.rs +++ b/src/linux/minidump_writer/handle_data_stream.rs @@ -14,9 +14,9 @@ fn descriptor_from_path( path: &Path, ) -> Option { let handle = filename_to_fd(path.file_name().unwrap())?; - let realpath = process_inspector.read_link(path).ok()?; + let realpath = process_inspector.read_link(path.into()).ok()?; let path_rva = write_string_to_location(buffer, realpath.to_string_lossy().as_ref()).ok()?; - let stat = process_inspector.stat_file(path).ok()?; + let stat = process_inspector.stat_file(path.into()).ok()?; // TODO: We store the contents of `st_mode` into the `attributes` field, but // we could also store a human-readable string of the file type inside @@ -67,7 +67,7 @@ impl MinidumpWriter { let proc_fd_path = PathBuf::from(format!("/proc/{}/fd", self.process_id)); let proc_fd_iter = self .process_inspector - .read_dir(&proc_fd_path) + .read_dir(proc_fd_path.clone()) .map_err(SectionHandleDataStreamError::ReadDirFailed)?; let descriptors: Vec<_> = proc_fd_iter .filter_map(|filename| filename.ok()) diff --git a/src/linux/minidump_writer/memory_info_list_stream.rs b/src/linux/minidump_writer/memory_info_list_stream.rs index 63fc56a0..e9f07855 100644 --- a/src/linux/minidump_writer/memory_info_list_stream.rs +++ b/src/linux/minidump_writer/memory_info_list_stream.rs @@ -27,7 +27,7 @@ impl MinidumpWriter { let path = format!("/proc/{}/maps", self.blamed_thread); let reader = self .process_inspector - .read_file(&path) + .read_file(path.into()) .map_err(SectionMemInfoListError::ReadFileFailed)?; let maps = procfs_core::process::MemoryMaps::from_read(reader)?; diff --git a/src/linux/minidump_writer/mod.rs b/src/linux/minidump_writer/mod.rs index e66ede2a..2afd1fcc 100644 --- a/src/linux/minidump_writer/mod.rs +++ b/src/linux/minidump_writer/mod.rs @@ -29,6 +29,7 @@ use { }, std::{ io::{Read, Seek, Write}, + path::PathBuf, time::{Duration, Instant}, }, thiserror::Error, @@ -132,7 +133,7 @@ impl MinidumpWriterConfig { crashing_thread_context: Default::default(), stop_timeout: STOP_TIMEOUT, direct_auxv_dump_info: Default::default(), - process_inspector: ProcessInspector::local(process_id), + process_inspector: process_inspection::local(process_id), } } @@ -600,6 +601,10 @@ impl MinidumpWriter { /// /// This will block waiting for the process to stop until `timeout` has passed. fn stop_process(&mut self, timeout: Duration) -> Result<(), StopProcessError> { + failspot!(if StopProcess { + self.process_inspector.fail_one_syscall_with(libc::EPERM); + }); + self.process_inspector .stop_process() .map_err(StopProcessError::Stop)?; @@ -607,13 +612,13 @@ impl MinidumpWriter { // Something like waitpid for non-child processes would be better, but we have no such // tool, so we poll the status. const POLL_INTERVAL: Duration = Duration::from_millis(1); - let proc_file = format!("/proc/{}/stat", self.process_id); + let proc_file = PathBuf::from(format!("/proc/{}/stat", self.process_id)); let end = Instant::now() + timeout; loop { let stat_file = self .process_inspector - .read_file(&proc_file) + .read_file(proc_file.clone()) .map_err(StopProcessError::ReadFileFailed)?; if let Ok(ProcState::Stopped) = Stat::from_read(stat_file)?.state() { return Ok(()); @@ -646,7 +651,7 @@ impl MinidumpWriter { for file_name in self .process_inspector - .read_dir(&task_path) + .read_dir(task_path.into()) .map_err(InitError::ReadProcTaskFailed)? { let file_name = match file_name { @@ -671,7 +676,7 @@ impl MinidumpWriter { // Read the thread-name (if there is any) let name_result = self .process_inspector - .read_file(format!("/proc/{pid}/task/{tid}/comm")) + .read_file(format!("/proc/{pid}/task/{tid}/comm").into()) .map_err(std::io::Error::other) .and_then(|mut file| { let mut s = String::new(); @@ -971,7 +976,7 @@ fn write_file( filename: &str, ) -> std::result::Result { let content = process_inspector - .read_file(filename) + .read_file(filename.into()) .map_err(std::io::Error::other) .and_then(|mut file| { let mut v = Vec::new(); diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs index 7a1ae939..a42f95c8 100644 --- a/src/linux/module_reader.rs +++ b/src/linux/module_reader.rs @@ -164,7 +164,7 @@ pub fn read_build_id_from_file( path: &Path, ) -> Result, Error> { let module_memory_reader = process_inspector - .map_module_into_memory(path, 0) + .map_module_into_memory(path.into(), 0) .map_err(Error::MapModuleFailed)?; read_build_id_from_module(module_memory_reader) } @@ -206,7 +206,7 @@ pub fn read_soname_from_file( } let module_memory_reader = process_inspector - .map_module_into_memory(path, offset) + .map_module_into_memory(path.into(), offset) .map_err(Error::MapModuleFailed)?; let memory_len = module_memory_reader.len().map_err(Error::MapModuleFailed)?; diff --git a/src/linux/process_inspection/mod.rs b/src/linux/process_inspection/mod.rs index 52a03f58..9a328153 100644 --- a/src/linux/process_inspection/mod.rs +++ b/src/linux/process_inspection/mod.rs @@ -1,23 +1,30 @@ -use super::maps_reader; +use super as linux; use crate::module_reader::{ModuleMemoryReadError, ReadError, ReadModuleMemory}; -use core::ffi::c_int; -use failspot::failspot; -use process_backend::{MAX_PATH_LEN, local, regs::*}; +use linux::maps_reader; +use process_backend::{local, regs::*}; use process_reader::ProcessReader; use std::{ borrow::Cow, - ffi::{CString, OsString}, + ffi::{CString, OsString, c_int}, io, os::unix::ffi::OsStringExt, path::PathBuf, }; -pub use process_backend::regs; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use process_backend::PTRACE_DATA_LEN; + +pub(crate) use process_backend::regs; pub use process_backend::ProcessReaderKind; pub mod process_reader; +pub(crate) type Result = core::result::Result; + +// This is an arbitrary choice and may need to be tweaked +const MAX_PATH_LEN: usize = 65536; + #[derive(Debug)] pub struct ProcessInspector { pid: libc::pid_t, @@ -25,47 +32,51 @@ pub struct ProcessInspector { } #[derive(Debug)] -pub enum Backend { +enum Backend { Local { backend: local::Backend }, } -impl ProcessInspector { - pub fn local(pid: libc::pid_t) -> Self { - set_process_backend_drop_fail_handler(); +pub(crate) fn local(pid: libc::pid_t) -> ProcessInspector { + set_process_backend_drop_fail_handler(); - let backend = local::Backend::new(pid); + let backend = local::Backend::new(pid); - ProcessInspector { - pid, - backend: Backend::Local { backend }, - } + ProcessInspector { + pid, + backend: Backend::Local { backend }, } +} + +impl ProcessInspector { pub fn process_reader(&self) -> ProcessReader<'_> { ProcessReader::new(self) } - pub fn stop_process(&self) -> Result<(), Error> { - failspot!(if StopProcess { - return Err(Error::Local(local::Error::SigStopFailed(libc::EPERM))); - }); + pub fn pid(&self) -> Result { + match &self.backend { + Backend::Local { backend, .. } => Ok(backend.pid()), + } + } + + pub fn stop_process(&self) -> Result<()> { match &self.backend { Backend::Local { backend, .. } => backend.stop_process().map_err(Error::Local), } } - pub fn continue_process(&self) -> Result<(), Error> { + pub fn continue_process(&self) -> Result<()> { match &self.backend { Backend::Local { backend, .. } => backend.continue_process().map_err(Error::Local), } } - pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + pub fn suspend_thread(&self, tid: libc::pid_t) -> Result<()> { match &self.backend { Backend::Local { backend, .. } => backend.suspend_thread(tid).map_err(Error::Local), } } - pub fn resume_thread(&self, tid: libc::pid_t) -> Result<(), Error> { + pub fn resume_thread(&self, tid: libc::pid_t) -> Result<()> { match &self.backend { Backend::Local { backend, .. } => backend.resume_thread(tid).map_err(Error::Local), } @@ -73,10 +84,10 @@ impl ProcessInspector { pub fn map_module_into_memory( &self, - path: impl Into, + path: PathBuf, offset: u64, - ) -> Result { - let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + ) -> Result { + let c_path = CString::new(path.into_os_string().into_vec()).unwrap(); match &self.backend { Backend::Local { backend, .. } => backend .map_module_into_memory(&c_path, offset) @@ -85,15 +96,15 @@ impl ProcessInspector { } } - pub fn stat_file(&self, path: impl Into) -> Result { - let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + pub fn stat_file(&self, path: PathBuf) -> Result { + let c_path = CString::new(path.into_os_string().into_vec()).unwrap(); match &self.backend { Backend::Local { backend, .. } => backend.stat_file(&c_path).map_err(Error::Local), } } - pub fn read_file(&self, path: impl Into) -> Result { - let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + pub fn read_file(&self, path: PathBuf) -> Result { + let c_path = CString::new(path.into_os_string().into_vec()).unwrap(); match &self.backend { Backend::Local { backend, .. } => backend .read_file(&c_path) @@ -102,8 +113,8 @@ impl ProcessInspector { } } - pub fn read_dir(&self, path: impl Into) -> Result { - let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + pub fn read_dir(&self, path: PathBuf) -> Result { + let c_path = CString::new(path.into_os_string().into_vec()).unwrap(); match &self.backend { Backend::Local { backend, .. } => backend .read_dir(&c_path) @@ -112,8 +123,8 @@ impl ProcessInspector { } } - pub fn read_link(&self, path: impl Into) -> Result { - let c_path = CString::new(path.into().into_os_string().into_vec()).unwrap(); + pub fn read_link(&self, path: PathBuf) -> Result { + let c_path = CString::new(path.into_os_string().into_vec()).unwrap(); let mut buf = vec![0u8; MAX_PATH_LEN]; @@ -127,45 +138,46 @@ impl ProcessInspector { Ok(PathBuf::from(OsString::from_vec(buf))) } - pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_gen_regs(&self, tid: libc::pid_t) -> Result { match &self.backend { Backend::Local { backend, .. } => backend.get_gen_regs(tid).map_err(Error::Local), } } - pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_fp_regs(&self, tid: libc::pid_t) -> Result { match &self.backend { Backend::Local { backend, .. } => backend.get_fp_regs(tid).map_err(Error::Local), } } #[cfg(target_arch = "x86")] - pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { + pub fn get_fpx_regs(&self, tid: libc::pid_t) -> Result { match &self.backend { Backend::Local { backend, .. } => backend.get_fpx_regs(tid).map_err(Error::Local), } } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - pub fn ptrace_peekuser( - &self, - pid: libc::pid_t, - addr: usize, - ) -> Result<[u8; core::mem::size_of::()], Error> { + pub fn ptrace_peekuser(&self, addr: usize) -> Result<[u8; PTRACE_DATA_LEN]> { match &self.backend { - Backend::Local { backend, .. } => { - backend.ptrace_peekuser(pid, addr).map_err(Error::Local) - } + Backend::Local { backend, .. } => backend.ptrace_peekuser(addr).map_err(Error::Local), } } - pub fn force_process_reader_kind(&mut self, kind: ProcessReaderKind) -> Result<(), Error> { + pub fn force_process_reader_kind(&mut self, kind: ProcessReaderKind) -> Result<()> { match &mut self.backend { Backend::Local { backend, .. } => backend .force_process_reader_kind(kind) .map_err(Error::Local), } } + + #[doc(hidden)] + pub fn fail_one_syscall_with(&self, errno: c_int) { + match &self.backend { + Backend::Local { backend, .. } => backend.fail_one_syscall_with(errno), + } + } } #[derive(Debug)] @@ -188,23 +200,16 @@ pub enum DirReader { } impl Iterator for DirReader { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { match self { - Self::Local(l) => match l.read_name().map_err(Error::Local) { - Ok(Some(name_bytes)) => Some(Ok(OsString::from_vec(name_bytes.to_vec()))), - Ok(None) => None, - Err(e) => Some(Err(e)), - }, - } - } -} - -#[doc(hidden)] -impl ProcessInspector { - pub fn fail_one_syscall_with(&self, errno: c_int) { - match &self.backend { - Backend::Local { backend, .. } => backend.fail_one_syscall_with(errno), + Self::Local(l) => Some( + l.read_next_name() + .transpose()? + .map(<[u8]>::to_vec) + .map(OsString::from_vec) + .map_err(Error::Local), + ), } } } @@ -215,32 +220,63 @@ pub enum MappedModuleMemoryReader { } impl MappedModuleMemoryReader { - pub fn read(&self, offset: u64, length: u64) -> Result<&[u8], Error> { - match self { - Self::Local(l) => l.read(offset, length).map_err(Error::Local), + pub fn read_exact_at(&self, mut offset: usize, mut buf: &mut [u8]) -> Result<()> { + if buf.is_empty() { + return Ok(()); } - } - pub fn len(&self) -> Result { + match self { - Self::Local(l) => l.len().map_err(Error::Local), + Self::Local(l) => loop { + let bytes = l.read_at(offset, buf.len()).map_err(Error::Local)?; + if bytes.is_empty() { + return Err(Error::UnexpectedEndOfBuffer); + } + let (dst, tail) = buf.split_at_mut(bytes.len()); + dst.copy_from_slice(bytes); + if tail.is_empty() { + return Ok(()); + } + offset = offset + .checked_add(dst.len()) + .ok_or(Error::AddressOverflowed)?; + buf = tail; + }, } } - pub fn is_empty(&self) -> Result { + pub fn len(&self) -> Result { match self { - Self::Local(l) => l.is_empty().map_err(Error::Local), + Self::Local(l) => Ok(l.len()), } } } impl ReadModuleMemory for MappedModuleMemoryReader { - fn read(&self, offset: u64, length: u64) -> Result, ModuleMemoryReadError> { - self.read(offset, length) - .map(Cow::Borrowed) - .map_err(|e| ModuleMemoryReadError { + fn read( + &self, + offset: u64, + length: u64, + ) -> core::result::Result, ModuleMemoryReadError> { + let result = (|| { + let (offset, length) = match (usize::try_from(offset), usize::try_from(length)) { + (Ok(o), Ok(l)) => (o, l), + _ => return Err(ReadError::OutOfBounds), + }; + + let mut buf = vec![0u8; length]; + + self.read_exact_at(offset, &mut buf) + .map_err(ReadError::PlatformSpecific)?; + + Ok(buf) + })(); + + result + .map(Cow::Owned) + .map_err(|error| ModuleMemoryReadError { offset, length, start_address: None, - error: ReadError::PlatformSpecific(e), + error, }) } fn absolute_to_relative(&self, addr: u64) -> Option { @@ -255,10 +291,14 @@ impl ReadModuleMemory for MappedModuleMemoryReader { } } -#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] +#[derive(Debug, thiserror::Error, serde::Serialize)] pub enum Error { #[error("an error occurred running a syscall directly")] Local(#[source] local::Error), + #[error("an address overflowed")] + AddressOverflowed, + #[error("unexpected end of buffer reached")] + UnexpectedEndOfBuffer, } fn set_process_backend_drop_fail_handler() { diff --git a/src/linux/process_inspection/process_reader.rs b/src/linux/process_inspection/process_reader.rs index a9f6a856..ed467c7f 100644 --- a/src/linux/process_inspection/process_reader.rs +++ b/src/linux/process_inspection/process_reader.rs @@ -12,10 +12,6 @@ pub struct ProcessReader<'a> { } impl<'a> ProcessReader<'a> { - pub fn new(process_inspector: &'a ProcessInspector) -> Self { - Self { process_inspector } - } - /// Read memory from the process into the given buffer. /// /// Returns the number of bytes read. @@ -58,9 +54,12 @@ impl<'a> ProcessReader<'a> { }) .ok_or(FindModuleError::ModuleNotFound) } + pub(crate) fn new(process_inspector: &'a ProcessInspector) -> Self { + Self { process_inspector } + } } -#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)] +#[derive(Debug, thiserror::Error, serde::Serialize)] pub enum CopyFromProcessError { #[error("an error occurred calling ProcessReader")] Backend(Error), diff --git a/src/linux/thread_info/x86.rs b/src/linux/thread_info/x86.rs index 0f2e2d8f..b6e5e95b 100644 --- a/src/linux/thread_info/x86.rs +++ b/src/linux/thread_info/x86.rs @@ -42,7 +42,7 @@ impl ThreadInfoX86 { let debug_offset = mem::offset_of!(user, u_debugreg); for (idx, dreg) in dregs.iter_mut().enumerate() { let chunk = process_inspector - .ptrace_peekuser(tid, debug_offset + idx * mem::size_of::()) + .ptrace_peekuser(debug_offset + idx * mem::size_of::()) .map_err(ThreadInfoError::PtraceError)?; *dreg = RegType::from_ne_bytes(chunk[0..mem::size_of::()].try_into().unwrap()); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index bf6368b1..7c8321bd 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,6 +1,6 @@ use std::{ error, - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader}, process::{Child, Command, Stdio}, result, }; @@ -47,8 +47,15 @@ pub fn spawn_child(command: &str, args: &[&str]) { let child = cmd.output().expect("failed to execute child"); println!("Child output:"); - std::io::stdout().write_all(&child.stdout).unwrap(); - std::io::stdout().write_all(&child.stderr).unwrap(); + println!("===stdout==="); + + print_stdio(&child.stdout); + + println!("\n===stderr==="); + + print_stdio(&child.stderr); + + println!("\n============"); assert_eq!(child.status.code().expect("No return value"), 0); } @@ -193,3 +200,19 @@ mod linux { } } } + +fn print_stdio(bytes: &[u8]) { + if let Ok(s) = str::from_utf8(bytes) { + print!("{s}"); + } else { + for (idx, b) in bytes.iter().enumerate() { + if idx == 0 { + print!("{b:02x}"); + } else if idx % 16 == 0 { + print!("\n{b:02x}"); + } else { + print!(" {b:02x}"); + } + } + } +} diff --git a/tests/linux_minidump_writer_soft_error.rs b/tests/linux_minidump_writer_soft_error.rs index 0b2bd1a0..7032d6b1 100644 --- a/tests/linux_minidump_writer_soft_error.rs +++ b/tests/linux_minidump_writer_soft_error.rs @@ -1,11 +1,9 @@ #![cfg(any(target_os = "linux", target_os = "android"))] -use { - common::*, - minidump::Minidump, - minidump_writer::{FailSpotName, minidump_writer::MinidumpWriterConfig}, - serde_json::json, -}; +use common::*; +use minidump::Minidump; +use minidump_writer::{FailSpotName, minidump_writer::MinidumpWriterConfig}; +use serde_json as json; mod common; @@ -34,43 +32,29 @@ fn soft_error_stream() { read_minidump_soft_errors_or_panic(&dump); } +fn visit_json_terminals(json: &json::Value, path: &mut Vec, visit: &mut V) +where + V: FnMut(&[String], &json::Value), +{ + if let Some(obj) = json.as_object() { + for (key, value) in obj.iter() { + path.push(key.clone()); + visit_json_terminals(value, path, visit); + path.pop(); + } + return; + } + if let Some(arr) = json.as_array() { + for value in arr.iter() { + visit_json_terminals(value, path, visit); + } + return; + } + visit(path, json); +} + #[test] fn soft_error_stream_content() { - let expected_errors = vec![ - json!({"InitErrors": [ - {"StopProcessFailed": - {"Stop": - {"Local": - {"SigStopFailed": 1} - } - } - }, - {"FillMissingAuxvInfoErrors": ["InvalidFormat"]}, - {"EnumerateThreadsErrors": [ - {"ReadThreadNameFailed": "\ - Custom {\n \ - kind: Other,\n \ - error: Local(\n \ - OpenFileFailed(\n \ - 1,\n \ - ),\n \ - ),\n\ - }" - } - ]}, - {"SuspendThreadsErrors": [{"PtraceAttachError": [1234, libc::EPERM]}]} - ]}), - json!({"WriteSystemInfoErrors": [ - {"WriteCpuInformationFailed": { - "ReadFileError": { - "Local": { - "OpenFileFailed": 1 - } - } - }} - ]}), - ]; - let mut child = start_child_and_wait_for_threads(1); let pid = child.id() as i32; @@ -99,5 +83,46 @@ fn soft_error_stream_content() { // Ensure the MozSoftErrors stream contains the expected errors let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump"); - assert_soft_errors_in_minidump(&dump, &expected_errors); + + let actual_json = read_minidump_soft_errors_or_panic(&dump); + + let mut stop_process_error_found = false; + let mut missing_auxv_error_found = false; + let mut thread_name_error_found = false; + let mut suspend_thread_error_found = false; + let mut cpu_info_error_found = false; + + let mut path = Vec::new(); + + visit_json_terminals(&actual_json, &mut path, &mut |path, value| { + if value.as_i64() == Some(libc::EPERM.into()) + && path.contains(&"StopProcessFailed".to_string()) + { + stop_process_error_found = true; + } + if value.as_str() == Some("InvalidFormat") + && path.contains(&"FillMissingAuxvInfoErrors".to_string()) + { + missing_auxv_error_found = true; + } + if path.contains(&"ReadThreadNameFailed".to_string()) { + thread_name_error_found = true; + } + if value.as_i64() == Some(libc::EPERM.into()) + && path.contains(&"SuspendThreadsErrors".to_string()) + { + suspend_thread_error_found = true; + } + if value.as_i64() == Some(libc::EPERM.into()) + && path.contains(&"WriteCpuInformationFailed".to_string()) + { + cpu_info_error_found = true; + } + }); + + assert!(stop_process_error_found); + assert!(missing_auxv_error_found); + assert!(thread_name_error_found); + assert!(suspend_thread_error_found); + assert!(cpu_info_error_found); } diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs index cdbb6f79..75be7002 100644 --- a/tests/ptrace_dumper.rs +++ b/tests/ptrace_dumper.rs @@ -12,6 +12,7 @@ use { mem::size_of, os::unix::process::ExitStatusExt, ptr, + sync::Mutex, }, }; @@ -35,13 +36,22 @@ macro_rules! assert_no_soft_errors(($n: ident, $e: expr) => {{ __result }}); +static GLOBAL_LOCK: Mutex<()> = Mutex::new(()); + +macro_rules! one_at_a_time(() => { + let _guard_ = GLOBAL_LOCK.lock().unwrap_or_else(|e| e.into_inner()); +}); + #[test] fn setup() { + one_at_a_time!(); + spawn_child("setup", &[]); } #[test] fn thread_list_from_child() { + one_at_a_time!(); // Child spawns and looks in the parent (== this process) for its own thread-ID let (tx, rx) = std::sync::mpsc::sync_channel(1); @@ -100,6 +110,8 @@ fn thread_list_from_child() { #[test] fn thread_list_from_parent() { + one_at_a_time!(); + let num_of_threads = 5; let mut child = start_child_and_wait_for_threads(num_of_threads); let pid = child.id() as i32; @@ -175,17 +187,23 @@ fn thread_list_from_parent() { #[test] // Ensure that the linux-gate VDSO is included in the mapping list. fn mappings_include_linux_gate() { + one_at_a_time!(); + spawn_child("mappings_include_linux_gate", &[]); } #[test] fn linux_gate_mapping_id() { + one_at_a_time!(); + disabled_on_ci_and_android!(); spawn_child("linux_gate_mapping_id", &[]); } #[test] fn merges_mappings() { + one_at_a_time!(); + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) }; assert!(page_size > 0); let page_size = usize::try_from(page_size).unwrap(); @@ -238,6 +256,8 @@ fn merges_mappings() { // Ensure that the linux-gate VDSO is included in the mapping list. #[test] fn file_id() { + one_at_a_time!(); + disabled_on_ci_and_android!(); spawn_child("file_id", &[]); } @@ -245,6 +265,8 @@ fn file_id() { #[cfg(not(target_os = "android"))] #[test] fn finds_mappings() { + one_at_a_time!(); + spawn_child( "find_mappings", &[ @@ -256,6 +278,8 @@ fn finds_mappings() { #[test] fn copies_from_process_self() { + one_at_a_time!(); + disabled_on_ci_and_android!(); let stack_var: libc::c_long = 0x11223344; @@ -272,6 +296,8 @@ fn copies_from_process_self() { // Ensures that we sanitize the stack properly #[test] fn sanitizes_stack_copies() { + one_at_a_time!(); + let num_of_threads = 1; let mut child = start_child_and_return(&["spawn_alloc_wait"]); let pid = child.id() as i32;