diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs')
-rw-r--r-- | third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs | 81 |
1 files changed, 73 insertions, 8 deletions
diff --git a/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs b/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs index f75499bcdd..0dd0fa2719 100644 --- a/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs +++ b/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs @@ -15,10 +15,20 @@ use crate::{ use goblin::elf; use nix::{ errno::Errno, - sys::{ptrace, wait}, + sys::{ptrace, signal, wait}, +}; +use procfs_core::{ + process::{MMPermissions, ProcState, Stat}, + FromRead, ProcError, +}; +use std::{ + collections::HashMap, + ffi::c_void, + io::BufReader, + path, + result::Result, + time::{Duration, Instant}, }; -use procfs_core::process::MMPermissions; -use std::{collections::HashMap, ffi::c_void, io::BufReader, path, result::Result}; #[derive(Debug, Clone)] pub struct Thread { @@ -45,9 +55,27 @@ impl Drop for PtraceDumper { fn drop(&mut self) { // Always try to resume all threads (e.g. in case of error) let _ = self.resume_threads(); + // Always allow the process to continue. + let _ = self.continue_process(); } } +#[derive(Debug, thiserror::Error)] +enum StopProcessError { + #[error("Failed to stop the process")] + Stop(#[from] Errno), + #[error("Failed to get the process state")] + State(#[from] ProcError), + #[error("Timeout waiting for process to stop")] + Timeout, +} + +#[derive(Debug, thiserror::Error)] +enum ContinueProcessError { + #[error("Failed to continue the process")] + Continue(#[from] Errno), +} + /// PTRACE_DETACH the given pid. /// /// This handles special errno cases (ESRCH) which we won't consider errors. @@ -67,7 +95,7 @@ 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) -> Result<Self, InitError> { + pub fn new(pid: Pid, stop_timeout: Duration) -> Result<Self, InitError> { let mut dumper = PtraceDumper { pid, threads_suspended: false, @@ -76,12 +104,16 @@ impl PtraceDumper { mappings: Vec::new(), page_size: 0, }; - dumper.init()?; + dumper.init(stop_timeout)?; Ok(dumper) } // TODO: late_init for chromeos and android - pub fn init(&mut self) -> Result<(), InitError> { + pub fn init(&mut self, stop_timeout: Duration) -> Result<(), InitError> { + // Stopping the process is best-effort. + if let Err(e) = self.stop_process(stop_timeout) { + log::warn!("failed to stop process {}: {e}", self.pid); + } self.read_auxv()?; self.enumerate_threads()?; self.enumerate_mappings()?; @@ -207,6 +239,38 @@ impl PtraceDumper { result } + /// Send SIGSTOP to the process so that we can get a consistent state. + /// + /// This will block waiting for the process to stop until `timeout` has passed. + fn stop_process(&mut self, timeout: Duration) -> Result<(), StopProcessError> { + signal::kill(nix::unistd::Pid::from_raw(self.pid), Some(signal::SIGSTOP))?; + + // 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.pid); + let end = Instant::now() + timeout; + + loop { + if let Ok(ProcState::Stopped) = Stat::from_file(&proc_file)?.state() { + return Ok(()); + } + + std::thread::sleep(POLL_INTERVAL); + if Instant::now() > end { + return Err(StopProcessError::Timeout); + } + } + } + + /// Send SIGCONT to the process to continue. + /// + /// Unlike `stop_process`, this function does not wait for the process to continue. + fn continue_process(&mut self) -> Result<(), ContinueProcessError> { + signal::kill(nix::unistd::Pid::from_raw(self.pid), Some(signal::SIGCONT))?; + Ok(()) + } + /// Parse /proc/$pid/task to list all the threads of the process identified by /// pid. fn enumerate_threads(&mut self) -> Result<(), InitError> { @@ -334,8 +398,9 @@ impl PtraceDumper { let mut mapping = self.find_mapping(stack_pointer); // The guard page has been 1 MiB in size since kernel 4.12, older - // kernels used a 4 KiB one instead. - let guard_page_max_addr = stack_pointer + (1024 * 1024); + // kernels used a 4 KiB one instead. Note the saturating add, as 32-bit + // processes can have a stack pointer within 1MiB of usize::MAX + let guard_page_max_addr = stack_pointer.saturating_add(1024 * 1024); // If we found no mapping, or the mapping we found has no permissions // then we might have hit a guard page, try looking for a mapping in |