Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

- Auto-populate `event.user.id` with a persistent per-installation UUID when no explicit user ID is set. ([#1661](https://github.com/getsentry/sentry-native/pull/1661))

**Fixes**:

- Native/Windows: capture fast-fail and stack buffer overrun crashes via WER. ([#1710](https://github.com/getsentry/sentry-native/pull/1710))

## 0.14.0

**Breaking / Important behavior changes**:
Expand Down
34 changes: 34 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,25 @@ elseif(SENTRY_BACKEND_NATIVE)
# Native backend sources and configuration are in src/CMakeLists.txt
# The native backend requires C11 for atomics (set in src/CMakeLists.txt)

if(WIN32)
add_library(sentry-wer SHARED
src/backends/native/sentry_wer.c
src/backends/native/sentry_wer.def
)
target_include_directories(sentry-wer PRIVATE
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/backends/native
)
target_link_libraries(sentry-wer PRIVATE wer)
set_property(TARGET sentry-wer PROPERTY PREFIX "") # ensure MINGW doesn't prefix "lib" to dll name
set_property(TARGET sentry-wer PROPERTY DEBUG_POSTFIX "") # prevent CMAKE_DEBUG_POSTFIX from being applied
if(SENTRY_BUILD_RUNTIMESTATIC AND MSVC)
set_property(TARGET sentry-wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
sentry_add_version_resource(sentry-wer "Native WER Module")
endif()

# Build sentry-crash executable for native backend
# Get all sources that were added to sentry target
get_target_property(SENTRY_SOURCES sentry SOURCES)
Expand Down Expand Up @@ -837,11 +856,25 @@ elseif(SENTRY_BACKEND_NATIVE)

# Make sentry library depend on crash daemon so it's always built together
add_dependencies(sentry sentry-crash)
if(WIN32)
add_dependencies(sentry sentry-wer)
endif()

# Install daemon
install(TARGETS sentry-crash
RUNTIME DESTINATION bin
)
if(WIN32)
install(TARGETS sentry-wer
RUNTIME DESTINATION bin
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
if(MSVC)
sentry_install(FILES $<TARGET_PDB_FILE:sentry-wer>
DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL)
endif()
endif()

if(DEFINED SENTRY_FOLDER)
# Native backend doesn't have separate targets to organize
Expand Down Expand Up @@ -928,6 +961,7 @@ if(SENTRY_BUILD_EXAMPLES)

# to test handling SEH by-passing exceptions we need to enable the control flow guard
target_compile_options(sentry_example PRIVATE $<BUILD_INTERFACE:/guard:cf>)
target_link_options(sentry_example PRIVATE $<BUILD_INTERFACE:/GUARD:CF>)
else()
# Disable all optimizations for the `sentry_example` in gcc/clang. This allows us to keep crash triggers simple.
# The effects besides reproducible code-gen across compiler versions, will be negligible for build- and runtime.
Expand Down
8 changes: 4 additions & 4 deletions scripts/run_tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ param (
[string]$Keyword = "",
## Defines the number of parallel runners via pytest-xdist. This is highly experimental since tests remove database paths. (default: 1)
[int]$Parallelism = 1,
## Disables tests that require the crashpad WER module. (default: false)
[switch]$WithoutCrashpadWer = $false,
## Disables tests that require WER modules. (default: false)
[switch]$WithoutWer = $false,
## Disables stdout/stderr capture through pytest (default: false)
[switch]$DisableCapture = $false,
## Defines the maximum number of failing tests before the test session is stopped. 0 means infinite. Will not do what you expect, together with Parallelism > 1 (default: 0)
Expand Down Expand Up @@ -44,9 +44,9 @@ if ($Parallelism -gt 1)
$pytestCommand += " -n $Parallelism"
}

if (-not $WithoutCrashpadWer -and -not $Unit)
if (-not $WithoutWer -and -not $Unit)
{
$pytestCommand += " --with_crashpad_wer"
$pytestCommand += " --with_wer"
}

if ($Keyword)
Expand Down
24 changes: 19 additions & 5 deletions src/backends/native/minidump/sentry_minidump_windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,27 @@ sentry__write_minidump(

// Prepare exception information using original pointers from crashed
// process
EXCEPTION_POINTERS local_exception_pointers = { 0 };
EXCEPTION_RECORD local_exception_record = { 0 };
MINIDUMP_EXCEPTION_INFORMATION exception_info = { 0 };
exception_info.ThreadId = ctx->crashed_tid;
// Use original exception pointers from crashed process's address space
exception_info.ExceptionPointers = ctx->platform.exception_pointers;
// ClientPointers=TRUE tells Windows these pointers are in the target
// process
exception_info.ClientPointers = TRUE;
if (ctx->platform.exception_pointers) {
// Use original exception pointers from crashed process's address space
exception_info.ExceptionPointers = ctx->platform.exception_pointers;
// ClientPointers=TRUE tells Windows these pointers are in the target
// process
exception_info.ClientPointers = TRUE;
} else {
// WER copies exception data into shared memory, so ClientPointers must
// be false when using the copied record/context.
local_exception_record = ctx->platform.exception_record;
local_exception_record.ExceptionRecord = NULL;
local_exception_pointers.ExceptionRecord = &local_exception_record;
local_exception_pointers.ContextRecord
= (PCONTEXT)&ctx->platform.context;
exception_info.ExceptionPointers = &local_exception_pointers;
exception_info.ClientPointers = FALSE;
Comment thread
sentry[bot] marked this conversation as resolved.
}

// Determine minidump type based on configuration
MINIDUMP_TYPE dump_type;
Expand Down
6 changes: 6 additions & 0 deletions src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ typedef struct {
sentry_thread_context_windows_t threads[SENTRY_CRASH_MAX_THREADS];
} sentry_crash_platform_windows_t;

typedef struct {
DWORD version;
DWORD app_pid;
uint64_t app_tid;
} sentry_wer_registration_t;

# ifdef _MSC_VER
# pragma warning(pop)
# endif
Expand Down
229 changes: 229 additions & 0 deletions src/backends/native/sentry_wer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
#include "sentry_crash_context.h"

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <werapi.h>
#include <windows.h>

#ifndef STATUS_FAIL_FAST_EXCEPTION
# define STATUS_FAIL_FAST_EXCEPTION ((DWORD)0xC0000602)
#endif

#ifndef STATUS_STACK_BUFFER_OVERRUN
# define STATUS_STACK_BUFFER_OVERRUN ((DWORD)0xC0000409)
#endif

static BOOL
is_fatal_wer_exception(const WER_RUNTIME_EXCEPTION_INFORMATION *info)
{
// bIsFatal is missing in older SDKs; guard access with dwSize.
typedef struct {
DWORD dwSize;
HANDLE hProcess;
HANDLE hThread;
EXCEPTION_RECORD exceptionRecord;
CONTEXT context;
PCWSTR pwszReportId;
BOOL bIsFatal;
DWORD dwReserved;
} WER_RUNTIME_EXCEPTION_INFORMATION_19041;

if (!info
|| info->dwSize
<= offsetof(WER_RUNTIME_EXCEPTION_INFORMATION_19041, bIsFatal)) {
return FALSE;
}

return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info)->bIsFatal;
}

static BOOL
is_native_wer_exception(DWORD code)
{
return code == STATUS_FAIL_FAST_EXCEPTION
|| code == STATUS_STACK_BUFFER_OVERRUN;
}

static BOOL
read_registration(
HANDLE process, PVOID context, sentry_wer_registration_t *registration)
{
if (!process || !context || !registration) {
return FALSE;
}

if (!ReadProcessMemory(
process, context, registration, sizeof(*registration), NULL)) {
return FALSE;
}

return registration->version == 1 && registration->app_pid != 0;
}

static BOOL
open_native_crash_objects(const sentry_wer_registration_t *registration,
HANDLE *mapping, HANDLE *event, sentry_crash_context_t **ctx)
{
wchar_t shm_name[SENTRY_CRASH_IPC_NAME_SIZE];
wchar_t event_name[SENTRY_CRASH_IPC_NAME_SIZE];

swprintf(shm_name, SENTRY_CRASH_IPC_NAME_SIZE,
L"Local\\SentryCrash-%lu-%llx", (unsigned long)registration->app_pid,
(unsigned long long)registration->app_tid);
swprintf(event_name, SENTRY_CRASH_IPC_NAME_SIZE,
L"Local\\SentryCrashEvent-%lu-%llx",
(unsigned long)registration->app_pid,
(unsigned long long)registration->app_tid);

*mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, shm_name);
if (!*mapping) {
return FALSE;
}

*ctx = MapViewOfFile(
*mapping, FILE_MAP_ALL_ACCESS, 0, 0, SENTRY_CRASH_SHM_SIZE);
if (!*ctx) {
CloseHandle(*mapping);
*mapping = NULL;
return FALSE;
}

if ((*ctx)->magic != SENTRY_CRASH_MAGIC) {
UnmapViewOfFile(*ctx);
CloseHandle(*mapping);
*ctx = NULL;
*mapping = NULL;
return FALSE;
}

*event = OpenEventW(EVENT_MODIFY_STATE, FALSE, event_name);
if (!*event) {
UnmapViewOfFile(*ctx);
CloseHandle(*mapping);
*ctx = NULL;
*mapping = NULL;
return FALSE;
}

return TRUE;
}

static BOOL
process_wer_exception(
PVOID context, const WER_RUNTIME_EXCEPTION_INFORMATION *exception_info)
{
if (!exception_info || !is_fatal_wer_exception(exception_info)
|| !is_native_wer_exception(
exception_info->exceptionRecord.ExceptionCode)) {
return FALSE;
}

sentry_wer_registration_t registration = { 0 };
if (!read_registration(exception_info->hProcess, context, &registration)) {
return FALSE;
}

HANDLE mapping = NULL;
HANDLE event = NULL;
sentry_crash_context_t *ctx = NULL;
if (!open_native_crash_objects(&registration, &mapping, &event, &ctx)) {
return FALSE;
}

BOOL claimed = FALSE;
if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING,
SENTRY_CRASH_STATE_READY)
== SENTRY_CRASH_STATE_READY) {
ctx->crashed_pid = GetProcessId(exception_info->hProcess);
ctx->crashed_tid = GetThreadId(exception_info->hThread);
ctx->platform.exception_code
= exception_info->exceptionRecord.ExceptionCode;
ctx->platform.exception_record = exception_info->exceptionRecord;
ctx->platform.context = exception_info->context;
ctx->platform.exception_pointers = NULL;
ctx->platform.num_threads = 1;
ctx->platform.threads[0].thread_id = ctx->crashed_tid;
ctx->platform.threads[0].context = exception_info->context;

InterlockedExchange(&ctx->state, SENTRY_CRASH_STATE_CRASHED);
if (SetEvent(event)) {
claimed = TRUE;
uint64_t timeout_ms = ctx->shutdown_timeout
? ctx->shutdown_timeout
: SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS;
for (uint64_t waited_ms = 0; waited_ms < timeout_ms;
waited_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS) {
if (InterlockedCompareExchange(&ctx->state,
SENTRY_CRASH_STATE_DONE, SENTRY_CRASH_STATE_DONE)
== SENTRY_CRASH_STATE_DONE) {
break;
}
Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
}
TerminateProcess(exception_info->hProcess,
exception_info->exceptionRecord.ExceptionCode);
}
Comment thread
sentry[bot] marked this conversation as resolved.
}

CloseHandle(event);
UnmapViewOfFile(ctx);
CloseHandle(mapping);
return claimed;
}

BOOL WINAPI
DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
{
(void)instance;
(void)reason;
(void)reserved;
return TRUE;
}

HRESULT WINAPI
OutOfProcessExceptionEventCallback(PVOID context,
const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info,
BOOL *ownership_claimed, PWSTR event_name, PDWORD event_name_size,
PDWORD signature_count)
{
(void)event_name;
(void)event_name_size;
(void)signature_count;

*ownership_claimed = FALSE;
if (process_wer_exception(context, exception_info)) {
*ownership_claimed = TRUE;
}
return S_OK;
Comment thread
sentry[bot] marked this conversation as resolved.
}

HRESULT WINAPI
OutOfProcessExceptionEventSignatureCallback(PVOID context,
const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info, DWORD index,
PWSTR name, PDWORD name_size, PWSTR value, PDWORD value_size)
{
(void)context;
(void)exception_info;
(void)index;
(void)name;
(void)name_size;
(void)value;
(void)value_size;
return E_FAIL;
}

HRESULT WINAPI
OutOfProcessExceptionEventDebuggerLaunchCallback(PVOID context,
const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info,
PBOOL is_custom_debugger, PWSTR debugger_launch,
PDWORD debugger_launch_size, PBOOL is_debugger_autolaunch)
{
(void)context;
(void)exception_info;
(void)is_custom_debugger;
(void)debugger_launch;
(void)debugger_launch_size;
(void)is_debugger_autolaunch;
return E_FAIL;
}
6 changes: 6 additions & 0 deletions src/backends/native/sentry_wer.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
LIBRARY "sentry-wer.dll"

EXPORTS
OutOfProcessExceptionEventCallback
OutOfProcessExceptionEventSignatureCallback
OutOfProcessExceptionEventDebuggerLaunchCallback
Loading
Loading