Skip to content

Commit

Permalink
WIP: Add soft errors to minidump
Browse files Browse the repository at this point in the history
Fixes #31
  • Loading branch information
Chris Martin committed Oct 11, 2024
1 parent 86febec commit 6bc6d73
Show file tree
Hide file tree
Showing 12 changed files with 511 additions and 150 deletions.
23 changes: 18 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ cfg-if = "1.0"
crash-context = "0.6"
log = "0.4"
memoffset = "0.9"
minidump-common = "0.22"
minidump-common = { path = "../rust-minidump/minidump-common" }
scroll = "0.12"
tempfile = "3.8"
thiserror = "1.0"
Expand Down
54 changes: 40 additions & 14 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ mod linux {

fn test_setup() -> Result<()> {
let ppid = getppid();
PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let (_dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
Ok(())
}

fn test_thread_list() -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let (dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
test!(!dumper.threads.is_empty(), "No threads");
test!(
dumper
Expand All @@ -59,8 +61,12 @@ mod linux {
use minidump_writer::mem_reader::MemReader;

let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;
let (mut dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;

if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}

// We support 3 different methods of reading memory from another
// process, ensure they all function and give the same results
Expand Down Expand Up @@ -113,13 +119,17 @@ mod linux {

test!(heap_res == expected_heap, "heap var not correct");

dumper.resume_threads()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}

Ok(())
}

fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let (dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
dumper
.find_mapping(addr1)
.ok_or("No mapping for addr1 found")?;
Expand All @@ -136,8 +146,12 @@ mod linux {
let ppid = getppid().as_raw();
let exe_link = format!("/proc/{ppid}/exe");
let exe_name = std::fs::read_link(exe_link)?.into_os_string();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;
let (mut dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}

let mut found_exe = None;
for (idx, mapping) in dumper.mappings.iter().enumerate() {
if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) {
Expand All @@ -147,15 +161,21 @@ mod linux {
}
let idx = found_exe.unwrap();
let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?;
dumper.resume_threads()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}
assert!(!id.is_empty());
assert!(id.iter().any(|&x| x > 0));
Ok(())
}

fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> {
// Now check that PtraceDumper interpreted the mappings properly.
let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT, Default::default())?;
let (dumper, _soft_errors) = PtraceDumper::new_report_soft_errors(
getppid().as_raw(),
STOP_TIMEOUT,
Default::default(),
)?;
let mut mapping_count = 0;
for map in &dumper.mappings {
if map
Expand All @@ -177,17 +197,22 @@ mod linux {

fn test_linux_gate_mapping_id() -> Result<()> {
let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
let (mut dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
let mut found_linux_gate = false;
for mapping in dumper.mappings.clone() {
if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
found_linux_gate = true;
dumper.suspend_threads()?;
if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}
let module_reader::BuildId(id) =
PtraceDumper::from_process_memory_for_mapping(&mapping, ppid)?;
test!(!id.is_empty(), "id-vec is empty");
test!(id.iter().any(|&x| x > 0), "all id elements are 0");
dumper.resume_threads()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}
break;
}
}
Expand All @@ -197,7 +222,8 @@ mod linux {

fn test_mappings_include_linux_gate() -> Result<()> {
let ppid = getppid().as_raw();
let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
let (dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap();
test!(linux_gate_loc != 0, "linux_gate_loc == 0");
let mut found_linux_gate = false;
Expand Down
101 changes: 101 additions & 0 deletions src/error_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Handling of "soft errors" while generating the minidump
/// Encapsulates a list of "soft error"s
///
/// A "soft error" is an error that is encounted while generating the minidump that doesn't
/// totally prevent the minidump from being useful, but it may have missing or invalid
/// information.
///
/// It should be returned by a function when the function was able to at-least partially achieve
/// its goals, and when further use of functions in the same API is permissible and can still be
/// at-least partially functional.
///
/// Admittedly, this concept makes layers of abstraction a bit more difficult, as something that
/// is considered "soft" by one layer may be a deal-breaker for the layer above it, or visa-versa
/// -- an error that a lower layer considers a total failure might just be a nuissance for the layer
/// above it.
///
/// An example of the former might be the act of suspending all the threads -- The `PTraceDumper``
/// API will actually work just fine even if none of the threads are suspended, so it only returns
/// a soft error; however, the dumper itself considers it to be a critical failure if not even one
/// thread could be stopped.
///
/// An example of the latter might trying to stop the process -- Being unable to send SIGSTOP to
/// the process would be considered a critical failure by `stop_process()`, but merely an
/// inconvenience by the code that's calling it.
#[must_use]
pub struct SoftErrorList<E> {
errors: Vec<E>,
}

impl<E> SoftErrorList<E> {
/// Returns `Some(Self)` if the list contains at least one soft error
pub fn some(self) -> Option<Self> {
if !self.is_empty() {
Some(self)
} else {
None
}
}
/// Returns `true` if the list is empty
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
/// Add a soft error to the list
pub fn push(&mut self, error: E) {
self.errors.push(error);
}
}

impl<E> Default for SoftErrorList<E> {
fn default() -> Self {
Self { errors: Vec::new() }
}
}

impl<E: std::error::Error> SoftErrorList<E> {
// Helper function for the Debug and Display traits
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, write_sources: bool) -> std::fmt::Result {
writeln!(f, "one or more soft errors occurred:")?;
writeln!(f)?;
for (i, e) in self.errors.iter().enumerate() {
writeln!(f, " {i}:")?;

for line in e.to_string().lines() {
writeln!(f, " {line}")?;
}

writeln!(f)?;

if write_sources {
let mut source = e.source();
while let Some(e) = source {
writeln!(f, " caused by:")?;

for line in e.to_string().lines() {
writeln!(f, " {line}")?;
}

writeln!(f)?;

source = e.source();
}
}
}
Ok(())
}
}

impl<E: std::error::Error> std::fmt::Debug for SoftErrorList<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt(f, true)
}
}

impl<E: std::error::Error> std::fmt::Display for SoftErrorList<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt(f, false)
}
}

impl<E: std::error::Error> std::error::Error for SoftErrorList<E> {}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub mod minidump_format;

pub mod dir_section;
pub mod mem_writer;

mod error_list;
24 changes: 18 additions & 6 deletions src/linux/auxv/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::error_list::SoftErrorList;
pub use reader::ProcfsAuxvIter;

use {
crate::Pid,
std::{fs::File, io::BufReader},
Expand Down Expand Up @@ -79,17 +81,27 @@ pub struct AuxvDumpInfo {
}

impl AuxvDumpInfo {
pub fn try_filling_missing_info(&mut self, pid: Pid) -> Result<(), AuxvError> {
pub fn try_filling_missing_info(
&mut self,
pid: Pid,
) -> Result<SoftErrorList<AuxvError>, AuxvError> {
if self.is_complete() {
return Ok(());
return Ok(SoftErrorList::default());
}

let auxv_path = format!("/proc/{pid}/auxv");
let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?;

for AuxvPair { key, value } in
ProcfsAuxvIter::new(BufReader::new(auxv_file)).filter_map(Result::ok)
{
let mut soft_errors = SoftErrorList::default();

for pair_result in ProcfsAuxvIter::new(BufReader::new(auxv_file)) {
let AuxvPair { key, value } = match pair_result {
Ok(pair) => pair,
Err(e) => {
soft_errors.push(e);
continue;
}
};
let dest_field = match key {
consts::AT_PHNUM => &mut self.program_header_count,
consts::AT_PHDR => &mut self.program_header_address,
Expand All @@ -102,7 +114,7 @@ impl AuxvDumpInfo {
}
}

Ok(())
Ok(soft_errors)
}
pub fn get_program_header_count(&self) -> Option<AuxvType> {
self.program_header_count
Expand Down
Loading

0 comments on commit 6bc6d73

Please sign in to comment.