From 83e241a6977515c30155d743a4204e142c8445bc Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 15 Jul 2022 13:14:50 +0200
Subject: [PATCH 1/5] Rework Windows minidump writer
---
src/windows/minidump_writer.rs | 132 ++++++++++++++++++++++---------
tests/windows_minidump_writer.rs | 39 ++-------
2 files changed, 103 insertions(+), 68 deletions(-)
diff --git a/src/windows/minidump_writer.rs b/src/windows/minidump_writer.rs
index 6a1bd2f1..325a6177 100644
--- a/src/windows/minidump_writer.rs
+++ b/src/windows/minidump_writer.rs
@@ -4,44 +4,96 @@ use scroll::Pwrite;
use std::{ffi::c_void, os::windows::io::AsRawHandle};
pub use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::{
- Foundation::{CloseHandle, ERROR_SUCCESS, STATUS_INVALID_HANDLE},
+ Foundation::{
+ CloseHandle, ERROR_SUCCESS, STATUS_INVALID_HANDLE, STATUS_NONCONTINUABLE_EXCEPTION,
+ },
System::{ApplicationVerifier as av, Diagnostics::Debug as md, Threading as threading},
};
pub struct MinidumpWriter {
- /// The crash context as captured by an exception handler
- crash_context: crash_context::CrashContext,
+ /// Optional exception information
+ exc_info: Option,
/// Handle to the crashing process, which could be ourselves
crashing_process: HANDLE,
- /// The `EXCEPTION_POINTERS` contained in crash context is a pointer into the
- /// memory of the process that crashed, as it contains an `EXCEPTION_RECORD`
- /// record which is an internally linked list, so in the case that we are
- /// dumping a process other than the current one, we need to tell
- /// MiniDumpWriteDump that the pointers come from an external process so that
- /// it can use eg ReadProcessMemory to get the contextual information from
- /// the crash, rather than from the current process
- is_external_process: bool,
+ /// The id of the process we are dumping
+ pid: u32,
}
impl MinidumpWriter {
- /// Creates a minidump writer capable of dumping the process specified by
- /// the [`crash_context::CrashContext`].
+ /// Creates a minidump of the current process, optionally including an
+ /// exception code and/or the CPU context of the current thread.
///
/// Note that it is inherently unreliable to dump the currently running
- /// processes, it is recommended to dump from an external process if possible.
+ /// process, at least in the event of an actual exception. It is recommended
+ /// to dump from an external process if possible via [`Self::dump_crash_context`]
+ ///
+ /// # Errors
+ ///
+ /// One or more system calls fail when gathering the minidump information
+ /// or writing it to the specified file
+ pub fn dump_current_context(
+ exception_code: Option,
+ include_cpu_context: bool,
+ destination: &mut std::fs::File,
+ ) -> Result<(), Error> {
+ let exception_code = exception_code.unwrap_or(STATUS_NONCONTINUABLE_EXCEPTION);
+
+ if include_cpu_context {
+ let mut exception_record: md::EXCEPTION_RECORD = std::mem::zeroed();
+ let mut exception_context = {
+ let mut ec = std::mem::MaybeUninit::uninit();
+
+ md::RtlCaptureContext(exception_context.as_mut_ptr());
+
+ exception_context.assume_init();
+ };
+
+ let exception_ptrs = md::EXCEPTION_POINTERS {
+ ExceptionRecord: &mut exception_record,
+ ContextRecord: &mut exception_context,
+ };
+
+ exception_record.ExceptionCode = exception_code;
+
+ let cc = crash_context::CrashContext {
+ exception_pointers: (&exception_ptrs as *const md::EXCEPTION_POINTERS).cast(),
+ process_id: std::process::id(),
+ thread_id: threading::GetCurrentThreadId(),
+ exception_code,
+ };
+
+ Self::dump_crash_context(cc, destination)
+ } else {
+ let cc = crash_context::CrashContext {
+ exception_pointers: std::ptr::null(),
+ process_id: std::process::id(),
+ thread_id: 0,
+ exception_code,
+ };
+
+ Self::dump_crash_context(cc, destination)
+ }
+ }
+
+ /// Writes a minidump for the context described by [`crash_context::CrashContext`].
///
/// # Errors
///
/// Fails if the process specified in the context is not the local process
/// and we are unable to open it due to eg. security reasons.
- pub fn new(crash_context: crash_context::CrashContext) -> Result {
+ pub fn dump_crash_context(
+ crash_context: crash_context::CrashContext,
+ destination: &mut std::fs::File,
+ ) -> Result<(), Error> {
+ let pid = crash_context.process_id;
+
// SAFETY: syscalls
let (crashing_process, is_external_process) = unsafe {
- if crash_context.process_id != std::process::id() {
+ if pid != std::process::id() {
let proc = threading::OpenProcess(
threading::PROCESS_ALL_ACCESS, // desired access
0, // inherit handles
- crash_context.process_id, // pid
+ pid, // pid
);
if proc == 0 {
@@ -54,30 +106,38 @@ impl MinidumpWriter {
}
};
- Ok(Self {
- crash_context,
- crashing_process,
- is_external_process: true,
- })
- }
+ let pid = crash_context.process_id;
- /// Writes a minidump to the specified file
- pub fn dump(&self, destination: &mut std::fs::File) -> Result<(), Error> {
- let exc_info = if !self.crash_context.exception_pointers.is_null() {
+ let exc_info = (!crash_context.exception_pointers.is_null()).then(||
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_exception_information
- Some(md::MINIDUMP_EXCEPTION_INFORMATION {
- ThreadId: self.crash_context.thread_id,
+ md::MINIDUMP_EXCEPTION_INFORMATION {
+ ThreadId: crash_context.thread_id,
// This is a mut pointer for some reason...I don't _think_ it is
// actually mut in practice...?
- ExceptionPointers: self.crash_context.exception_pointers as *mut _,
- ClientPointers: if self.is_external_process { 1 } else { 0 },
- })
- } else {
- None
+ ExceptionPointers: crash_context.exception_pointers as *mut _,
+ /// The `EXCEPTION_POINTERS` contained in crash context is a pointer into the
+ /// memory of the process that crashed, as it contains an `EXCEPTION_RECORD`
+ /// record which is an internally linked list, so in the case that we are
+ /// dumping a process other than the current one, we need to tell
+ /// `MiniDumpWriteDump` that the pointers come from an external process so that
+ /// it can use eg `ReadProcessMemory` to get the contextual information from
+ /// the crash, rather than from the current process
+ ClientPointers: if is_external_process { 1 } else { 0 },
+ });
+
+ let mdw = Self {
+ exc_info,
+ crashing_process,
+ pid,
};
- // This is a bit dangerous if doing in-process dumping, but that's not
- // (currently) a real target of this crate, so this allocation is fine
+ mdw.dump(destination)
+ }
+
+ /// Writes a minidump to the specified file
+ fn dump(mut self, destination: &mut std::fs::File) -> Result<(), Error> {
+ let exc_info = self.exc_info.take();
+
let mut user_streams = Vec::with_capacity(2);
let mut breakpad_info = self.fill_breakpad_stream();
@@ -118,7 +178,7 @@ impl MinidumpWriter {
let ret = unsafe {
md::MiniDumpWriteDump(
self.crashing_process, // HANDLE to the process with the crash we want to capture
- self.crash_context.process_id, // process id
+ self.pid, // process id
destination.as_raw_handle() as HANDLE, // file to write the minidump to
md::MiniDumpNormal, // MINIDUMP_TYPE - we _might_ want to make this configurable
exc_info
diff --git a/tests/windows_minidump_writer.rs b/tests/windows_minidump_writer.rs
index 038f8599..1904c6f1 100644
--- a/tests/windows_minidump_writer.rs
+++ b/tests/windows_minidump_writer.rs
@@ -34,34 +34,12 @@ fn dump_current_process() {
.tempfile()
.unwrap();
- unsafe {
- let mut exception_record: EXCEPTION_RECORD = mem::zeroed();
- let mut exception_context = mem::MaybeUninit::uninit();
-
- RtlCaptureContext(exception_context.as_mut_ptr());
-
- let mut exception_context = exception_context.assume_init();
-
- let exception_ptrs = EXCEPTION_POINTERS {
- ExceptionRecord: &mut exception_record,
- ContextRecord: &mut exception_context,
- };
-
- exception_record.ExceptionCode = STATUS_INVALID_PARAMETER;
-
- let crash_context = crash_context::CrashContext {
- exception_pointers: (&exception_ptrs as *const EXCEPTION_POINTERS).cast(),
- process_id: std::process::id(),
- thread_id: GetCurrentThreadId(),
- exception_code: STATUS_INVALID_PARAMETER,
- };
-
- let dumper = MinidumpWriter::new(crash_context).expect("failed to create MinidumpWriter");
-
- dumper
- .dump(tmpfile.as_file_mut())
- .expect("failed to write minidump");
- }
+ MinidumpWriter::dump_current_context(
+ Some(STATUS_INVALID_PARAMETER),
+ true,
+ tmpfile.as_file_mut(),
+ )
+ .expect("failed to write minidump");
let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
@@ -118,10 +96,7 @@ fn dump_external_process() {
.tempfile()
.unwrap();
- let dumper = MinidumpWriter::new(crash_context).expect("failed to create MinidumpWriter");
-
- dumper
- .dump(tmpfile.as_file_mut())
+ MinidumpWriter::dump_crash_context(crash_context, tmpfile.as_file_mut())
.expect("failed to write minidump");
child.kill().expect("failed to kill child");
From 99240c9eb3e2b3f901f958b58d058d5838147271 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 15 Jul 2022 16:00:43 +0200
Subject: [PATCH 2/5] Rework Windows API to support local dumping
---
src/bin/test.rs | 9 +--
src/windows/errors.rs | 6 ++
src/windows/minidump_writer.rs | 125 ++++++++++++++++++++++++-------
tests/windows_minidump_writer.rs | 76 ++++++++++++++++---
4 files changed, 171 insertions(+), 45 deletions(-)
diff --git a/src/bin/test.rs b/src/bin/test.rs
index 6ef236bf..6f84c799 100644
--- a/src/bin/test.rs
+++ b/src/bin/test.rs
@@ -297,12 +297,9 @@ mod linux {
mod windows {
use super::*;
use std::mem;
- use windows_sys::Win32::{
- Foundation::CloseHandle,
- System::{
- Diagnostics::Debug::{GetThreadContext, CONTEXT, EXCEPTION_POINTERS, EXCEPTION_RECORD},
- Threading::{GetCurrentProcessId, GetCurrentThread, GetCurrentThreadId},
- },
+ use windows_sys::Win32::System::{
+ Diagnostics::Debug::{GetThreadContext, CONTEXT, EXCEPTION_POINTERS, EXCEPTION_RECORD},
+ Threading::{GetCurrentProcessId, GetCurrentThread, GetCurrentThreadId},
};
#[inline(never)]
diff --git a/src/windows/errors.rs b/src/windows/errors.rs
index f85e9752..a2ba6c9b 100644
--- a/src/windows/errors.rs
+++ b/src/windows/errors.rs
@@ -4,4 +4,10 @@ pub enum Error {
Io(#[from] std::io::Error),
#[error(transparent)]
Scroll(#[from] scroll::Error),
+ #[error("Failed to open thread")]
+ ThreadOpen(#[source] std::io::Error),
+ #[error("Failed to suspend thread")]
+ ThreadSuspend(#[source] std::io::Error),
+ #[error("Failed to get thread context")]
+ ThreadContext(#[source] std::io::Error),
}
diff --git a/src/windows/minidump_writer.rs b/src/windows/minidump_writer.rs
index 325a6177..8bbcd79f 100644
--- a/src/windows/minidump_writer.rs
+++ b/src/windows/minidump_writer.rs
@@ -17,11 +17,18 @@ pub struct MinidumpWriter {
crashing_process: HANDLE,
/// The id of the process we are dumping
pid: u32,
+ /// The id of the 'crashing' thread
+ tid: u32,
+ /// The exception code for the dump
+ exception_code: i32,
+ /// Whether we are dumping the current process or not
+ is_external_process: bool,
}
impl MinidumpWriter {
/// Creates a minidump of the current process, optionally including an
- /// exception code and/or the CPU context of the current thread.
+ /// exception code and the CPU context of the specified thread. If no thread
+ /// is specified the current thread CPU context is used.
///
/// Note that it is inherently unreliable to dump the currently running
/// process, at least in the event of an actual exception. It is recommended
@@ -29,25 +36,83 @@ impl MinidumpWriter {
///
/// # Errors
///
- /// One or more system calls fail when gathering the minidump information
- /// or writing it to the specified file
- pub fn dump_current_context(
+ /// In addition to the errors described in [`Self::dump_crash_context`], this
+ /// function can also fail if `thread_id` is specified and we are unable to
+ /// acquire the thread's context
+ pub fn dump_local_context(
exception_code: Option,
- include_cpu_context: bool,
+ thread_id: Option,
destination: &mut std::fs::File,
) -> Result<(), Error> {
let exception_code = exception_code.unwrap_or(STATUS_NONCONTINUABLE_EXCEPTION);
- if include_cpu_context {
- let mut exception_record: md::EXCEPTION_RECORD = std::mem::zeroed();
- let mut exception_context = {
+ // SAFETY: syscalls, while this encompasses most of the function, the user
+ // has no invariants to uphold so the entire function is not marked unsafe
+ unsafe {
+ let mut exception_context = if let Some(tid) = thread_id {
let mut ec = std::mem::MaybeUninit::uninit();
- md::RtlCaptureContext(exception_context.as_mut_ptr());
+ // We need to suspend the thread to get its context, which would be bad
+ // if it's the current thread, so we check it early before regrets happen
+ if tid == threading::GetCurrentThreadId() {
+ md::RtlCaptureContext(ec.as_mut_ptr());
+ } else {
+ // We _could_ just fallback to the current thread if we can't get the
+ // thread handle, but probably better for this to fail with a specific
+ // error so that the caller can do that themselves if they want to
+ // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread
+ let thread_handle = threading::OpenThread(
+ threading::THREAD_GET_CONTEXT
+ | threading::THREAD_QUERY_INFORMATION
+ | threading::THREAD_SUSPEND_RESUME, // desired access rights, we only need to get the context, which also requires suspension
+ 0, // inherit handles
+ tid, // thread id
+ );
+
+ if thread_handle == 0 {
+ return Err(Error::ThreadOpen(std::io::Error::last_os_error()));
+ }
+
+ struct OwnedHandle(HANDLE);
+
+ impl Drop for OwnedHandle {
+ fn drop(&mut self) {
+ // SAFETY: syscall
+ unsafe { CloseHandle(self.0) };
+ }
+ }
+
+ let thread_handle = OwnedHandle(thread_handle);
+
+ // As noted in the GetThreadContext docs, we have to suspend the thread before we can get its context
+ // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread
+ if threading::SuspendThread(thread_handle.0) == u32::MAX {
+ return Err(Error::ThreadSuspend(std::io::Error::last_os_error()));
+ }
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext
+ if md::GetThreadContext(thread_handle.0, ec.as_mut_ptr()) == 0 {
+ // Try to be a good citizen and resume the thread
+ threading::ResumeThread(thread_handle.0);
+
+ return Err(Error::ThreadContext(std::io::Error::last_os_error()));
+ }
+
+ // _presumably_ this will not fail if SuspendThread succeeded, but if it does
+ // there's really not much we can do about it, thus we don't bother checking the
+ // return value
+ threading::ResumeThread(thread_handle.0);
+ }
- exception_context.assume_init();
+ ec.assume_init()
+ } else {
+ let mut ec = std::mem::MaybeUninit::uninit();
+ md::RtlCaptureContext(ec.as_mut_ptr());
+ ec.assume_init()
};
+ let mut exception_record: md::EXCEPTION_RECORD = std::mem::zeroed();
+
let exception_ptrs = md::EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
@@ -58,16 +123,7 @@ impl MinidumpWriter {
let cc = crash_context::CrashContext {
exception_pointers: (&exception_ptrs as *const md::EXCEPTION_POINTERS).cast(),
process_id: std::process::id(),
- thread_id: threading::GetCurrentThreadId(),
- exception_code,
- };
-
- Self::dump_crash_context(cc, destination)
- } else {
- let cc = crash_context::CrashContext {
- exception_pointers: std::ptr::null(),
- process_id: std::process::id(),
- thread_id: 0,
+ thread_id: thread_id.unwrap_or_else(|| threading::GetCurrentThreadId()),
exception_code,
};
@@ -80,8 +136,16 @@ impl MinidumpWriter {
/// # Errors
///
/// Fails if the process specified in the context is not the local process
- /// and we are unable to open it due to eg. security reasons.
- pub fn dump_crash_context(
+ /// and we are unable to open it due to eg. security reasons, or we fail to
+ /// write the minidump, which can be due to a host of issues with both acquiring
+ /// the process information as well as writing the actual minidump contents to disk
+ ///
+ /// # Safety
+ ///
+ /// If [`crash_context::CrashContext::exception_pointers`] is specified, it
+ /// is the responsibility of the caller to ensure that the pointer is valid
+ /// for the duration of this function call.
+ pub unsafe fn dump_crash_context(
crash_context: crash_context::CrashContext,
destination: &mut std::fs::File,
) -> Result<(), Error> {
@@ -107,6 +171,8 @@ impl MinidumpWriter {
};
let pid = crash_context.process_id;
+ let tid = crash_context.thread_id;
+ let exception_code = crash_context.exception_code;
let exc_info = (!crash_context.exception_pointers.is_null()).then(||
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_exception_information
@@ -129,6 +195,9 @@ impl MinidumpWriter {
exc_info,
crashing_process,
pid,
+ tid,
+ exception_code,
+ is_external_process,
};
mdw.dump(destination)
@@ -151,7 +220,11 @@ impl MinidumpWriter {
});
}
- let mut handle_stream_buffer = self.fill_handle_stream();
+ let mut handle_stream_buffer = if self.exception_code == STATUS_INVALID_HANDLE {
+ self.fill_handle_stream()
+ } else {
+ None
+ };
// Note that we do this by ref, as the buffer inside the option needs
// to stay alive for as long as we're writing the minidump since
@@ -214,7 +287,7 @@ impl MinidumpWriter {
let bp_info = MINIDUMP_BREAKPAD_INFO {
validity: BreakpadInfoValid::DumpThreadId.bits()
| BreakpadInfoValid::RequestingThreadId.bits(),
- dump_thread_id: self.crash_context.thread_id,
+ dump_thread_id: self.tid,
// Safety: syscall
requesting_thread_id: unsafe { threading::GetCurrentThreadId() },
};
@@ -239,10 +312,6 @@ impl MinidumpWriter {
/// the last invalid handle that is enumerated, which is, presumably
/// (hopefully?), the one that led to the exception
fn fill_handle_stream(&self) -> Option> {
- if self.crash_context.exception_code != STATUS_INVALID_HANDLE {
- return None;
- }
-
// State object we pass to the enumeration
struct HandleState {
ops: Vec,
diff --git a/tests/windows_minidump_writer.rs b/tests/windows_minidump_writer.rs
index 1904c6f1..75275786 100644
--- a/tests/windows_minidump_writer.rs
+++ b/tests/windows_minidump_writer.rs
@@ -1,15 +1,11 @@
#![cfg(all(target_os = "windows", target_arch = "x86_64"))]
-use minidump::{CrashReason, Minidump, MinidumpMemoryList, MinidumpSystemInfo, MinidumpThreadList};
-use minidump_writer::minidump_writer::MinidumpWriter;
-use std::mem;
-use windows_sys::Win32::{
- Foundation::{EXCEPTION_ILLEGAL_INSTRUCTION, STATUS_INVALID_PARAMETER},
- System::{
- Diagnostics::Debug::{RtlCaptureContext, EXCEPTION_POINTERS, EXCEPTION_RECORD},
- Threading::GetCurrentThreadId,
- },
+use minidump::{
+ CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpSystemInfo,
+ MinidumpThreadList,
};
+use minidump_writer::minidump_writer::MinidumpWriter;
+use windows_sys::Win32::Foundation::{EXCEPTION_ILLEGAL_INSTRUCTION, STATUS_INVALID_PARAMETER};
mod common;
use common::start_child_and_return;
@@ -34,13 +30,61 @@ fn dump_current_process() {
.tempfile()
.unwrap();
- MinidumpWriter::dump_current_context(
+ MinidumpWriter::dump_local_context(Some(STATUS_INVALID_PARAMETER), None, tmpfile.as_file_mut())
+ .expect("failed to write minidump");
+
+ let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
+
+ let _: MinidumpThreadList = md.get_stream().expect("Couldn't find MinidumpThreadList");
+ let _: MinidumpMemoryList = md.get_stream().expect("Couldn't find MinidumpMemoryList");
+ let _: MinidumpSystemInfo = md.get_stream().expect("Couldn't find MinidumpSystemInfo");
+
+ let crash_reason = get_crash_reason(&md);
+
+ assert_eq!(
+ crash_reason,
+ CrashReason::from_windows_error(STATUS_INVALID_PARAMETER as u32)
+ );
+
+ // SAFETY: syscall
+ let thread_id = unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() };
+
+ let bp_info: MinidumpBreakpadInfo =
+ md.get_stream().expect("Couldn't find MinidumpBreakpadInfo");
+
+ assert_eq!(bp_info.dump_thread_id.unwrap(), thread_id);
+ assert_eq!(bp_info.requesting_thread_id.unwrap(), thread_id);
+}
+
+#[test]
+fn dump_specific_thread() {
+ let mut tmpfile = tempfile::Builder::new()
+ .prefix("windows_current_process")
+ .tempfile()
+ .unwrap();
+
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ let jh = std::thread::spawn(move || {
+ // SAFETY: syscall
+ let thread_id = unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() };
+ while tx.send(thread_id).is_ok() {
+ std::thread::sleep(std::time::Duration::from_millis(10));
+ }
+ });
+
+ let crashing_thread_id = rx.recv().unwrap();
+
+ MinidumpWriter::dump_local_context(
Some(STATUS_INVALID_PARAMETER),
- true,
+ Some(crashing_thread_id),
tmpfile.as_file_mut(),
)
.expect("failed to write minidump");
+ drop(rx);
+ jh.join().unwrap();
+
let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
let _: MinidumpThreadList = md.get_stream().expect("Couldn't find MinidumpThreadList");
@@ -53,6 +97,16 @@ fn dump_current_process() {
crash_reason,
CrashReason::from_windows_error(STATUS_INVALID_PARAMETER as u32)
);
+
+ // SAFETY: syscall
+ let requesting_thread_id =
+ unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() };
+
+ let bp_info: MinidumpBreakpadInfo =
+ md.get_stream().expect("Couldn't find MinidumpBreakpadInfo");
+
+ assert_eq!(bp_info.dump_thread_id.unwrap(), crashing_thread_id);
+ assert_eq!(bp_info.requesting_thread_id.unwrap(), requesting_thread_id);
}
/// Ensures that we can write minidumps for an external process. Unfortunately
From 618fd8a5f9311c5565d1f4dff07e895abadf55d2 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 15 Jul 2022 16:13:13 +0200
Subject: [PATCH 3/5] Fix windows build
---
tests/windows_minidump_writer.rs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/tests/windows_minidump_writer.rs b/tests/windows_minidump_writer.rs
index 75275786..4ee2306b 100644
--- a/tests/windows_minidump_writer.rs
+++ b/tests/windows_minidump_writer.rs
@@ -150,8 +150,12 @@ fn dump_external_process() {
.tempfile()
.unwrap();
- MinidumpWriter::dump_crash_context(crash_context, tmpfile.as_file_mut())
- .expect("failed to write minidump");
+ // SAFETY: We keep the process we are dumping alive until the minidump is written
+ // and the test process keep the pointers it sent us alive until it is killed
+ unsafe {
+ MinidumpWriter::dump_crash_context(crash_context, tmpfile.as_file_mut())
+ .expect("failed to write minidump");
+ }
child.kill().expect("failed to kill child");
From 766fd823abf438b4f6abe3385d4b92860f22ff15 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 15 Jul 2022 17:09:34 +0200
Subject: [PATCH 4/5] Update README
---
README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 80 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 5bc6e58d..2b45cfb6 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,42 @@ This project is currently being very actively brought up from nothing, and is re
## Usage / Examples
+The primary use case of this crate is for creating a minidump for an **external** process (ie a process other than the one that writes the minidump) as writing minidumps from within a crashing process is inherently unreliable. That being said, there are scenarios where creating a minidump can be useful outside of a crash scenario thus each supported platforms has a way to generate a minidump for a local process as well.
+
+For more information on how to dump an external process you can check out the documentation or code for the [minidumper](https://docs.rs/minidumper/latest/minidumper/) crate.
+
### Linux
+#### Local process
+
+```rust
+fn write_minidump() {
+ // At a minimum, the crashdump writer needs to know the process and thread that you want to dump
+ let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(
+ std::process::id() as _,
+ // This gets the current thread, but you could get the id for any thread
+ // in the current process
+ unsafe { libc::syscall(libc::SYS_gettid) } as i32
+ );
+
+ // If provided with a full [crash_context::CrashContext](https://docs.rs/crash-context/latest/crash_context/struct.CrashContext.html),
+ // the crash will contain more info on the crash cause, such as the signal
+ //writer.set_crash_context(minidump_writer::crash_context::CrashContext { inner: crash_context });
+
+ // Here we could add more context or modify how the minidump is written, eg
+ // Add application specific memory blocks to the minidump
+ //writer.set_app_memory()
+ // Sanitize stack memory before it is written to the minidump by replacing
+ // non-pointer values with a sentinel value
+ //writer.sanitize_stack();
+
+ let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");
+ writer.dump(&mut minidump_file).expect("failed to write minidump");
+}
+```
+
+#### External process
+
```rust
fn write_minidump(crash_context: crash_context::CrashContext) {
// At a minimum, the crashdump writer needs to know the process and thread that the crash occurred in
@@ -39,23 +73,63 @@ fn write_minidump(crash_context: crash_context::CrashContext) {
### Windows
+#### Local process
+
+```rust
+fn write_minidump() {
+ let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");
+
+ // Attempts to the write the minidump
+ minidump_writer::minidump_writer::MinidumpWriter::dump_local_context(
+ // The exception code, presumably one of STATUS_*. Defaults to STATUS_NONCONTINUABLE_EXCEPTION if not specified
+ None,
+ // If not specified, uses the current thread as the "crashing" thread,
+ // so this is equivalent to passing `None`, but it could be any thread
+ // in the process
+ Some(unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() }),
+ &mut minidump_file,
+ ).expect("failed to write minidump");;
+}
+```
+
+#### External process
+
```rust
fn write_minidump(crash_context: crash_context::CrashContext) {
- // Creates the Windows MinidumpWriter. This function handles both the case
- // of the crashing process being the same, or different, than the current
- // process
- let writer = minidump_writer::minidump_writer::MinidumpWriter::new(crash_context)?;
+ use std::io::{Read, Seek};
+ // Create the file to write the minidump to. Unlike MacOS and Linux, the
+ // system call used to write the minidump only supports outputting to a file
let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");
- writer.dump(&mut minidump_file).expect("failed to write minidump");
+ // Attempts to the write the minidump for the crash context
+ minidump_writer::minidump_writer::MinidumpWriter::dump_crash_context(crash_context, &mut minidump_file).expect("failed to write minidump");;
+
+ let mut minidump_contents = Vec::with_capacity(minidump_file.stream_position().expect("failed to get stream length") as usize);
+ minidump_file.rewind().expect("failed to rewind minidump file");
+
+ minidump_file.read_to_end(&mut minidump_contents).expect("failed to read minidump");
}
```
### MacOS
+#### Local process
+
+```rust
+fn write_minidump() {
+ // Passing the defaults to dumping the current process and thread.
+ let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(None, None)?;
+
+ let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");
+ writer.dump(&mut minidump_file).expect("failed to write minidump");
+}
+```
+
+#### External process
+
```rust
fn write_minidump(crash_context: crash_context::CrashContext) {
- let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(crash_context)?;
+ let mut writer = minidump_writer::minidump_writer::MinidumpWriter::with_crash_context(crash_context)?;
let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");
writer.dump(&mut minidump_file).expect("failed to write minidump");
From 17c07b1621b19d5574395c3f981685e2406cb2a7 Mon Sep 17 00:00:00 2001
From: Jake Shadle
Date: Fri, 15 Jul 2022 17:17:44 +0200
Subject: [PATCH 5/5] Cleanup comment
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 2b45cfb6..dcdc4d6d 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ fn write_minidump(crash_context: crash_context::CrashContext) {
```rust
fn write_minidump() {
- // Passing the defaults to dumping the current process and thread.
+ // Defaults to dumping the current process and thread.
let mut writer = minidump_writer::minidump_writer::MinidumpWriter::new(None, None)?;
let mut minidump_file = std::fs::File::create("example_dump.mdmp").expect("failed to create file");