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");