Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 🚫 The changelog entry seems to be part of an already released section ## 0.15.4.
    Consider moving the entry to the ## Unreleased section, please.

- Add Upload-Metadata header to TUS requests. ([#1795](https://github.com/getsentry/sentry-native/pull/1795))

**Fixes**:
Expand Down
3 changes: 3 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,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 @@ -1924,6 +1924,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
209 changes: 208 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 @@ -3009,6 +3010,161 @@ enumerate_threads_from_process(sentry_crash_context_t *ctx)
ctx->crashed_pid);
# endif // SENTRY_PLATFORM_XBOX
}

# if !defined(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
= LoadLibraryExW(L"wer.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
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;
}

sentry_path_t *report_path = NULL;
REPORT_STORE_TYPES store_types[]
= { E_STORE_USER_ARCHIVE, E_STORE_MACHINE_ARCHIVE };
for (size_t i = 0;
!report_path && i < sizeof(store_types) / sizeof(store_types[0]); i++) {
HREPORTSTORE report_store;
if (g_wer.WerStoreOpen(store_types[i], &report_store) != S_OK) {
continue;
}

PCWSTR report_key = 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);
}
}
}
g_wer.WerFreeString(report_key);
if (!report_path) {
hr = g_wer.WerStoreGetNextReportKey(report_store, &report_key);
}
}
Comment thread
sentry[bot] marked this conversation as resolved.

g_wer.WerStoreClose(report_store);
}

return report_path;
}

/**
* Reads a WER report, converts it to UTF-16LE -> UTF-8, and writes to the .run
* directory.
*
* TODO: use sentry__path_copy: https://github.com/getsentry/sentry/issues/91336
*/
static bool
write_wer_report(sentry_path_t *report_path, sentry_path_t *run_folder)
{
int rv = -1;
char *utf8 = NULL;
void *utf16 = NULL;
size_t utf16_size = 0;

sentry_path_t *run_path = sentry__path_join_str(run_folder, "Report.wer");
if (!run_path) {
goto cleanup;
}

utf16 = sentry__path_read_to_buffer(report_path, &utf16_size);
if (!utf16) {
SENTRY_WARN("Failed to read WER report");
goto cleanup;
}

utf8 = sentry__string_from_wstr_n(
(const wchar_t *)utf16, utf16_size / sizeof(wchar_t));
if (!utf8) {
SENTRY_WARN("Failed to convert WER report to UTF-8");
goto cleanup;
}

if ((rv = sentry__path_write_buffer(run_path, utf8, strlen(utf8))) != 0) {
SENTRY_WARN("Failed to write WER report");
goto cleanup;
}

cleanup:
sentry_free(utf8);
sentry_free(utf16);
sentry__path_free(run_path);
return rv == 0;
}
# endif // !SENTRY_PLATFORM_XBOX
#endif // SENTRY_PLATFORM_WINDOWS

/**
Expand Down Expand Up @@ -3685,6 +3841,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 @@ -3959,6 +4126,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 @@ -4175,7 +4353,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 @@ -4187,6 +4366,34 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
enumerate_threads_from_process(ctx);
}
}

# if !defined(SENTRY_PLATFORM_XBOX)
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);
if (write_wer_report(wer_report_path, run_folder)) {
sentry__path_free(wer_report_path);
break;
}
sentry__path_free(wer_report_path);
}
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
#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
Loading
Loading