Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e4e703e
feat(native): WER report
jpnurmi Jun 2, 2026
641bf36
ReportId vs. ReportIntegratorId
jpnurmi Jun 3, 2026
9c3e82f
close handles
jpnurmi Jun 3, 2026
25861f6
attach_wer_report
jpnurmi Jun 3, 2026
617239e
!exception_info
jpnurmi Jun 3, 2026
7e04976
respect attach_wer_report for the wait
jpnurmi Jun 3, 2026
57d3086
Update CHANGELOG.md
jpnurmi Jun 3, 2026
bcac0cb
no report
jpnurmi Jun 3, 2026
eaf18c6
fix minidump & report id
jpnurmi Jun 3, 2026
e8a3609
test all crash modes
jpnurmi Jun 3, 2026
26eb821
fix sentry__path_copy check
jpnurmi Jun 3, 2026
b01fcd1
simplify state machine
jpnurmi Jun 3, 2026
5511bb3
resolve WER symbols & bump down from V3 to V2
jpnurmi Jun 3, 2026
1286013
revert accidental reformatting
jpnurmi Jun 3, 2026
e165e94
restore is_fatal_exception
jpnurmi Jun 3, 2026
d8c4e7b
minimize diff
jpnurmi Jun 3, 2026
10b8a7f
fix sentry__path_copy rv handling
jpnurmi Jun 3, 2026
155f698
E_STORE_USER_ARCHIVE vs. E_STORE_MACHINE_ARCHIVE
jpnurmi Jun 3, 2026
4c9229a
pytest skip != win32
jpnurmi Jun 3, 2026
e72acc2
check WideCharToMultiByte rv
jpnurmi Jun 3, 2026
0ee38c1
Revert "fix sentry__path_copy rv handling"
jpnurmi Jun 3, 2026
8799ebe
fix leak
jpnurmi Jun 3, 2026
5c812aa
utf8
jpnurmi Jun 3, 2026
8f61b96
fix leaks
jpnurmi Jun 3, 2026
84a4568
sys32
jpnurmi Jun 3, 2026
e5699a4
!SENTRY_PLATFORM_XBOX
jpnurmi Jun 3, 2026
d701cad
void*
jpnurmi Jun 3, 2026
92c9153
Merge branch 'master' into jpnurmi/feat/native/wer
jpnurmi Jun 3, 2026
e77d570
uuid: braces
jpnurmi Jun 4, 2026
bd2e8d3
Merge remote-tracking branch 'origin/master' into jpnurmi/feat/native…
jpnurmi Jul 23, 2026
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
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,9 @@ elseif(SENTRY_BACKEND_NATIVE)
add_dependencies(sentry sentry-crash)
if(WIN32 AND NOT XBOX)
add_dependencies(sentry sentry-wer)
# TODO: resolve symbols at run-time
target_link_libraries(sentry PRIVATE wer)
target_link_libraries(sentry-crash PRIVATE wer)
endif()

# Install daemon
Expand Down
7 changes: 5 additions & 2 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 Expand Up @@ -1102,8 +1105,8 @@ main(int argc, char **argv)
sentry_scope_add_breadcrumb(scope, debug_crumb);

if (has_arg(argc, argv, "attach-to-scope")) {
// assuming the example / test is run directly from the cmake build
// directory
// assuming the example / test is run directly from the cmake
// build directory
sentry_scope_attach_file(scope, "./CMakeCache.txt");
sentry_attachment_t *bytes = sentry_scope_attach_bytes(
scope, "\xc0\xff\xee", 3, "bytes.bin");
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
10 changes: 8 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,10 @@ 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_POSTPROCESSING = 3,
SENTRY_CRASH_STATE_POSTPROCESSED = 4,
SENTRY_CRASH_STATE_CAPTURED = 5,
SENTRY_CRASH_STATE_DONE = 6
} sentry_crash_state_t;

/**
Expand Down Expand Up @@ -242,6 +244,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 +286,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
91 changes: 90 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,43 @@ enumerate_threads_from_process(sentry_crash_context_t *ctx)
ctx->crashed_pid);
# endif // SENTRY_PLATFORM_XBOX
}

static sentry_path_t *
find_wer_report(sentry_uuid_t *report_id)
{
HREPORTSTORE report_store;
if (WerStoreOpen(E_STORE_MACHINE_ARCHIVE, &report_store) != S_OK) {
return NULL;
}

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

HRESULT hr = WerStoreGetFirstReportKey(report_store, &report_key);
while (SUCCEEDED(hr) && report_key && !report_path) {
if (WerStoreQueryReportMetadataV3(
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.
WerFreeString(report_key);
hr = WerStoreGetNextReportKey(report_store, &report_key);
}

WerStoreClose(report_store);

return report_path;
}
#endif // SENTRY_PLATFORM_WINDOWS

/**
Expand Down Expand Up @@ -2819,6 +2857,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 @@ -3288,7 +3337,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 +3350,45 @@ 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");
sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_POSTPROCESSING);

Comment thread
jpnurmi marked this conversation as resolved.
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_POSTPROCESSED) {
break;
}
Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
elapsed_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS;
}
SENTRY_DEBUGF("WER report ID: %s", ctx->platform.wer_report_id);

sentry_uuid_t report_id
= sentry_uuid_from_string(ctx->platform.wer_report_id);

elapsed_ms = 0;
while (elapsed_ms < SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS) {
Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
sentry_path_t *wer_report_path = find_wer_report(&report_id);
if (wer_report_path) {
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
SENTRY_DEBUGF("Found WER report: %s", 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");
}
Comment thread
cursor[bot] marked this conversation as resolved.
sentry__path_free(run_report_path);
Comment thread
jpnurmi marked this conversation as resolved.
Outdated
sentry__path_free(wer_report_path);
break;
}
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_POSTPROCESSING) {
// Either WER is post-processing, or daemon already captured
// crash data (no logging - exception filter context)
break;
}
Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS);
Expand Down
69 changes: 50 additions & 19 deletions src/backends/native/sentry_wer.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
# 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,6 +38,20 @@ is_fatal_wer_exception(const WER_RUNTIME_EXCEPTION_INFORMATION *info)
return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info)->bIsFatal;
}

static PCWSTR
get_report_id(const WER_RUNTIME_EXCEPTION_INFORMATION *info)
{
// 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
is_native_wer_exception(DWORD code)
{
Expand Down Expand Up @@ -113,25 +127,41 @@ 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;
}

Comment thread
cursor[bot] marked this conversation as resolved.
sentry_wer_registration_t registration = { 0 };
if (!read_registration(exception_info->hProcess, context, &registration)) {
if (!exception_info
|| !read_registration(
exception_info->hProcess, context, &registration)) {
return FALSE;
}

BOOL claimed = 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;
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.

// advance POSTPROCESSING -> POSTPROCESSED
if (InterlockedCompareExchange(&ctx->state,
SENTRY_CRASH_STATE_POSTPROCESSED, SENTRY_CRASH_STATE_POSTPROCESSING)
== SENTRY_CRASH_STATE_POSTPROCESSING) {
goto done;
}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

if (!is_fatal_wer_exception(exception_info)
|| !is_native_wer_exception(
exception_info->exceptionRecord.ExceptionCode)) {
goto done;
}

if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING,
SENTRY_CRASH_STATE_READY)
== SENTRY_CRASH_STATE_READY) {
Expand Down Expand Up @@ -166,6 +196,7 @@ process_wer_exception(
}
}

done:
CloseHandle(event);
UnmapViewOfFile(ctx);
CloseHandle(mapping);
Expand Down
Loading
Loading