summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/src/windows
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/minidump-writer/src/windows.rs5
-rw-r--r--third_party/rust/minidump-writer/src/windows/errors.rs13
-rw-r--r--third_party/rust/minidump-writer/src/windows/ffi.rs449
-rw-r--r--third_party/rust/minidump-writer/src/windows/minidump_writer.rs309
4 files changed, 776 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/src/windows.rs b/third_party/rust/minidump-writer/src/windows.rs
new file mode 100644
index 0000000000..34b72444c1
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/windows.rs
@@ -0,0 +1,5 @@
+pub mod errors;
+mod ffi;
+pub mod minidump_writer;
+
+pub use ffi::MinidumpType;
diff --git a/third_party/rust/minidump-writer/src/windows/errors.rs b/third_party/rust/minidump-writer/src/windows/errors.rs
new file mode 100644
index 0000000000..a2ba6c9b66
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/windows/errors.rs
@@ -0,0 +1,13 @@
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error(transparent)]
+ 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/third_party/rust/minidump-writer/src/windows/ffi.rs b/third_party/rust/minidump-writer/src/windows/ffi.rs
new file mode 100644
index 0000000000..933228f8e6
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/windows/ffi.rs
@@ -0,0 +1,449 @@
+//! Contains bindings for [`MiniDumpWriteDump`](https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump)
+//! and related structures, as they are not present in `winapi` and we don't want
+//! to depend on `windows-sys` due to version churn.
+//!
+//! Also has a binding for [`GetThreadContext`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext)
+//! as the `CONTEXT` structures in `winapi` are not correctly aligned which can
+//! cause crashes or bad data, so the [`crash_context::ffi::CONTEXT`] is used
+//! instead. See [#63](https://github.com/rust-minidump/minidump-writer/issues/63)
+
+#![allow(
+ non_snake_case,
+ non_camel_case_types,
+ non_upper_case_globals,
+ clippy::upper_case_acronyms
+)]
+
+pub use crash_context::{capture_context, CONTEXT, EXCEPTION_POINTERS, EXCEPTION_RECORD};
+
+pub type HANDLE = isize;
+pub type BOOL = i32;
+pub const FALSE: BOOL = 0;
+
+pub type Hresult = i32;
+pub const STATUS_NONCONTINUABLE_EXCEPTION: i32 = -1073741787;
+
+pub type PROCESS_ACCESS_RIGHTS = u32;
+pub const PROCESS_ALL_ACCESS: PROCESS_ACCESS_RIGHTS = 2097151;
+
+pub type THREAD_ACCESS_RIGHTS = u32;
+pub const THREAD_SUSPEND_RESUME: THREAD_ACCESS_RIGHTS = 2;
+pub const THREAD_GET_CONTEXT: THREAD_ACCESS_RIGHTS = 8;
+pub const THREAD_QUERY_INFORMATION: THREAD_ACCESS_RIGHTS = 64;
+
+bitflags::bitflags! {
+ /// <https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type>
+ #[derive(Copy, Clone, Debug)]
+ #[repr(transparent)]
+ pub struct MinidumpType: u32 {
+ /// Include just the information necessary to capture stack traces for all
+ /// existing threads in a process.
+ const Normal = 0;
+ /// Include the data sections from all loaded modules.
+ ///
+ /// This results in the inclusion of global variables, which can make
+ /// the minidump file significantly larger.
+ const WithDataSegs = 1 << 0;
+ /// Include all accessible memory in the process.
+ ///
+ /// The raw memory data is included at the end, so that the initial
+ /// structures can be mapped directly without the raw memory information.
+ /// This option can result in a very large file.
+ const WithFullMemory = 1 << 1;
+ /// Include high-level information about the operating system handles that
+ /// are active when the minidump is made.
+ const WithHandleData = 1 << 2;
+ /// Stack and backing store memory written to the minidump file should be
+ /// filtered to remove all but the pointer values necessary to reconstruct a
+ /// stack trace.
+ const FilterMemory = 1 << 3;
+ /// Stack and backing store memory should be scanned for pointer references
+ /// to modules in the module list.
+ ///
+ /// If a module is referenced by stack or backing store memory, the
+ /// [`MINIDUMP_CALLBACK_OUTPUT_0::ModuleWriteFlags`] field is set to
+ /// [`ModuleWriteFlags::ModuleReferencedByMemory`].
+ const ScanMemory = 1 << 4;
+ /// Include information from the list of modules that were recently
+ /// unloaded, if this information is maintained by the operating system.
+ const WithUnloadedModules = 1 << 5;
+ /// Include pages with data referenced by locals or other stack memory.
+ /// This option can increase the size of the minidump file significantly.
+ const WithIndirectlyReferencedMemory = 1 << 6;
+ /// Filter module paths for information such as user names or important
+ /// directories.
+ ///
+ /// This option may prevent the system from locating the image file and
+ /// should be used only in special situations.
+ const FilterModulePaths = 1 << 7;
+ /// Include complete per-process and per-thread information from the
+ /// operating system.
+ const WithProcessThreadData = 1 << 8;
+ /// Scan the virtual address space for [`PAGE_READWRITE`](https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants)
+ /// memory to be included.
+ const WithPrivateReadWriteMemory = 1 << 9;
+ /// Reduce the data that is dumped by eliminating memory regions that
+ /// are not essential to meet criteria specified for the dump.
+ ///
+ /// This can avoid dumping memory that may contain data that is private
+ /// to the user. However, it is not a guarantee that no private information
+ /// will be present.
+ const WithoutOptionalData = 1 << 10;
+ /// Include memory region information.
+ ///
+ /// See [MINIDUMP_MEMORY_INFO_LIST](https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_info_list)
+ const WithFullMemoryInfo = 1 << 11;
+ /// Include thread state information.
+ ///
+ /// See [MINIDUMP_THREAD_INFO_LIST](https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_thread_info_list)
+ const WithThreadInfo = 1 << 12;
+ /// Include all code and code-related sections from loaded modules to
+ /// capture executable content.
+ ///
+ /// For per-module control, use the [`ModuleWriteFlags::ModuleWriteCodeSegs`]
+ const WithCodeSegs = 1 << 13;
+ /// Turns off secondary auxiliary-supported memory gathering.
+ const WithoutAuxiliaryState = 1 << 14;
+ /// Requests that auxiliary data providers include their state in the
+ /// dump image; the state data that is included is provider dependent.
+ ///
+ /// This option can result in a large dump image.
+ const WithFullAuxiliaryState = 1 << 15;
+ /// Scans the virtual address space for [`PAGE_WRITECOPY`](https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants) memory to be included.
+ const WithPrivateWriteCopyMemory = 1 << 16;
+ /// If you specify [`MinidumpType::MiniDumpWithFullMemory`], the
+ /// `MiniDumpWriteDump` function will fail if the function cannot read
+ /// the memory regions; however, if you include
+ /// [`IgnoreInaccessibleMemory`], the `MiniDumpWriteDump` function will
+ /// ignore the memory read failures and continue to generate the dump.
+ ///
+ /// Note that the inaccessible memory regions are not included in the dump.
+ const IgnoreInaccessibleMemory = 1 << 17;
+ /// Adds security token related data.
+ ///
+ /// This will make the "!token" extension work when processing a user-mode dump.
+ const WithTokenInformation = 1 << 18;
+ /// Adds module header related data.
+ const WithModuleHeaders = 1 << 19;
+ /// Adds filter triage related data.
+ const FilterTriage = 1 << 20;
+ /// Adds AVX crash state context registers.
+ const WithAvxXStateContext = 1 << 21;
+ /// Adds Intel Processor Trace related data.
+ const WithIptTrace = 1 << 22;
+ /// Scans inaccessible partial memory pages.
+ const ScanInaccessiblePartialPages = 1 << 23;
+ /// Exclude all memory with the virtual protection attribute of [`PAGE_WRITECOMBINE`](https://learn.microsoft.com/en-us/windows/win32/memory/memory-protection-constants).
+ const FilterWriteCombinedMemory = 1 << 24;
+ }
+}
+
+pub type VS_FIXEDFILEINFO_FILE_FLAGS = u32;
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_USER_STREAM {
+ pub Type: u32,
+ pub BufferSize: u32,
+ pub Buffer: *mut std::ffi::c_void,
+}
+#[repr(C, packed(4))]
+pub struct MINIDUMP_USER_STREAM_INFORMATION {
+ pub UserStreamCount: u32,
+ pub UserStreamArray: *mut MINIDUMP_USER_STREAM,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_EXCEPTION_INFORMATION {
+ pub ThreadId: u32,
+ pub ExceptionPointers: *mut EXCEPTION_POINTERS,
+ pub ClientPointers: BOOL,
+}
+
+pub type VS_FIXEDFILEINFO_FILE_OS = i32;
+pub type VS_FIXEDFILEINFO_FILE_TYPE = i32;
+pub type VS_FIXEDFILEINFO_FILE_SUBTYPE = i32;
+
+#[repr(C)]
+pub struct VS_FIXEDFILEINFO {
+ pub dwSignature: u32,
+ pub dwStrucVersion: u32,
+ pub dwFileVersionMS: u32,
+ pub dwFileVersionLS: u32,
+ pub dwProductVersionMS: u32,
+ pub dwProductVersionLS: u32,
+ pub dwFileFlagsMask: u32,
+ pub dwFileFlags: VS_FIXEDFILEINFO_FILE_FLAGS,
+ pub dwFileOS: VS_FIXEDFILEINFO_FILE_OS,
+ pub dwFileType: VS_FIXEDFILEINFO_FILE_TYPE,
+ pub dwFileSubtype: VS_FIXEDFILEINFO_FILE_SUBTYPE,
+ pub dwFileDateMS: u32,
+ pub dwFileDateLS: u32,
+}
+#[repr(C, packed(4))]
+pub struct MINIDUMP_MODULE_CALLBACK {
+ pub FullPath: *mut u16,
+ pub BaseOfImage: u64,
+ pub SizeOfImage: u32,
+ pub CheckSum: u32,
+ pub TimeDateStamp: u32,
+ pub VersionInfo: VS_FIXEDFILEINFO,
+ pub CvRecord: *mut std::ffi::c_void,
+ pub SizeOfCvRecord: u32,
+ pub MiscRecord: *mut std::ffi::c_void,
+ pub SizeOfMiscRecord: u32,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_INCLUDE_THREAD_CALLBACK {
+ pub ThreadId: u32,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_INCLUDE_MODULE_CALLBACK {
+ pub BaseOfImage: u64,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_IO_CALLBACK {
+ pub Handle: HANDLE,
+ pub Offset: u64,
+ pub Buffer: *mut std::ffi::c_void,
+ pub BufferBytes: u32,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_READ_MEMORY_FAILURE_CALLBACK {
+ pub Offset: u64,
+ pub Bytes: u32,
+ pub FailureStatus: Hresult,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_VM_QUERY_CALLBACK {
+ pub Offset: u64,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_VM_PRE_READ_CALLBACK {
+ pub Offset: u64,
+ pub Buffer: *mut std::ffi::c_void,
+ pub Size: u32,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_VM_POST_READ_CALLBACK {
+ pub Offset: u64,
+ pub Buffer: *mut std::ffi::c_void,
+ pub Size: u32,
+ pub Completed: u32,
+ pub Status: Hresult,
+}
+
+/// Oof, so we have a problem with these structs, they are all packed(4), but
+/// `CONTEXT` is aligned by either 4 (x86) or 16 (x86_64/aarch64)...which Rust
+/// doesn't currently allow https://github.com/rust-lang/rust/issues/59154, so
+/// we need to basically cheat with a big byte array until that issue is fixed (possibly never)
+#[repr(C)]
+pub struct CALLBACK_CONTEXT([u8; std::mem::size_of::<CONTEXT>()]);
+
+cfg_if::cfg_if! {
+ if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
+ #[repr(C, packed(4))]
+ pub struct MINIDUMP_THREAD_CALLBACK {
+ pub ThreadId: u32,
+ pub ThreadHandle: HANDLE,
+ pub Context: CALLBACK_CONTEXT,
+ pub SizeOfContext: u32,
+ pub StackBase: u64,
+ pub StackEnd: u64,
+ }
+
+ #[repr(C, packed(4))]
+ pub struct MINIDUMP_THREAD_EX_CALLBACK {
+ pub ThreadId: u32,
+ pub ThreadHandle: HANDLE,
+ pub Context: CALLBACK_CONTEXT,
+ pub SizeOfContext: u32,
+ pub StackBase: u64,
+ pub StackEnd: u64,
+ pub BackingStoreBase: u64,
+ pub BackingStoreEnd: u64,
+ }
+ } else if #[cfg(target_arch = "aarch64")] {
+ #[repr(C, packed(4))]
+ pub struct MINIDUMP_THREAD_CALLBACK {
+ pub ThreadId: u32,
+ pub ThreadHandle: HANDLE,
+ pub Pad: u32,
+ pub Context: CALLBACK_CONTEXT,
+ pub SizeOfContext: u32,
+ pub StackBase: u64,
+ pub StackEnd: u64,
+ }
+
+ #[repr(C, packed(4))]
+ pub struct MINIDUMP_THREAD_EX_CALLBACK {
+ pub ThreadId: u32,
+ pub ThreadHandle: HANDLE,
+ pub Pad: u32,
+ pub Context: CALLBACK_CONTEXT,
+ pub SizeOfContext: u32,
+ pub StackBase: u64,
+ pub StackEnd: u64,
+ pub BackingStoreBase: u64,
+ pub BackingStoreEnd: u64,
+ }
+ }
+}
+
+#[repr(C)]
+pub union MINIDUMP_CALLBACK_INPUT_0 {
+ pub Status: Hresult,
+ pub Thread: std::mem::ManuallyDrop<MINIDUMP_THREAD_CALLBACK>,
+ pub ThreadEx: std::mem::ManuallyDrop<MINIDUMP_THREAD_EX_CALLBACK>,
+ pub Module: std::mem::ManuallyDrop<MINIDUMP_MODULE_CALLBACK>,
+ pub IncludeThread: std::mem::ManuallyDrop<MINIDUMP_INCLUDE_THREAD_CALLBACK>,
+ pub IncludeModule: std::mem::ManuallyDrop<MINIDUMP_INCLUDE_MODULE_CALLBACK>,
+ pub Io: std::mem::ManuallyDrop<MINIDUMP_IO_CALLBACK>,
+ pub ReadMemoryFailure: std::mem::ManuallyDrop<MINIDUMP_READ_MEMORY_FAILURE_CALLBACK>,
+ pub SecondaryFlags: u32,
+ pub VmQuery: std::mem::ManuallyDrop<MINIDUMP_VM_QUERY_CALLBACK>,
+ pub VmPreRead: std::mem::ManuallyDrop<MINIDUMP_VM_PRE_READ_CALLBACK>,
+ pub VmPostRead: std::mem::ManuallyDrop<MINIDUMP_VM_POST_READ_CALLBACK>,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_CALLBACK_INPUT {
+ pub ProcessId: u32,
+ pub ProcessHandle: HANDLE,
+ pub CallbackType: u32,
+ pub Anonymous: MINIDUMP_CALLBACK_INPUT_0,
+}
+
+pub type VIRTUAL_ALLOCATION_TYPE = u32;
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_MEMORY_INFO {
+ pub BaseAddress: u64,
+ pub AllocationBase: u64,
+ pub AllocationProtect: u32,
+ __alignment1: u32,
+ pub RegionSize: u64,
+ pub State: VIRTUAL_ALLOCATION_TYPE,
+ pub Protect: u32,
+ pub Type: u32,
+ __alignment2: u32,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_CALLBACK_OUTPUT_0_0 {
+ pub MemoryBase: u64,
+ pub MemorySize: u32,
+}
+
+#[repr(C)]
+pub struct MINIDUMP_CALLBACK_OUTPUT_0_1 {
+ pub CheckCancel: BOOL,
+ pub Cancel: BOOL,
+}
+
+#[repr(C)]
+pub struct MINIDUMP_CALLBACK_OUTPUT_0_2 {
+ pub VmRegion: MINIDUMP_MEMORY_INFO,
+ pub Continue: BOOL,
+}
+
+#[repr(C)]
+pub struct MINIDUMP_CALLBACK_OUTPUT_0_3 {
+ pub VmQueryStatus: Hresult,
+ pub VmQueryResult: MINIDUMP_MEMORY_INFO,
+}
+
+#[repr(C)]
+pub struct MINIDUMP_CALLBACK_OUTPUT_0_4 {
+ pub VmReadStatus: Hresult,
+ pub VmReadBytesCompleted: u32,
+}
+
+bitflags::bitflags! {
+ /// Identifies the type of module information that will be written to the
+ /// minidump file by the MiniDumpWriteDump function.
+ #[derive(Copy, Clone)]
+ #[repr(transparent)]
+ pub struct ModuleWriteFlags: u32 {
+ /// Only module information will be written to the minidump file.
+ const ModuleWriteModule = 0x0001;
+ const ModuleWriteDataSeg = 0x0002;
+ const ModuleWriteMiscRecord = 0x0004;
+ const ModuleWriteCvRecord = 0x0008;
+ const ModuleReferencedByMemory = 0x0010;
+ const ModuleWriteTlsData = 0x0020;
+ const ModuleWriteCodeSegs = 0x0040;
+ }
+}
+
+#[repr(C)]
+pub union MINIDUMP_CALLBACK_OUTPUT_0 {
+ pub ModuleWriteFlags: ModuleWriteFlags,
+ pub ThreadWriteFlags: u32,
+ pub SecondaryFlags: u32,
+ pub Anonymous1: std::mem::ManuallyDrop<MINIDUMP_CALLBACK_OUTPUT_0_0>,
+ pub Anonymous2: std::mem::ManuallyDrop<MINIDUMP_CALLBACK_OUTPUT_0_1>,
+ pub Handle: HANDLE,
+ pub Anonymous3: std::mem::ManuallyDrop<MINIDUMP_CALLBACK_OUTPUT_0_2>,
+ pub Anonymous4: std::mem::ManuallyDrop<MINIDUMP_CALLBACK_OUTPUT_0_3>,
+ pub Anonymous5: std::mem::ManuallyDrop<MINIDUMP_CALLBACK_OUTPUT_0_4>,
+ pub Status: Hresult,
+}
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_CALLBACK_OUTPUT {
+ pub Anonymous: MINIDUMP_CALLBACK_OUTPUT_0,
+}
+
+pub type MINIDUMP_CALLBACK_ROUTINE = Option<
+ unsafe extern "system" fn(
+ CallbackParam: *mut std::ffi::c_void,
+ CallbackInput: *const MINIDUMP_CALLBACK_INPUT,
+ CallbackOutput: *mut MINIDUMP_CALLBACK_OUTPUT,
+ ) -> BOOL,
+>;
+
+#[repr(C, packed(4))]
+pub struct MINIDUMP_CALLBACK_INFORMATION {
+ pub CallbackRoutine: MINIDUMP_CALLBACK_ROUTINE,
+ pub CallbackParam: *mut std::ffi::c_void,
+}
+
+#[link(name = "kernel32")]
+extern "system" {
+ pub fn CloseHandle(handle: HANDLE) -> BOOL;
+ pub fn GetCurrentProcess() -> HANDLE;
+ pub fn GetCurrentThreadId() -> u32;
+ pub fn OpenProcess(
+ desired_access: PROCESS_ACCESS_RIGHTS,
+ inherit_handle: BOOL,
+ process_id: u32,
+ ) -> HANDLE;
+ pub fn OpenThread(
+ desired_access: THREAD_ACCESS_RIGHTS,
+ inherit_handle: BOOL,
+ thread_id: u32,
+ ) -> HANDLE;
+ pub fn ResumeThread(thread: HANDLE) -> u32;
+ pub fn SuspendThread(thread: HANDLE) -> u32;
+ pub fn GetThreadContext(thread: HANDLE, context: *mut CONTEXT) -> BOOL;
+}
+
+#[link(name = "dbghelp")]
+extern "system" {
+ pub fn MiniDumpWriteDump(
+ process: HANDLE,
+ process_id: u32,
+ file: HANDLE,
+ dump_type: MinidumpType,
+ exception_param: *const MINIDUMP_EXCEPTION_INFORMATION,
+ user_stream_param: *const MINIDUMP_USER_STREAM_INFORMATION,
+ callback_param: *const MINIDUMP_CALLBACK_INFORMATION,
+ ) -> BOOL;
+}
diff --git a/third_party/rust/minidump-writer/src/windows/minidump_writer.rs b/third_party/rust/minidump-writer/src/windows/minidump_writer.rs
new file mode 100644
index 0000000000..70cc420e57
--- /dev/null
+++ b/third_party/rust/minidump-writer/src/windows/minidump_writer.rs
@@ -0,0 +1,309 @@
+#![allow(unsafe_code)]
+
+use crate::windows::errors::Error;
+use crate::windows::ffi::{
+ capture_context, CloseHandle, GetCurrentProcess, GetCurrentThreadId, GetThreadContext,
+ MiniDumpWriteDump, MinidumpType, OpenProcess, OpenThread, ResumeThread, SuspendThread,
+ EXCEPTION_POINTERS, EXCEPTION_RECORD, FALSE, HANDLE, MINIDUMP_EXCEPTION_INFORMATION,
+ MINIDUMP_USER_STREAM, MINIDUMP_USER_STREAM_INFORMATION, PROCESS_ALL_ACCESS,
+ STATUS_NONCONTINUABLE_EXCEPTION, THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION,
+ THREAD_SUSPEND_RESUME,
+};
+use minidump_common::format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO, MINIDUMP_STREAM_TYPE};
+use scroll::Pwrite;
+use std::os::windows::io::AsRawHandle;
+
+pub struct MinidumpWriter {
+ /// Optional exception information
+ exc_info: Option<MINIDUMP_EXCEPTION_INFORMATION>,
+ /// Handle to the crashing process, which could be ourselves
+ 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
+ #[allow(dead_code)]
+ 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 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
+ /// to dump from an external process if possible via [`Self::dump_crash_context`]
+ ///
+ /// # Errors
+ ///
+ /// 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<i32>,
+ thread_id: Option<u32>,
+ minidump_type: Option<MinidumpType>,
+ destination: &mut std::fs::File,
+ ) -> Result<(), Error> {
+ let exception_code = exception_code.unwrap_or(STATUS_NONCONTINUABLE_EXCEPTION);
+
+ // 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();
+
+ // 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 == GetCurrentThreadId() {
+ capture_context(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 = OpenThread(
+ THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, // desired access rights, we only need to get the context, which also requires suspension
+ FALSE, // 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 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 GetThreadContext(thread_handle.0, ec.as_mut_ptr()) == 0 {
+ // Try to be a good citizen and resume the thread
+ 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
+ ResumeThread(thread_handle.0);
+ }
+
+ ec.assume_init()
+ } else {
+ let mut ec = std::mem::MaybeUninit::uninit();
+ capture_context(ec.as_mut_ptr());
+ ec.assume_init()
+ };
+
+ let mut exception_record: EXCEPTION_RECORD = std::mem::zeroed();
+
+ let exception_ptrs = 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 EXCEPTION_POINTERS).cast(),
+ process_id: std::process::id(),
+ thread_id: thread_id.unwrap_or_else(|| GetCurrentThreadId()),
+ exception_code,
+ };
+
+ Self::dump_crash_context(cc, minidump_type, 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, 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 fn dump_crash_context(
+ crash_context: crash_context::CrashContext,
+ minidump_type: Option<MinidumpType>,
+ destination: &mut std::fs::File,
+ ) -> Result<(), Error> {
+ let pid = crash_context.process_id;
+
+ // SAFETY: syscalls
+ let (crashing_process, is_external_process) = unsafe {
+ if pid != std::process::id() {
+ let proc = OpenProcess(
+ PROCESS_ALL_ACCESS, // desired access
+ FALSE, // inherit handles
+ pid, // pid
+ );
+
+ if proc == 0 {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ (proc, true)
+ } else {
+ (GetCurrentProcess(), false)
+ }
+ };
+
+ 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_some(
+ // https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_exception_information
+ 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: 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: i32::from(is_external_process),
+ },
+ );
+
+ let mdw = Self {
+ exc_info,
+ crashing_process,
+ pid,
+ tid,
+ exception_code,
+ is_external_process,
+ };
+
+ mdw.dump(minidump_type, destination)
+ }
+
+ /// Writes a minidump to the specified file
+ fn dump(
+ mut self,
+ minidump_type: Option<MinidumpType>,
+ destination: &mut std::fs::File,
+ ) -> Result<(), Error> {
+ let exc_info = self.exc_info.take();
+
+ let mut user_streams = Vec::with_capacity(1);
+
+ let mut breakpad_info = self.fill_breakpad_stream();
+
+ if let Some(bp_info) = &mut breakpad_info {
+ user_streams.push(MINIDUMP_USER_STREAM {
+ Type: MINIDUMP_STREAM_TYPE::BreakpadInfoStream as u32,
+ BufferSize: bp_info.len() as u32,
+ // Again with the mut pointer
+ Buffer: bp_info.as_mut_ptr().cast(),
+ });
+ }
+
+ let user_stream_infos = MINIDUMP_USER_STREAM_INFORMATION {
+ UserStreamCount: user_streams.len() as u32,
+ UserStreamArray: user_streams.as_mut_ptr(),
+ };
+
+ // Write the actual minidump
+ // https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump
+ // SAFETY: syscall
+ let ret = unsafe {
+ MiniDumpWriteDump(
+ self.crashing_process, // HANDLE to the process with the crash we want to capture
+ self.pid, // process id
+ destination.as_raw_handle() as HANDLE, // file to write the minidump to
+ minidump_type.unwrap_or(MinidumpType::Normal),
+ exc_info
+ .as_ref()
+ .map_or(std::ptr::null(), |ei| ei as *const _), // exceptionparam - the actual exception information
+ &user_stream_infos, // user streams
+ std::ptr::null(), // callback, unused
+ )
+ };
+
+ if ret == 0 {
+ Err(std::io::Error::last_os_error().into())
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Create an MDRawBreakpadInfo stream to the minidump, to provide additional
+ /// information about the exception handler to the Breakpad processor.
+ /// The information will help the processor determine which threads are
+ /// relevant. The Breakpad processor does not require this information but
+ /// can function better with Breakpad-generated dumps when it is present.
+ /// The native debugger is not harmed by the presence of this information.
+ ///
+ /// This info is only relevant for in-process dumping
+ fn fill_breakpad_stream(&self) -> Option<[u8; 12]> {
+ if self.is_external_process {
+ return None;
+ }
+
+ let mut breakpad_info = [0u8; 12];
+
+ let bp_info = MINIDUMP_BREAKPAD_INFO {
+ validity: BreakpadInfoValid::DumpThreadId.bits()
+ | BreakpadInfoValid::RequestingThreadId.bits(),
+ dump_thread_id: self.tid,
+ // SAFETY: syscall
+ requesting_thread_id: unsafe { GetCurrentThreadId() },
+ };
+
+ // TODO: derive Pwrite for MINIDUMP_BREAKPAD_INFO
+ // https://github.com/rust-minidump/rust-minidump/pull/534
+ let mut offset = 0;
+ breakpad_info.gwrite(bp_info.validity, &mut offset).ok()?;
+ breakpad_info
+ .gwrite(bp_info.dump_thread_id, &mut offset)
+ .ok()?;
+ breakpad_info
+ .gwrite(bp_info.requesting_thread_id, &mut offset)
+ .ok()?;
+
+ Some(breakpad_info)
+ }
+}
+
+impl Drop for MinidumpWriter {
+ fn drop(&mut self) {
+ // Note we close the handle regardless of whether it is the local handle
+ // or an external one, as noted in the docs
+ //
+ // > The pseudo handle need not be closed when it is no longer needed.
+ // > Calling the CloseHandle function with a pseudo handle has no effect.
+ // SAFETY: syscall
+ unsafe { CloseHandle(self.crashing_process) };
+ }
+}