Skip to content

Commit

Permalink
Use the winapi crate instead of the windows-sys one for the Windows b…
Browse files Browse the repository at this point in the history
…indings
  • Loading branch information
gabrielesvelto committed Oct 5, 2022
1 parent 8375951 commit 951d2d8
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 62 deletions.
15 changes: 3 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,9 @@ nix = { version = "0.24", default-features = false, features = [
"user",
] }

[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.36"
features = [
# MiniDumpWriteDump requires...a lot of features
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_Debug",
"Win32_System_Kernel",
"Win32_System_Memory",
# GetCurrentThreadId & OpenProcess
"Win32_System_Threading",
]
[target.'cfg(target_os = "windows")'.dependencies.winapi]
version = "0.3"
features = ["dbghelp", "handleapi", "ntstatus", "processthreadsapi", "winnt"]

[target.'cfg(target_os = "macos")'.dependencies]
# Binds some additional mac specifics not in libc
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

This project is currently being very actively brought up from nothing, and is really ultimately many separate client implementations for different platforms.

This is a friendly fork with small changes required for vendoring the crate in Firefox. It will go away as soon as we will be able to vendor the upstream crate.

## 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.
Expand Down
10 changes: 6 additions & 4 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,16 @@ mod linux {
mod windows {
use super::*;
use std::mem;
use windows_sys::Win32::System::{
Diagnostics::Debug::{GetThreadContext, CONTEXT, EXCEPTION_POINTERS, EXCEPTION_RECORD},
Threading::{GetCurrentProcessId, GetCurrentThread, GetCurrentThreadId},
use winapi::um::{
processthreadsapi::{
GetCurrentProcessId, GetCurrentThread, GetCurrentThreadId, GetThreadContext,
},
winnt::{CONTEXT, EXCEPTION_POINTERS, EXCEPTION_RECORD},
};

#[inline(never)]
pub(super) fn real_main(args: Vec<String>) -> Result<()> {
let exception_code = u32::from_str_radix(&args[0], 16).unwrap() as i32;
let exception_code = u32::from_str_radix(&args[0], 16).unwrap();

// Generate the exception and communicate back where the exception pointers
// are
Expand Down
216 changes: 176 additions & 40 deletions src/windows/minidump_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@ use crate::windows::errors::Error;
use minidump_common::format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO, MINIDUMP_STREAM_TYPE};
use scroll::Pwrite;
use std::os::windows::io::AsRawHandle;
pub use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::{
Foundation::{CloseHandle, STATUS_NONCONTINUABLE_EXCEPTION},
System::{Diagnostics::Debug as md, Threading as threading},
use winapi::{
shared::{
basetsd::ULONG32,
minwindef::{BOOL, DWORD, FALSE, TRUE, ULONG},
ntstatus::STATUS_NONCONTINUABLE_EXCEPTION,
},
um::{
handleapi::CloseHandle,
processthreadsapi::{
GetCurrentProcess, GetCurrentThreadId, GetThreadContext, OpenProcess, OpenThread,
ResumeThread, SuspendThread,
},
winnt::{
RtlCaptureContext, EXCEPTION_POINTERS, EXCEPTION_RECORD, HANDLE, PEXCEPTION_POINTERS,
PROCESS_ALL_ACCESS, PVOID, THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION,
THREAD_SUSPEND_RESUME,
},
},
STRUCT,
};

pub struct MinidumpWriter {
/// Optional exception information
exc_info: Option<md::MINIDUMP_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
Expand Down Expand Up @@ -53,22 +68,20 @@ impl MinidumpWriter {

// 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());
if tid == GetCurrentThreadId() {
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
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 {
if thread_handle.is_null() {
return Err(Error::ThreadOpen(std::io::Error::last_os_error()));
}

Expand All @@ -85,44 +98,44 @@ impl MinidumpWriter {

// 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 {
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 md::GetThreadContext(thread_handle.0, ec.as_mut_ptr()) == 0 {
if GetThreadContext(thread_handle.0, ec.as_mut_ptr()) == 0 {
// Try to be a good citizen and resume the thread
threading::ResumeThread(thread_handle.0);
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);
ResumeThread(thread_handle.0);
}

ec.assume_init()
} else {
let mut ec = std::mem::MaybeUninit::uninit();
md::RtlCaptureContext(ec.as_mut_ptr());
RtlCaptureContext(ec.as_mut_ptr());
ec.assume_init()
};

let mut exception_record: md::EXCEPTION_RECORD = std::mem::zeroed();
let mut exception_record: EXCEPTION_RECORD = std::mem::zeroed();

let exception_ptrs = md::EXCEPTION_POINTERS {
let exception_ptrs = EXCEPTION_POINTERS {
ExceptionRecord: &mut exception_record,
ContextRecord: &mut exception_context,
};

exception_record.ExceptionCode = exception_code;
exception_record.ExceptionCode = exception_code as _;

let cc = crash_context::CrashContext {
exception_pointers: (&exception_ptrs as *const md::EXCEPTION_POINTERS).cast(),
exception_pointers: (&exception_ptrs as *const EXCEPTION_POINTERS).cast(),
process_id: std::process::id(),
thread_id: thread_id.unwrap_or_else(|| threading::GetCurrentThreadId()),
thread_id: thread_id.unwrap_or_else(|| GetCurrentThreadId()),
exception_code,
};

Expand Down Expand Up @@ -153,19 +166,19 @@ impl MinidumpWriter {
// SAFETY: syscalls
let (crashing_process, is_external_process) = unsafe {
if pid != std::process::id() {
let proc = threading::OpenProcess(
threading::PROCESS_ALL_ACCESS, // desired access
0, // inherit handles
pid, // pid
let proc = OpenProcess(
PROCESS_ALL_ACCESS, // desired access
FALSE, // inherit handles
pid, // pid
);

if proc == 0 {
if proc.is_null() {
return Err(std::io::Error::last_os_error().into());
}

(proc, true)
} else {
(threading::GetCurrentProcess(), false)
(GetCurrentProcess(), false)
}
};

Expand All @@ -175,7 +188,7 @@ impl MinidumpWriter {

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
md::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...?
Expand All @@ -187,7 +200,7 @@ impl MinidumpWriter {
/// `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 },
ClientPointers: if is_external_process { TRUE } else { FALSE },
},
);

Expand All @@ -205,22 +218,22 @@ impl MinidumpWriter {

/// 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 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(md::MINIDUMP_USER_STREAM {
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 = md::MINIDUMP_USER_STREAM_INFORMATION {
let user_stream_infos = MINIDUMP_USER_STREAM_INFORMATION {
UserStreamCount: user_streams.len() as u32,
UserStreamArray: user_streams.as_mut_ptr(),
};
Expand All @@ -229,14 +242,14 @@ impl MinidumpWriter {
// https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/nf-minidumpapiset-minidumpwritedump
// SAFETY: syscall
let ret = unsafe {
md::MiniDumpWriteDump(
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
md::MiniDumpNormal, // MINIDUMP_TYPE - we _might_ want to make this configurable
MiniDumpNormal, // MINIDUMP_TYPE - we _might_ want to make this configurable
exc_info
.as_ref()
.map_or(std::ptr::null(), |ei| ei as *const _), // exceptionparam - the actual exception information
.as_mut()
.map_or(std::ptr::null_mut(), |ei| ei as *mut _), // exceptionparam - the actual exception information
&user_stream_infos, // user streams
std::ptr::null(), // callback, unused
)
Expand Down Expand Up @@ -269,7 +282,7 @@ impl MinidumpWriter {
| BreakpadInfoValid::RequestingThreadId.bits(),
dump_thread_id: self.tid,
// Safety: syscall
requesting_thread_id: unsafe { threading::GetCurrentThreadId() },
requesting_thread_id: unsafe { GetCurrentThreadId() },
};

// TODO: derive Pwrite for MINIDUMP_BREAKPAD_INFO
Expand Down Expand Up @@ -298,3 +311,126 @@ impl Drop for MinidumpWriter {
unsafe { CloseHandle(self.crashing_process) };
}
}

/******************************************************************************
* The stuff below is missing from the winapi crate *
******************************************************************************/

// we can't use winapi's ENUM macro directly because it doesn't support
// attributes, so let's define this one here until we migrate this code
macro_rules! ENUM {
{enum $name:ident { $($variant:ident = $value:expr,)+ }} => {
#[allow(non_camel_case_types)] pub type $name = u32;
$(#[allow(non_upper_case_globals)] pub const $variant: $name = $value;)+
};
}

// winapi doesn't export the FN macro, so we duplicate it here
macro_rules! FN {
(stdcall $func:ident($($t:ty,)*) -> $ret:ty) => (
#[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($t,)*) -> $ret>;
);
(stdcall $func:ident($($p:ident: $t:ty,)*) -> $ret:ty) => (
#[allow(non_camel_case_types)] pub type $func = Option<unsafe extern "system" fn($($p: $t,)*) -> $ret>;
);
}

// From minidumpapiset.h

STRUCT! {#[allow(non_snake_case)] #[repr(C, packed(4))] struct MINIDUMP_EXCEPTION_INFORMATION {
ThreadId: DWORD,
ExceptionPointers: PEXCEPTION_POINTERS,
ClientPointers: BOOL,
}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_EXCEPTION_INFORMATION = *mut MINIDUMP_EXCEPTION_INFORMATION;

ENUM! { enum MINIDUMP_TYPE {
MiniDumpNormal = 0x00000000,
MiniDumpWithDataSegs = 0x00000001,
MiniDumpWithFullMemory = 0x00000002,
MiniDumpWithHandleData = 0x00000004,
MiniDumpFilterMemory = 0x00000008,
MiniDumpScanMemory = 0x00000010,
MiniDumpWithUnloadedModules = 0x00000020,
MiniDumpWithIndirectlyReferencedMemory = 0x00000040,
MiniDumpFilterModulePaths = 0x00000080,
MiniDumpWithProcessThreadData = 0x00000100,
MiniDumpWithPrivateReadWriteMemory = 0x00000200,
MiniDumpWithoutOptionalData = 0x00000400,
MiniDumpWithFullMemoryInfo = 0x00000800,
MiniDumpWithThreadInfo = 0x00001000,
MiniDumpWithCodeSegs = 0x00002000,
MiniDumpWithoutAuxiliaryState = 0x00004000,
MiniDumpWithFullAuxiliaryState = 0x00008000,
MiniDumpWithPrivateWriteCopyMemory = 0x00010000,
MiniDumpIgnoreInaccessibleMemory = 0x00020000,
MiniDumpWithTokenInformation = 0x00040000,
MiniDumpWithModuleHeaders = 0x00080000,
MiniDumpFilterTriage = 0x00100000,
MiniDumpWithAvxXStateContext = 0x00200000,
MiniDumpWithIptTrace = 0x00400000,
MiniDumpScanInaccessiblePartialPages = 0x00800000,
MiniDumpValidTypeFlags = 0x00ffffff,
}}

// We don't actually need the following three structs so we use placeholders
STRUCT! {#[allow(non_snake_case)] struct MINIDUMP_CALLBACK_INPUT {
dummy: u32,
}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_CALLBACK_INPUT = *const MINIDUMP_CALLBACK_INPUT;

STRUCT! {#[allow(non_snake_case)] #[repr(C, packed(4))] struct MINIDUMP_USER_STREAM {
Type: ULONG32,
BufferSize: ULONG,
Buffer: PVOID,

}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_USER_STREAM = *const MINIDUMP_USER_STREAM;

STRUCT! {#[allow(non_snake_case)] #[repr(C, packed(4))] struct MINIDUMP_USER_STREAM_INFORMATION {
UserStreamCount: ULONG,
UserStreamArray: PMINIDUMP_USER_STREAM,
}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_USER_STREAM_INFORMATION = *const MINIDUMP_USER_STREAM_INFORMATION;

STRUCT! {#[allow(non_snake_case)] #[repr(C, packed(4))] struct MINIDUMP_CALLBACK_OUTPUT {
dummy: u32,
}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_CALLBACK_OUTPUT = *const MINIDUMP_CALLBACK_OUTPUT;

// MiniDumpWriteDump() function and structs
FN! {stdcall MINIDUMP_CALLBACK_ROUTINE(
CallbackParam: PVOID,
CallbackInput: PMINIDUMP_CALLBACK_INPUT,
CallbackOutput: PMINIDUMP_CALLBACK_OUTPUT,
) -> BOOL}

STRUCT! {#[allow(non_snake_case)] #[repr(C, packed(4))] struct MINIDUMP_CALLBACK_INFORMATION {
CallbackRoutine: MINIDUMP_CALLBACK_ROUTINE,
CallbackParam: PVOID,
}}

#[allow(non_camel_case_types)]
pub type PMINIDUMP_CALLBACK_INFORMATION = *const MINIDUMP_CALLBACK_INFORMATION;

extern "system" {
pub fn MiniDumpWriteDump(
hProcess: HANDLE,
ProcessId: DWORD,
hFile: HANDLE,
DumpType: MINIDUMP_TYPE,
ExceptionParam: PMINIDUMP_EXCEPTION_INFORMATION,
UserStreamParam: PMINIDUMP_USER_STREAM_INFORMATION,
CallbackParam: PMINIDUMP_CALLBACK_INFORMATION,
) -> BOOL;
}
Loading

0 comments on commit 951d2d8

Please sign in to comment.