use {
super::{
Pid,
app_memory::AppMemoryList,
auxv::AuxvDumpInfo,
crash_context::CrashContext,
dso_debug,
dumper_cpu_info::CpuInfoError,
maps_reader::{MappingInfo, MappingList, MapsReaderError},
process_reader::{CopyFromProcessError, ProcessReader},
serializers::*,
thread_info::{ThreadInfo, ThreadInfoError},
},
crate::{
dir_section::{DirSection, DumpBuf},
mem_writer::{
Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError, write_string_to_location,
},
minidump_format::*,
module_reader,
serializers::*,
},
error_graph::{ErrorList, WriteErrorList},
errors::{ContinueProcessError, InitError, StopProcessError, WriterError},
failspot::failspot,
nix::{
errno::Errno,
sys::{ptrace, signal, wait},
},
procfs_core::{
FromRead,
process::{MMPermissions, ProcState, Stat},
},
std::{
io::{Seek, Write},
path,
time::{Duration, Instant},
},
thiserror::Error,
};
#[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};
pub mod app_memory;
pub mod errors;
pub mod exception_stream;
pub mod handle_data_stream;
pub mod mappings;
pub mod memory_info_list_stream;
pub mod memory_list_stream;
pub mod systeminfo_stream;
pub mod thread_list_stream;
pub mod thread_names_stream;
pub const STOP_TIMEOUT: Duration = Duration::from_millis(100);
#[cfg(target_pointer_width = "32")]
pub const AT_SYSINFO_EHDR: u32 = 33;
#[cfg(target_pointer_width = "64")]
pub const AT_SYSINFO_EHDR: u64 = 33;
#[derive(Debug)]
pub struct MinidumpWriterConfig {
process_id: Pid,
blamed_thread: Pid,
minidump_size_limit: Option<u64>,
skip_stacks_if_mapping_unreferenced: bool,
principal_mapping_address: Option<usize>,
user_mapping_list: MappingList,
app_memory: AppMemoryList,
memory_blocks: Vec<MDMemoryDescriptor>,
principal_mapping: Option<MappingInfo>,
sanitize_stack: bool,
crash_context: Option<CrashContext>,
crashing_thread_context: CrashingThreadContext,
stop_timeout: Duration,
direct_auxv_dump_info: Option<DirectAuxvDumpInfo>,
}
#[derive(Debug)]
pub struct MinidumpWriter {
pub process_id: Pid,
threads_suspended: bool,
pub threads: Vec<Thread>,
pub auxv: AuxvDumpInfo,
pub mappings: Vec<MappingInfo>,
pub page_size: usize,
pub sanitize_stack: bool,
pub minidump_size_limit: Option<u64>,
pub user_mapping_list: MappingList,
pub crashing_thread_context: CrashingThreadContext,
stop_timeout: Duration,
pub skip_stacks_if_mapping_unreferenced: bool,
principal_mapping_address: Option<usize>,
pub principal_mapping: Option<MappingInfo>,
pub blamed_thread: Pid,
pub crash_context: Option<CrashContext>,
pub app_memory: AppMemoryList,
pub memory_blocks: Vec<MDMemoryDescriptor>,
}
#[derive(Debug, Clone)]
pub struct Thread {
pub tid: Pid,
pub name: Option<String>,
}
#[derive(Debug, Default)]
pub enum CrashingThreadContext {
#[default]
None,
CrashContext(MDLocationDescriptor),
CrashContextPlusAddress((MDLocationDescriptor, usize)),
}
impl MinidumpWriterConfig {
pub fn new(process_id: Pid, blamed_thread: Pid) -> Self {
Self {
process_id,
blamed_thread,
minidump_size_limit: Default::default(),
skip_stacks_if_mapping_unreferenced: Default::default(),
principal_mapping_address: Default::default(),
user_mapping_list: Default::default(),
app_memory: Default::default(),
memory_blocks: Default::default(),
principal_mapping: Default::default(),
sanitize_stack: Default::default(),
crash_context: Default::default(),
crashing_thread_context: Default::default(),
stop_timeout: STOP_TIMEOUT,
direct_auxv_dump_info: Default::default(),
}
}
pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
self.minidump_size_limit = Some(limit);
self
}
pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
self.user_mapping_list = user_mapping_list;
self
}
pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
self.principal_mapping_address = Some(principal_mapping_address);
self
}
pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
self.app_memory = app_memory;
self
}
pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
self.crash_context = Some(crash_context);
self
}
pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
self.skip_stacks_if_mapping_unreferenced = true; self
}
pub fn sanitize_stack(&mut self) -> &mut Self {
self.sanitize_stack = true; self
}
pub fn stop_timeout(&mut self, duration: Duration) -> &mut Self {
self.stop_timeout = duration;
self
}
pub fn set_direct_auxv_dump_info(
&mut self,
direct_auxv_dump_info: DirectAuxvDumpInfo,
) -> &mut Self {
self.direct_auxv_dump_info = Some(direct_auxv_dump_info);
self
}
pub fn write(self, destination: &mut (impl Write + Seek)) -> Result<Vec<u8>, WriterError> {
let mut soft_errors = ErrorList::default();
let mut writer = self.build();
writer.init(soft_errors.subwriter(WriterError::InitErrors))?;
let mut buffer = Buffer::with_capacity(0);
writer.write_dump(&mut buffer, destination, soft_errors)?;
Ok(buffer.into())
}
pub fn build_for_testing(
self,
soft_errors: impl WriteErrorList<InitError>,
) -> Result<MinidumpWriter, InitError> {
let mut writer = self.build();
writer.init(soft_errors)?;
Ok(writer)
}
fn build(self) -> MinidumpWriter {
let auxv = self
.direct_auxv_dump_info
.map(AuxvDumpInfo::from)
.unwrap_or_default();
MinidumpWriter {
process_id: self.process_id,
threads_suspended: Default::default(),
threads: Default::default(),
auxv,
mappings: Default::default(),
page_size: Default::default(),
sanitize_stack: self.sanitize_stack,
minidump_size_limit: self.minidump_size_limit,
user_mapping_list: self.user_mapping_list,
crashing_thread_context: self.crashing_thread_context,
stop_timeout: self.stop_timeout,
skip_stacks_if_mapping_unreferenced: self.skip_stacks_if_mapping_unreferenced,
principal_mapping_address: self.principal_mapping_address,
principal_mapping: self.principal_mapping,
blamed_thread: self.blamed_thread,
crash_context: self.crash_context,
app_memory: self.app_memory,
memory_blocks: self.memory_blocks,
}
}
}
impl MinidumpWriter {
fn init(&mut self, mut soft_errors: impl WriteErrorList<InitError>) -> Result<(), InitError> {
if self.process_id == std::process::id() as i32 {
return Err(InitError::CannotPtraceSameProcess);
}
if let Err(e) = self.stop_process(self.stop_timeout) {
soft_errors.push(InitError::StopProcessFailed(e));
}
if let Err(e) = self.auxv.try_filling_missing_info(
self.process_id,
soft_errors.subwriter(InitError::FillMissingAuxvInfoErrors),
) {
soft_errors.push(InitError::FillMissingAuxvInfoFailed(e));
}
if let Err(e) =
self.enumerate_threads(soft_errors.subwriter(InitError::EnumerateThreadsErrors))
{
soft_errors.push(InitError::EnumerateThreadsFailed(Box::new(e)));
}
if let Err(e) = self.enumerate_mappings() {
soft_errors.push(InitError::EnumerateMappingsFailed(Box::new(e)));
}
self.page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)?
.expect("page size apparently unlimited: doesn't make sense.")
as usize;
let threads_count = self.threads.len();
self.suspend_threads(soft_errors.subwriter(InitError::SuspendThreadsErrors));
if self.threads.is_empty() {
soft_errors.push(InitError::SuspendNoThreadsLeft(threads_count));
}
#[cfg(target_os = "android")]
{
late_process_mappings(self.process_id, &mut self.mappings)?;
}
if self.skip_stacks_if_mapping_unreferenced {
if let Some(address) = self.principal_mapping_address {
self.principal_mapping = self.find_mapping_no_bias(address).cloned();
}
if !self.crash_thread_references_principal_mapping() {
soft_errors.push(InitError::PrincipalMappingNotReferenced);
}
}
Ok(())
}
fn write_dump(
&mut self,
buffer: &mut DumpBuf,
destination: &mut (impl Write + Seek),
mut soft_errors: ErrorList<WriterError>,
) -> Result<(), WriterError> {
let num_writers = 18u32;
let mut header_section = MemoryWriter::<MDRawHeader>::alloc(buffer)?;
let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
let header = MDRawHeader {
signature: MD_HEADER_SIGNATURE,
version: MD_HEADER_VERSION,
stream_count: num_writers,
stream_directory_rva: dir_section.position(),
checksum: 0,
time_date_stamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as u32, flags: 0,
};
header_section.set_value(buffer, header)?;
dir_section.write_to_file(buffer, None)?;
let dirent = self.write_thread_list_stream(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = self.write_mappings(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
self.write_app_memory(buffer)?;
dir_section.write_to_file(buffer, None)?;
let dirent = self.write_memory_list_stream(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = self.write_exception_stream(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = systeminfo_stream::write(
buffer,
soft_errors.subwriter(WriterError::WriteSystemInfoErrors),
)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = self.write_memory_info_list_stream(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let mut proc_root = {
let mut pr = String::with_capacity(24);
use std::fmt::Write;
write!(&mut pr, "/proc/{}/", self.blamed_thread).unwrap(); pr
};
macro_rules! file_entry {
(res $write:expr, $kind:ident, $err:ident) => {
let dirent = match $write {
Ok(location) => MDRawDirectory {
stream_type: MDStreamType::$kind as u32,
location,
},
Err(e) => {
soft_errors.push(WriterError::$err(e));
Default::default()
}
};
dir_section.write_to_file(buffer, Some(dirent))?;
};
($fname:literal, $kind:ident, $err:ident) => {
let trunc = proc_root.len();
proc_root.push_str($fname);
file_entry!(res write_file(buffer, &proc_root), $kind, $err);
proc_root.truncate(trunc);
};
}
file_entry!(
res write_file(buffer, "/proc/cpuinfo"),
LinuxCpuInfo,
WriteCpuInfoFailed
);
file_entry!("status", LinuxProcStatus, WriteThreadProcStatusFailed);
#[cfg(not(target_os = "android"))]
{
file_entry!(
res write_file(buffer, "/etc/lsb-release")
.or_else(|_| write_file(buffer, "/etc/os-release")),
LinuxLsbRelease,
WriteOsReleaseInfoFailed
);
}
file_entry!("cmdline", LinuxCmdLine, WriteCommandLineFailed);
file_entry!("environ", LinuxEnviron, WriteEnvironmentFailed);
file_entry!("auxv", LinuxAuxv, WriteEnvironmentFailed);
file_entry!("maps", LinuxMaps, WriteMapsFailed);
let dirent = match dso_debug::write_dso_debug_stream(buffer, self.process_id, &self.auxv) {
Ok(dirent) => dirent,
Err(e) => {
soft_errors.push(WriterError::WriteDSODebugStreamFailed(e));
Default::default()
}
};
dir_section.write_to_file(buffer, Some(dirent))?;
file_entry!("limits", MozLinuxLimits, WriteLimitsFailed);
let dirent = self.write_thread_names_stream(buffer)?;
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = match self.write_handle_data_stream(buffer) {
Ok(dirent) => dirent,
Err(e) => {
soft_errors.push(WriterError::WriteHandleDataStreamFailed(e));
Default::default()
}
};
dir_section.write_to_file(buffer, Some(dirent))?;
let dirent = write_soft_errors(buffer, soft_errors)
.map(|location| MDRawDirectory {
stream_type: MDStreamType::MozSoftErrors as u32,
location,
})
.unwrap_or_default();
dir_section.write_to_file(buffer, Some(dirent))?;
Ok(())
}
fn crash_thread_references_principal_mapping(&self) -> bool {
if self.crash_context.is_none() || self.principal_mapping.is_none() {
return false;
}
let low_addr = self
.principal_mapping
.as_ref()
.unwrap()
.system_mapping_info
.start_address;
let high_addr = self
.principal_mapping
.as_ref()
.unwrap()
.system_mapping_info
.end_address;
let pc = self
.crash_context
.as_ref()
.unwrap()
.get_instruction_pointer();
let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
if pc >= low_addr && pc < high_addr {
return true;
}
let (valid_stack_pointer, stack_len) = match self.get_stack_info(stack_pointer) {
Ok(x) => x,
Err(_) => {
return false;
}
};
let stack_copy = match MinidumpWriter::copy_from_process(
self.blamed_thread,
valid_stack_pointer,
stack_len,
) {
Ok(x) => x,
Err(_) => {
return false;
}
};
let sp_offset = stack_pointer.saturating_sub(valid_stack_pointer);
self.principal_mapping
.as_ref()
.unwrap()
.stack_has_pointer_to_mapping(&stack_copy, sp_offset)
}
fn suspend_thread(child: Pid) -> Result<(), WriterError> {
use WriterError::PtraceAttachError as AttachErr;
let pid = nix::unistd::Pid::from_raw(child);
ptrace::attach(pid).map_err(|e| AttachErr(child, e))?;
loop {
match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) {
Ok(status) => {
let wait::WaitStatus::Stopped(_, status) = status else {
return Err(WriterError::WaitPidError(
child,
nix::errno::Errno::UnknownErrno,
));
};
if status == nix::sys::signal::SIGSTOP {
break;
}
if let Err(err) = ptrace::cont(pid, status) {
return Err(WriterError::WaitPidError(child, err));
}
}
Err(Errno::EINTR) => continue,
Err(e) => {
ptrace_detach(child)?;
return Err(WriterError::WaitPidError(child, e));
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
let skip_thread;
let regs = thread_info::ThreadInfo::getregs(pid.into());
if let Ok(regs) = regs {
#[cfg(target_arch = "x86_64")]
{
skip_thread = regs.rsp == 0;
}
#[cfg(target_arch = "x86")]
{
skip_thread = regs.esp == 0;
}
} else {
skip_thread = true;
}
if skip_thread {
ptrace_detach(child)?;
return Err(WriterError::DetachSkippedThread(child));
}
}
Ok(())
}
fn resume_thread(child: Pid) -> Result<(), WriterError> {
ptrace_detach(child)
}
fn suspend_threads(&mut self, mut soft_errors: impl WriteErrorList<WriterError>) {
self.threads.retain(|x| match Self::suspend_thread(x.tid) {
Ok(()) => true,
Err(e) => {
soft_errors.push(e);
false
}
});
self.threads_suspended = true;
failspot::failspot!(<crate::FailSpotName>::SuspendThreads soft_errors.push(WriterError::PtraceAttachError(1234, nix::Error::EPERM)))
}
fn resume_threads(&mut self, mut soft_errors: impl WriteErrorList<WriterError>) {
if self.threads_suspended {
for thread in &self.threads {
match Self::resume_thread(thread.tid) {
Ok(()) => (),
Err(e) => {
soft_errors.push(e);
}
}
}
}
self.threads_suspended = false;
}
fn stop_process(&mut self, timeout: Duration) -> Result<(), StopProcessError> {
failspot!(StopProcess bail(nix::Error::EPERM));
signal::kill(
nix::unistd::Pid::from_raw(self.process_id),
Some(signal::SIGSTOP),
)?;
const POLL_INTERVAL: Duration = Duration::from_millis(1);
let proc_file = format!("/proc/{}/stat", self.process_id);
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);
}
}
}
fn continue_process(&mut self) -> Result<(), ContinueProcessError> {
signal::kill(
nix::unistd::Pid::from_raw(self.process_id),
Some(signal::SIGCONT),
)?;
Ok(())
}
fn enumerate_threads(
&mut self,
mut soft_errors: impl WriteErrorList<InitError>,
) -> Result<(), InitError> {
let pid = self.process_id;
let filename = format!("/proc/{pid}/task");
let task_path = path::PathBuf::from(&filename);
if !task_path.is_dir() {
return Err(InitError::ProcPidTaskNotDirectory(filename));
}
for entry in std::fs::read_dir(task_path).map_err(|e| InitError::IOError(filename, e))? {
let entry = match entry {
Ok(entry) => entry,
Err(e) => {
soft_errors.push(InitError::ReadProcessThreadEntryFailed(e));
continue;
}
};
let file_name = entry.file_name();
let tid = match file_name.to_str().and_then(|name| name.parse::<Pid>().ok()) {
Some(tid) => tid,
None => {
soft_errors.push(InitError::ProcessTaskEntryNotTid(file_name));
continue;
}
};
let name_result = failspot!(if ThreadName {
Err(std::io::Error::other(
"testing requested failure reading thread name",
))
} else {
std::fs::read_to_string(format!("/proc/{pid}/task/{tid}/comm"))
});
let name = match name_result {
Ok(name) => Some(name.trim_end().to_string()),
Err(e) => {
soft_errors.push(InitError::ReadThreadNameFailed(e));
None
}
};
self.threads.push(Thread { tid, name });
}
Ok(())
}
fn enumerate_mappings(&mut self) -> Result<(), InitError> {
self.mappings = MappingInfo::for_pid(self.process_id, self.auxv.get_linux_gate_address())
.map_err(InitError::AggregateMappingsFailed)?;
if let Some(entry_point_loc) = self
.auxv
.get_entry_address()
.map(|u| usize::try_from(u).unwrap())
{
if let Some(entry_mapping_idx) = self.mappings.iter().position(|mapping| {
(mapping.start_address..mapping.start_address + mapping.size)
.contains(&entry_point_loc)
}) {
self.mappings.swap(0, entry_mapping_idx);
}
}
Ok(())
}
pub fn get_thread_info_by_index(&self, index: usize) -> Result<ThreadInfo, ThreadInfoError> {
if index > self.threads.len() {
return Err(ThreadInfoError::IndexOutOfBounds(index, self.threads.len()));
}
ThreadInfo::create(self.process_id, self.threads[index].tid)
}
pub fn get_stack_info(&self, int_stack_pointer: usize) -> Result<(usize, usize), WriterError> {
let mut stack_pointer = int_stack_pointer & !(self.page_size - 1);
let mut mapping = self.find_mapping(stack_pointer);
let guard_page_max_addr = stack_pointer.saturating_add(1024 * 1024);
while !Self::may_be_stack(mapping) && (stack_pointer <= guard_page_max_addr) {
stack_pointer += self.page_size;
mapping = self.find_mapping(stack_pointer);
}
mapping
.map(|mapping| {
let valid_stack_pointer = if mapping.contains_address(stack_pointer) {
stack_pointer
} else {
mapping.start_address
};
let stack_len = mapping.size - (valid_stack_pointer - mapping.start_address);
(valid_stack_pointer, stack_len)
})
.ok_or(WriterError::NoStackPointerMapping)
}
fn may_be_stack(mapping: Option<&MappingInfo>) -> bool {
if let Some(mapping) = mapping {
return mapping
.permissions
.intersects(MMPermissions::READ | MMPermissions::WRITE);
}
false
}
pub fn sanitize_stack_copy(
&self,
stack_copy: &mut [u8],
stack_pointer: usize,
sp_offset: usize,
) -> Result<(), WriterError> {
let defaced;
#[cfg(target_pointer_width = "64")]
{
defaced = 0x0defaced0defacedusize.to_ne_bytes();
}
#[cfg(target_pointer_width = "32")]
{
defaced = 0x0defacedusize.to_ne_bytes();
};
let test_bits = 11;
let array_size: usize = 1 << (test_bits - 3);
let array_mask = array_size - 1;
let shift = 32 - 11;
let stack_mapping = self.find_mapping_no_bias(stack_pointer);
let mut last_hit_mapping: Option<&MappingInfo> = None;
let small_int_magnitude: isize = 4096;
let mut could_hit_mapping = vec![0; array_size];
for mapping in &self.mappings {
if !mapping.is_executable() {
continue;
}
let mut start = mapping.start_address;
let mut end = start + mapping.size;
start >>= shift;
end >>= shift;
for bit in start..=end {
could_hit_mapping[(bit >> 3) & array_mask] |= 1 << (bit & 7);
}
}
let offset =
(sp_offset + std::mem::size_of::<usize>() - 1) & !(std::mem::size_of::<usize>() - 1);
for x in &mut stack_copy[0..offset] {
*x = 0;
}
let mut chunks = stack_copy[offset..].chunks_exact_mut(std::mem::size_of::<usize>());
for sp in &mut chunks {
let addr = usize::from_ne_bytes(sp.to_vec().as_slice().try_into()?);
let addr_signed = isize::from_ne_bytes(sp.to_vec().as_slice().try_into()?);
if addr <= small_int_magnitude as usize && addr_signed >= -small_int_magnitude {
continue;
}
if let Some(stack_map) = stack_mapping
&& stack_map.contains_address(addr)
{
continue;
}
if let Some(last_hit) = last_hit_mapping
&& last_hit.contains_address(addr)
{
continue;
}
let test = addr >> shift;
if (could_hit_mapping[(test >> 3) & array_mask] & (1 << (test & 7)) != 0)
&& let Some(hit_mapping) = self.find_mapping_no_bias(addr)
&& hit_mapping.is_executable()
{
last_hit_mapping = Some(hit_mapping);
continue;
}
sp.copy_from_slice(&defaced);
}
for sp in chunks.into_remainder() {
*sp = 0;
}
Ok(())
}
pub fn find_mapping(&self, address: usize) -> Option<&MappingInfo> {
self.mappings
.iter()
.find(|map| address >= map.start_address && address - map.start_address < map.size)
}
pub fn find_mapping_no_bias(&self, address: usize) -> Option<&MappingInfo> {
self.mappings.iter().find(|map| {
address >= map.system_mapping_info.start_address
&& address < map.system_mapping_info.end_address
})
}
pub fn from_process_memory_for_index<T: module_reader::ReadFromModule>(
&mut self,
idx: usize,
) -> Result<T, WriterError> {
assert!(idx < self.mappings.len());
Self::from_process_memory_for_mapping(&self.mappings[idx], self.process_id)
}
pub fn from_process_memory_for_mapping<T: module_reader::ReadFromModule>(
mapping: &MappingInfo,
pid: Pid,
) -> Result<T, WriterError> {
let reader = ProcessReader::new(pid);
Ok(T::read_from_module(
module_reader::ModuleMemory::from_process(&reader, mapping.start_address),
)?)
}
#[inline]
pub fn copy_from_process(
pid: Pid,
src: usize,
length: usize,
) -> Result<Vec<u8>, CopyFromProcessError> {
let length = std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError {
src,
child: pid,
offset: 0,
length,
source: nix::errno::Errno::EINVAL,
})?;
let mem = ProcessReader::new(pid);
mem.read_to_vec(src, length)
}
}
impl Drop for MinidumpWriter {
fn drop(&mut self) {
self.resume_threads(error_graph::strategy::DontCare);
let _ = self.continue_process();
}
}
fn ptrace_detach(child: Pid) -> Result<(), WriterError> {
let pid = nix::unistd::Pid::from_raw(child);
ptrace::detach(pid, None).or_else(|e| {
if e == nix::Error::ESRCH {
Ok(())
} else {
Err(WriterError::PtraceDetachError(child, e))
}
})
}
fn write_file(
buffer: &mut DumpBuf,
filename: &str,
) -> std::result::Result<MDLocationDescriptor, MemoryWriterError> {
let content = std::fs::read(filename)?;
let section = MemoryArrayWriter::write_bytes(buffer, &content);
Ok(section.location())
}
fn write_soft_errors(
buffer: &mut DumpBuf,
soft_errors: ErrorList<WriterError>,
) -> Result<MDLocationDescriptor, WriterError> {
let soft_errors_json_str =
serde_json::to_string_pretty(&soft_errors).map_err(WriterError::ConvertToJsonFailed)?;
let section = MemoryArrayWriter::write_bytes(buffer, soft_errors_json_str.as_bytes());
Ok(section.location())
}