Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add a `transfer_timeout` option for SDK-managed HTTP transports. ([#1741](https://github.com/getsentry/sentry-native/pull/1741))
- Apple: use `os_sync_wait_on_address` for the level-triggered waitable flag in the batcher on modern macOS(14.4+) and iOS(17.4+). ([#1765](https://github.com/getsentry/sentry-native/pull/1765))
- Native/macOS: add thread names. ([#1766](https://github.com/getsentry/sentry-native/pull/1766))
- Native/Windows: add `attach_wer_report` option for attaching `Report.wer` generated by WER. ([#1777](https://github.com/getsentry/sentry-native/pull/1777))

**Fixes**:

Expand Down
3 changes: 3 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,9 @@ main(int argc, char **argv)
sentry_options_set_crash_upload_mode(
options, SENTRY_CRASH_UPLOAD_MODE_ASYNC);
}
if (has_arg(argc, argv, "attach-wer-report")) {
sentry_options_set_attach_wer_report(options, true);
}

// E2E test mode: generate unique test ID for event correlation
char e2e_test_id[37] = { 0 };
Expand Down
12 changes: 12 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,18 @@ SENTRY_API void sentry_options_set_crash_upload_mode(
SENTRY_API sentry_crash_upload_mode_t sentry_options_get_crash_upload_mode(
const sentry_options_t *opts);

/**
* Enables or disables attaching WER (Windows Error Reporting) reports. This is
* disabled by default.
*
* When enabled, a `Report.wer` file generated by WER is attached to crash
* events.
*
* This setting only has an effect when using the `native` backend on Windows.
*/
SENTRY_EXPERIMENTAL_API void sentry_options_set_attach_wer_report(
sentry_options_t *opts, int val);

/**
* Enables a wait for the crash report upload to be finished before shutting
* down. This is disabled by default.
Expand Down
9 changes: 7 additions & 2 deletions src/backends/native/sentry_crash_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ typedef enum {
SENTRY_CRASH_STATE_READY = 0,
SENTRY_CRASH_STATE_CRASHED = 1,
SENTRY_CRASH_STATE_PROCESSING = 2,
SENTRY_CRASH_STATE_CAPTURED = 3,
SENTRY_CRASH_STATE_DONE = 4
SENTRY_CRASH_STATE_PROCESSED = 3,
SENTRY_CRASH_STATE_CAPTURED = 4,
SENTRY_CRASH_STATE_DONE = 5
} sentry_crash_state_t;

/**
Expand Down Expand Up @@ -242,6 +243,9 @@ typedef struct {
// Additional thread contexts
DWORD num_threads;
sentry_thread_context_windows_t threads[SENTRY_CRASH_MAX_THREADS];

bool wer_enabled;
char wer_report_id[64];
} sentry_crash_platform_windows_t;

typedef struct {
Expand Down Expand Up @@ -281,6 +285,7 @@ typedef struct {
bool attach_screenshot; // Screenshot attachment enabled in parent process
bool attach_session_replay; // Session replay attachment enabled in parent
// process
bool attach_wer_report; // WER report attachment enabled in parent process
uint32_t session_replay_duration; // Requested session replay duration in
// ms
int cache_keep; // sentry_cache_keep_t
Expand Down
157 changes: 156 additions & 1 deletion src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
# include <fcntl.h>
# include <io.h>
# include <sys/stat.h>
# include <werapi.h>
# include <windows.h>

// Forward declaration for StackWalk64-based stack unwinding (defined later)
Expand Down Expand Up @@ -2211,6 +2212,106 @@ enumerate_threads_from_process(sentry_crash_context_t *ctx)
ctx->crashed_pid);
# endif // SENTRY_PLATFORM_XBOX
}

typedef HRESULT(WINAPI *WerStoreOpen_t)(REPORT_STORE_TYPES, HREPORTSTORE *);
typedef void(WINAPI *WerStoreClose_t)(HREPORTSTORE);
typedef HRESULT(WINAPI *WerStoreGetFirstReportKey_t)(HREPORTSTORE, PCWSTR *);
typedef HRESULT(WINAPI *WerStoreGetNextReportKey_t)(HREPORTSTORE, PCWSTR *);
typedef HRESULT(WINAPI *WerStoreQueryReportMetadataV2_t)(
HREPORTSTORE, PCWSTR, PWER_REPORT_METADATA_V2);
typedef void(WINAPI *WerFreeString_t)(PCWSTR);

static struct {
HMODULE module;
WerStoreOpen_t WerStoreOpen;
WerStoreClose_t WerStoreClose;
WerStoreGetFirstReportKey_t WerStoreGetFirstReportKey;
WerStoreGetNextReportKey_t WerStoreGetNextReportKey;
WerStoreQueryReportMetadataV2_t WerStoreQueryReportMetadataV2;
WerFreeString_t WerFreeString;
} g_wer = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };

# define WER_FAILED ((HMODULE)(intptr_t)-1)

static bool
resolve_wer(void)
{
if (g_wer.module) {
return g_wer.module != WER_FAILED;
}

g_wer.module = LoadLibraryW(L"wer.dll");
Comment thread
sentry-warden[bot] marked this conversation as resolved.
Outdated
if (!g_wer.module) {
g_wer.module = WER_FAILED;
return false;
}

g_wer.WerStoreOpen
= (WerStoreOpen_t)GetProcAddress(g_wer.module, "WerStoreOpen");
g_wer.WerStoreClose
= (WerStoreClose_t)GetProcAddress(g_wer.module, "WerStoreClose");
g_wer.WerStoreGetFirstReportKey
= (WerStoreGetFirstReportKey_t)GetProcAddress(
g_wer.module, "WerStoreGetFirstReportKey");
g_wer.WerStoreGetNextReportKey = (WerStoreGetNextReportKey_t)GetProcAddress(
g_wer.module, "WerStoreGetNextReportKey");
g_wer.WerStoreQueryReportMetadataV2
= (WerStoreQueryReportMetadataV2_t)GetProcAddress(
g_wer.module, "WerStoreQueryReportMetadataV2");
g_wer.WerFreeString
= (WerFreeString_t)GetProcAddress(g_wer.module, "WerFreeString");

if (!g_wer.WerStoreOpen || !g_wer.WerStoreClose
|| !g_wer.WerStoreGetFirstReportKey || !g_wer.WerStoreGetNextReportKey
|| !g_wer.WerStoreQueryReportMetadataV2 || !g_wer.WerFreeString) {
FreeLibrary(g_wer.module);
g_wer.module = WER_FAILED;
return false;
}

return true;
}

static sentry_path_t *
find_wer_report(sentry_uuid_t *report_id)
{
if (!resolve_wer()) {
return NULL;
}

HREPORTSTORE report_store;
if (g_wer.WerStoreOpen(E_STORE_MACHINE_ARCHIVE, &report_store) != S_OK) {
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
return NULL;
}

PCWSTR report_key = NULL;
sentry_path_t *report_path = NULL;
WER_REPORT_METADATA_V2 report_data = { 0 };

HRESULT hr = g_wer.WerStoreGetFirstReportKey(report_store, &report_key);
while (SUCCEEDED(hr) && report_key && !report_path) {
if (g_wer.WerStoreQueryReportMetadataV2(
report_store, report_key, &report_data)
== S_OK) {
if (sentry__uuid_equal_native(report_id, &report_data.ReportId)
|| sentry__uuid_equal_native(
report_id, &report_data.ReportIntegratorId)) {
sentry_path_t *report_dir = sentry__path_from_wstr(report_key);
if (report_dir) {
report_path
= sentry__path_join_str(report_dir, "Report.wer");
sentry__path_free(report_dir);
}
}
}
Comment thread
sentry[bot] marked this conversation as resolved.
g_wer.WerFreeString(report_key);
hr = g_wer.WerStoreGetNextReportKey(report_store, &report_key);
}

g_wer.WerStoreClose(report_store);

return report_path;
}
#endif // SENTRY_PLATFORM_WINDOWS

/**
Expand Down Expand Up @@ -2819,6 +2920,17 @@ write_envelope_with_native_stacktrace(const sentry_options_t *options,
}
}

// Add Report.wer if captured by WER
if (ctx->attach_wer_report && run_folder) {
sentry_path_t *report_path
= sentry__path_join_str(run_folder, "Report.wer");
if (report_path) {
write_attachment_to_envelope(
fd, report_path->path, "Report.wer", NULL, "text/plain");
sentry__path_free(report_path);
}
}

#if defined(SENTRY_PLATFORM_UNIX)
close(fd);
#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -3071,6 +3183,17 @@ write_envelope_with_minidump(const sentry_options_t *options,
}
}

// Add Report.wer if captured by WER
if (ctx->attach_wer_report && run_folder) {
sentry_path_t *report_path
= sentry__path_join_str(run_folder, "Report.wer");
if (report_path) {
write_attachment_to_envelope(
fd, report_path->path, "Report.wer", NULL, "text/plain");
sentry__path_free(report_path);
}
}

#if defined(SENTRY_PLATFORM_UNIX)
close(fd);
#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -3288,7 +3411,8 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
}
#endif

// On Windows, capture modules and threads from the crashed process
// On Windows, capture modules, threads, and WER report for the crashed
// process
#if defined(SENTRY_PLATFORM_WINDOWS)
if (use_native_mode) {
if (ctx->module_count == 0) {
Expand All @@ -3300,6 +3424,37 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
enumerate_threads_from_process(ctx);
}
}

if (ctx->platform.wer_enabled && ctx->attach_wer_report && run_folder) {
SENTRY_DEBUG("Waiting for WER, allowing app process to exit");
sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_PROCESSED);
Comment thread
jpnurmi marked this conversation as resolved.
Comment thread
jpnurmi marked this conversation as resolved.

Comment thread
jpnurmi marked this conversation as resolved.
int elapsed_ms = 0;
while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) {
if (!sentry__string_empty(ctx->platform.wer_report_id)) {
sentry_uuid_t report_id
= sentry_uuid_from_string(ctx->platform.wer_report_id);
sentry_path_t *wer_report_path = find_wer_report(&report_id);
Comment thread
sentry[bot] marked this conversation as resolved.
if (wer_report_path) {
SENTRY_DEBUGF("Found WER report %s: %s",
ctx->platform.wer_report_id, wer_report_path->path);
sentry_path_t *run_report_path
= sentry__path_join_str(run_folder, "Report.wer");
if (!run_report_path
|| sentry__path_copy(
wer_report_path, run_report_path)) {
SENTRY_WARN("Failed to copy WER report");
}
sentry__path_free(run_report_path);
sentry__path_free(wer_report_path);
break;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}
Comment thread
cursor[bot] marked this conversation as resolved.
}
Comment thread
sentry[bot] marked this conversation as resolved.

Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
elapsed_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
#endif

// Write envelope based on mode
Expand Down
11 changes: 3 additions & 8 deletions src/backends/native/sentry_crash_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -1026,17 +1026,12 @@ crash_exception_filter(EXCEPTION_POINTERS *exception_info)

// Wait for daemon to finish processing (keep process alive for
// minidump)
bool processing_started = false;
int elapsed_ms = 0;
while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) {
long state = sentry__atomic_fetch(&ctx->state);
if (state == SENTRY_CRASH_STATE_PROCESSING && !processing_started) {
// Daemon started processing (no logging - exception filter
// context)
processing_started = true;
} else if (state >= SENTRY_CRASH_STATE_CAPTURED) {
// Daemon captured crash data (no logging - exception filter
// context)
if (state >= SENTRY_CRASH_STATE_PROCESSED) {
// Daemon already processed crash data (no logging - exception
// filter context)
break;
}
Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
Expand Down
55 changes: 30 additions & 25 deletions src/backends/native/sentry_wer.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,21 @@
#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
typedef struct {
DWORD dwSize;
HANDLE hProcess;
HANDLE hThread;
EXCEPTION_RECORD exceptionRecord;
CONTEXT context;
PCWSTR pwszReportId;
BOOL bIsFatal;
DWORD dwReserved;
} WER_RUNTIME_EXCEPTION_INFORMATION_19041;

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)) {
Expand All @@ -38,11 +30,18 @@ is_fatal_wer_exception(const WER_RUNTIME_EXCEPTION_INFORMATION *info)
return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info)->bIsFatal;
}

static BOOL
is_native_wer_exception(DWORD code)
static PCWSTR
get_report_id(const WER_RUNTIME_EXCEPTION_INFORMATION *info)
{
return code == STATUS_FAIL_FAST_EXCEPTION
|| code == STATUS_STACK_BUFFER_OVERRUN;
// pwszReportId is missing in older SDKs; guard access with dwSize.
if (!info
|| info->dwSize <= offsetof(
WER_RUNTIME_EXCEPTION_INFORMATION_19041, pwszReportId)) {
return NULL;
}

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

static BOOL
Expand Down Expand Up @@ -113,9 +112,7 @@ 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)) {
if (!exception_info || !is_fatal_wer_exception(exception_info)) {
return FALSE;
}

Comment thread
cursor[bot] marked this conversation as resolved.
Expand All @@ -132,6 +129,14 @@ process_wer_exception(
}

BOOL claimed = FALSE;
Comment thread
cursor[bot] marked this conversation as resolved.
PCWSTR report_id = get_report_id(exception_info);
if (report_id) {
WideCharToMultiByte(CP_UTF8, 0, report_id, -1,
ctx->platform.wer_report_id,
(int)sizeof(ctx->platform.wer_report_id), NULL, NULL);
}
Comment thread
sentry[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

// SENTRY_CRASH_STATE_READY: hard WER crash that bypassed the crash handler
if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING,
SENTRY_CRASH_STATE_READY)
== SENTRY_CRASH_STATE_READY) {
Expand Down
Loading
Loading