summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/linux/ptrace_dumper.rs
diff options
context:
space:
mode:
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.rs81
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