Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 20 additions & 12 deletions src/linux/minidump_writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use {
dso_debug,
dumper_cpu_info::CpuInfoError,
maps_reader::{MappingInfo, MappingList, MapsReaderError},
process_inspection::ProcessInspector,
process_reader::{CopyFromProcessError, ProcessReader},
serializers::*,
thread_info::{ThreadInfo, ThreadInfoError},
Expand Down Expand Up @@ -41,8 +42,6 @@ use {

#[cfg(target_os = "android")]
use super::android::late_process_mappings;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use super::thread_info;

pub use super::auxv::{AuxvType, DirectAuxvDumpInfo};

Expand Down Expand Up @@ -82,6 +81,7 @@ pub struct MinidumpWriterConfig {
crashing_thread_context: CrashingThreadContext,
stop_timeout: Duration,
direct_auxv_dump_info: Option<DirectAuxvDumpInfo>,
process_inspector: ProcessInspector,
}

#[derive(Debug)]
Expand All @@ -104,6 +104,7 @@ pub struct MinidumpWriter {
pub crash_context: Option<CrashContext>,
pub app_memory: AppMemoryList,
pub memory_blocks: Vec<MDMemoryDescriptor>,
process_inspector: ProcessInspector,
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -137,6 +138,7 @@ impl MinidumpWriterConfig {
crashing_thread_context: Default::default(),
stop_timeout: STOP_TIMEOUT,
direct_auxv_dump_info: Default::default(),
process_inspector: ProcessInspector::local(),
}
}

Expand Down Expand Up @@ -243,6 +245,7 @@ impl MinidumpWriterConfig {
crash_context: self.crash_context,
app_memory: self.app_memory,
memory_blocks: self.memory_blocks,
process_inspector: self.process_inspector,
}
}
}
Expand Down Expand Up @@ -515,7 +518,10 @@ impl MinidumpWriter {
}

/// Suspends a thread by attaching to it.
fn suspend_thread(child: Pid) -> Result<(), WriterError> {
fn suspend_thread(
_process_inspector: &ProcessInspector,
child: Pid,
) -> Result<(), WriterError> {
use WriterError::PtraceAttachError as AttachErr;

let pid = nix::unistd::Pid::from_raw(child);
Expand Down Expand Up @@ -561,7 +567,7 @@ impl MinidumpWriter {
// We thus test the stack pointer and exclude any threads that are part of
// the seccomp sandbox's trusted code.
let skip_thread;
let regs = thread_info::ThreadInfo::getregs(pid.into());
let regs = _process_inspector.get_gen_regs(pid.into());
if let Ok(regs) = regs {
#[cfg(target_arch = "x86_64")]
{
Expand Down Expand Up @@ -592,13 +598,15 @@ impl MinidumpWriter {
// If the thread either disappeared before we could attach to it, or if
// it was part of the seccomp sandbox's trusted code, it is OK to
// silently drop it from the minidump.
self.threads.retain(|x| match Self::suspend_thread(x.tid) {
Ok(()) => true,
Err(e) => {
soft_errors.push(e);
false
}
});
self.threads.retain(
|x| match Self::suspend_thread(&self.process_inspector, x.tid) {
Ok(()) => true,
Err(e) => {
soft_errors.push(e);
false
}
},
);

self.threads_suspended = true;

Expand Down Expand Up @@ -755,7 +763,7 @@ impl MinidumpWriter {
return Err(ThreadInfoError::IndexOutOfBounds(index, self.threads.len()));
}

ThreadInfo::create(self.process_id, self.threads[index].tid)
ThreadInfo::create(&self.process_inspector, self.threads[index].tid)
}

// Returns a valid stack pointer and the mapping that contains the stack.
Expand Down
19 changes: 12 additions & 7 deletions src/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// `WriterError` is large and clippy doesn't like that, but not a huge deal atm
#![allow(clippy::result_large_err)]

#[cfg(target_os = "android")]
mod android;
pub use maps_reader::LINUX_GATE_LIBRARY_NAME;

pub mod app_memory;
pub(crate) mod auxv;
pub mod crash_context;
mod dso_debug;
mod dumper_cpu_info;
pub mod maps_reader;
pub mod minidump_writer;
pub mod module_reader;
pub mod process_reader;
mod serializers;
pub mod thread_info;

pub use maps_reader::LINUX_GATE_LIBRARY_NAME;
pub(crate) mod auxv;

mod dso_debug;
mod dumper_cpu_info;
mod process_inspection;
mod serializers;

#[cfg(target_os = "android")]
mod android;

pub type Pid = i32;
141 changes: 141 additions & 0 deletions src/linux/process_inspection/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use {
core::{ffi::c_void, mem},
nix::errno::Errno,
regs::*,
std::{
fs::File,
io::{self, Read},
path::Path,
},
};

pub mod regs;

#[cfg(target_env = "gnu")]
type PtraceRequestType = core::ffi::c_uint;

#[cfg(not(target_env = "gnu"))]
type PtraceRequestType = core::ffi::c_int;

#[derive(Debug)]
pub struct ProcessInspector {
_private: (), // Placeholder to force API usage for creation
}

impl ProcessInspector {
pub fn local() -> Self {
ProcessInspector { _private: () }
}

pub fn read_file(&self, path: impl AsRef<Path>) -> io::Result<impl Read> {
File::open(path)
}

pub fn get_gen_regs(&self, tid: libc::pid_t) -> nix::Result<GenRegs> {
getregset(tid).or_else(|_| getregs(tid))
}

pub fn get_fp_regs(&self, tid: libc::pid_t) -> nix::Result<FpRegs> {
getfpregset(tid).or_else(|_| getfpregs(tid))
}

#[cfg(target_arch = "x86")]
pub fn get_fpx_regs(&self, tid: libc::pid_t) -> nix::Result<FpxRegs> {
const PTRACE_GETFPXREGS: PtraceRequestType = 18;
unsafe { ptrace_getregs::<FpxRegs>(PTRACE_GETFPXREGS, tid) }
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn ptrace_peekuser(
&self,
pid: libc::pid_t,
addr: usize,
) -> nix::Result<[u8; mem::size_of::<libc::c_long>()]> {
// Since ptrace() is vararg, best to explicitly state arg types
let addr: *mut libc::c_void = addr as *mut libc::c_void;
let data: *mut libc::c_void = core::ptr::null_mut();
Errno::set_raw(0);
let rv = unsafe { libc::ptrace(libc::PTRACE_PEEKUSER, pid, addr, data) };
if rv == -1 && Errno::last_raw() != 0 {
Err(Errno::last())
} else {
Ok(rv.to_ne_bytes())
}
}
}

fn getregset(_pid: libc::pid_t) -> nix::Result<GenRegs> {
#[cfg(target_arch = "arm")]
{
Err(Errno::ENOTSUP)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
{
const NT_PRSTATUS: usize = 1;
ptrace_getregset(NT_PRSTATUS, _pid)
}
}

fn getregs(pid: libc::pid_t) -> nix::Result<GenRegs> {
const PTRACE_GETREGS: PtraceRequestType = 12;
unsafe { ptrace_getregs::<GenRegs>(PTRACE_GETREGS, pid) }
}

fn getfpregset(pid: libc::pid_t) -> nix::Result<FpRegs> {
#[cfg(target_arch = "arm")]
{
const NT_ARM_VFP: usize = 0x400;
ptrace_getregset(NT_ARM_VFP, pid)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
{
const NT_PRFPREGSET: usize = 2;
ptrace_getregset(NT_PRFPREGSET, pid)
}
}

fn getfpregs(_pid: libc::pid_t) -> nix::Result<FpRegs> {
#[cfg(target_arch = "arm")]
{
Err(Errno::ENOTSUP)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
{
const PTRACE_GETFPREGS: PtraceRequestType = 14;
unsafe { ptrace_getregs::<FpRegs>(PTRACE_GETFPREGS, _pid) }
}
}

/// Safety: RequestType and T must agree on the size of the returned type
unsafe fn ptrace_getregs<T>(request: PtraceRequestType, pid: libc::pid_t) -> nix::Result<T> {
let mut output = mem::MaybeUninit::<T>::uninit();

// Since ptrace() is vararg, best to explicitly state arg types
let addr: *mut c_void = core::ptr::null_mut();
let data: *mut c_void = output.as_mut_ptr().cast();
let res = unsafe { libc::ptrace(request, pid, addr, data) };
Errno::result(res)?;
Ok(unsafe { output.assume_init() })
}

fn ptrace_getregset<T>(regset_type: usize, pid: libc::pid_t) -> nix::Result<T> {
let mut output = mem::MaybeUninit::<T>::uninit();
let mut io = libc::iovec {
iov_base: output.as_mut_ptr().cast(),
iov_len: mem::size_of::<T>(),
};

// Since ptrace() is vararg, best to explicitly state arg types
let addr: *mut c_void = regset_type as *mut c_void;
let data: *mut c_void = (&raw mut io).cast();
let res = unsafe { libc::ptrace(libc::PTRACE_GETREGSET, pid, addr, data) };
Errno::result(res)?;

// PTRACE_GETREGSET returns the number of bytes actually read in iov_len. Need to ensure
// all bytes of T are actually initialized
if io.iov_len != mem::size_of::<T>() {
return Err(Errno::EINVAL);
}

Ok(unsafe { output.assume_init() })
}
Loading
Loading