Skip to content
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a211f3e
feat(native): capture WER custom metadata
jpnurmi May 11, 2026
df51135
changelog
jpnurmi May 27, 2026
0f60503
review findings
jpnurmi May 27, 2026
bf8b0f7
make format
jpnurmi May 27, 2026
fa59321
re-remove wait-loop
jpnurmi May 27, 2026
d65d260
sentry__value_to_json
jpnurmi May 27, 2026
e8f51f6
fix test_native_abort
jpnurmi May 27, 2026
fc07b0b
fix review finding
jpnurmi May 27, 2026
315f250
fix: always notify daemon in crash handler WER path
jpnurmi May 28, 2026
765fc5d
fix: set exception_pointers to NULL in crash handler WER path
jpnurmi May 29, 2026
bf427a8
DEBUG
jpnurmi May 29, 2026
54afb7b
++
jpnurmi May 29, 2026
5430fe0
wait for daemon capture
jpnurmi May 29, 2026
3ab8f64
cleanup
jpnurmi May 29, 2026
f60abe1
round-trip
jpnurmi May 29, 2026
287eeb1
partial revert
jpnurmi May 29, 2026
7cbdb44
integration test
jpnurmi May 29, 2026
7f1687c
local wer_report_id
jpnurmi May 29, 2026
985abed
DEBUG
jpnurmi Jun 1, 2026
4d9c72e
use_wer -> SENTRY_CRASH_STATE_PROCESSING
jpnurmi Jun 1, 2026
1eebc98
earlier
jpnurmi Jun 1, 2026
eaa51dc
should_use_wer
jpnurmi Jun 1, 2026
57185c4
re-enable all tests
jpnurmi Jun 1, 2026
d535b7a
sentry_wer_sync_mode_t
jpnurmi Jun 1, 2026
e125ce5
WIP
jpnurmi Jun 1, 2026
678581b
WIP++
jpnurmi Jun 1, 2026
9dc23d7
Revert "DEBUG"
jpnurmi Jun 1, 2026
d137041
sync wer custom metadata as tags
jpnurmi Jun 1, 2026
b088def
debug--
jpnurmi Jun 1, 2026
f38c953
cas
jpnurmi Jun 1, 2026
b699bc9
!wkey || !wvalue
jpnurmi Jun 1, 2026
cfb5df9
update test
jpnurmi Jun 1, 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 @@ -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: capture WER custom metadata. ([#1760](https://github.com/getsentry/sentry-native/pull/1760))

**Fixes**:

Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ elseif(SENTRY_BACKEND_NATIVE)
${PROJECT_SOURCE_DIR}/src/backends/native
)
target_link_libraries(sentry-wer PRIVATE wer)
target_link_libraries(sentry 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)
Expand Down Expand Up @@ -868,6 +869,9 @@ elseif(SENTRY_BACKEND_NATIVE)

# Link same libraries as sentry
target_link_libraries(sentry-crash PRIVATE ${_SENTRY_PLATFORM_LIBS})
if(WIN32)
target_link_libraries(sentry-crash PRIVATE wer)
endif()
if(APPLE)
find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED)
find_library(SECURITY_LIBRARY Security REQUIRED)
Expand Down
20 changes: 20 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ get_arg_value(int argc, char **argv, const char *arg)

#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(__MINGW32__) \
&& !defined(__MINGW64__)
# include <werapi.h>

int
call_rffe_many_times()
Expand Down Expand Up @@ -879,6 +880,25 @@ main(int argc, char **argv)
options, SENTRY_CRASH_UPLOAD_MODE_ASYNC);
}

#ifdef SENTRY_PLATFORM_WINDOWS
if (has_arg(argc, argv, "wer-sync-mode")) {
const char *arg = get_arg_value(argc, argv, "wer-sync-mode");
if (arg != NULL) {
sentry_wer_sync_mode_t mode = SENTRY_WER_SYNC_MODE_NONE;
if (strcmp(arg, "from-wer") == 0) {
mode = SENTRY_WER_SYNC_MODE_FROM_WER;
} else if (strcmp(arg, "to-wer") == 0) {
mode = SENTRY_WER_SYNC_MODE_TO_WER;
} else if (strcmp(arg, "from-to-wer") == 0) {
mode = (sentry_wer_sync_mode_t)(SENTRY_WER_SYNC_MODE_FROM_WER
| SENTRY_WER_SYNC_MODE_TO_WER);
}
sentry_options_set_wer_sync_mode(options, mode);
WerRegisterCustomMetadata(L"SentryWer", L"value from WER");
}
}
#endif

// E2E test mode: generate unique test ID for event correlation
char e2e_test_id[37] = { 0 };
if (has_arg(argc, argv, "e2e-test")) {
Expand Down
21 changes: 21 additions & 0 deletions include/sentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,15 @@ typedef enum {
SENTRY_CRASH_UPLOAD_MODE_ASYNC = 1,
} sentry_crash_upload_mode_t;

/**
* Synchronization mode for Windows Error Reporting (WER) interop.
*/
typedef enum sentry_wer_sync_mode_t {
SENTRY_WER_SYNC_MODE_NONE = 0,
SENTRY_WER_SYNC_MODE_FROM_WER = 1 << 0,
SENTRY_WER_SYNC_MODE_TO_WER = 1 << 1,
} sentry_wer_sync_mode_t;

/**
* Controls if and when envelopes are kept in the persistent cache.
*/
Expand Down Expand Up @@ -1922,6 +1931,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);

/**
* Sets the Windows Error Reporting (WER) synchronization mode.
*
* This setting controls how data is synced between WER and Sentry. The value is
* bitmask and can combine flags from `sentry_wer_sync_mode_t`.
*
* This setting only has an effect when using the `native` backend on Windows.
* Default is `SENTRY_WER_SYNC_MODE_NONE`.
*/
SENTRY_API void sentry_options_set_wer_sync_mode(
sentry_options_t *opts, sentry_wer_sync_mode_t mode);

/**
* Enables a wait for the crash report upload to be finished before shutting
* down. This is disabled by default.
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ elseif(SENTRY_BACKEND_NATIVE)
elseif(WIN32)
sentry_target_sources_cwd(sentry
backends/native/minidump/sentry_minidump_windows.c
backends/native/sentry_wer_report.c
backends/native/sentry_wer_report.h
)
endif()

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,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 @@ -290,6 +293,9 @@ typedef struct {
uint64_t shutdown_timeout;
uint64_t transfer_timeout;
bool system_crash_reporter_enabled;
int wer_sync_mode; // sentry_wer_sync_mode_t

char crash_event_id[37];

// Atomic user consent (sentry_user_consent_t), updated whenever user
// consent changes so the daemon can honor it at crash time.
Expand Down
111 changes: 106 additions & 5 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#include "sentry_utils.h"
#include "sentry_uuid.h"
#include "sentry_value.h"
#if defined(SENTRY_PLATFORM_WINDOWS)
# include "sentry_wer_report.h"
#endif
#include "transports/sentry_disk_transport.h"

#include <limits.h>
Expand Down Expand Up @@ -57,6 +60,21 @@
# include <sys/stat.h>
# include <windows.h>

static void
daemon_debugf(const char *fmt, ...)
{
char msg[1024];
va_list args;
va_start(args, fmt);
int n = vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
if (n < 0) {
return;
}
msg[sizeof(msg) - 1] = '\0';
OutputDebugStringA(msg);
}

// Forward declaration for StackWalk64-based stack unwinding (defined later)
static size_t walk_stack_with_dbghelp(HANDLE hProcess, DWORD crashed_tid,
const CONTEXT *ctx_record, sentry_frame_info_t *frames, size_t max_frames);
Expand Down Expand Up @@ -2232,6 +2250,64 @@ build_stacktrace_from_ctx(const sentry_crash_context_t *ctx)
return build_stacktrace_for_thread(ctx, SIZE_MAX);
}

#if defined(SENTRY_PLATFORM_WINDOWS)
static bool
add_wer_context(sentry_value_t event, const sentry_crash_context_t *ctx)
{
if ((ctx->wer_sync_mode & SENTRY_WER_SYNC_MODE_FROM_WER) == 0) {
daemon_debugf("### DAEMON: add_wer_context skipped sync_from_wer=0\n");
return false;
}

if (!ctx->platform.wer_enabled) {
daemon_debugf("### DAEMON: add_wer_context skipped wer_enabled=0\n");
return false;
}
Comment thread
cursor[bot] marked this conversation as resolved.

char wer_report_id[sizeof(ctx->platform.wer_report_id)];
memcpy(
wer_report_id, ctx->platform.wer_report_id, sizeof(wer_report_id) - 1);
wer_report_id[sizeof(wer_report_id) - 1] = '\0';

daemon_debugf(
"### DAEMON: add_wer_context start event_id=%s report_id=%s\n",
ctx->crash_event_id,
sentry__string_empty(wer_report_id) ? "(empty)" : wer_report_id);

sentry_value_t wer_context = sentry__wer_report_lookup(ctx->crash_event_id);
if (sentry_value_is_null(wer_context)) {
daemon_debugf("### DAEMON: WER metadata xml not found yet\n");
if (sentry__string_empty(wer_report_id)) {
daemon_debugf(
"### DAEMON: no fallback report_id, skipping wer context\n");
return false;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}
daemon_debugf("### DAEMON: using fallback report_id from callback\n");
wer_context = sentry_value_new_object();
} else {
daemon_debugf("### DAEMON: WER metadata xml found\n");
}

sentry_value_set_by_key(
wer_context, "type", sentry_value_new_string("wer"));
const char *context_report_id = sentry_value_as_string(
sentry_value_get_by_key(wer_context, "report_id"));
if (sentry__string_empty(context_report_id)) {
sentry_value_set_by_key(
wer_context, "report_id", sentry_value_new_string(wer_report_id));
}

sentry_value_t contexts = sentry_value_get_by_key(event, "contexts");
if (sentry_value_get_type(contexts) != SENTRY_VALUE_TYPE_OBJECT) {
contexts = sentry_value_new_object();
sentry_value_set_by_key(event, "contexts", contexts);
}
sentry_value_set_by_key(contexts, "wer", wer_context);
daemon_debugf("### DAEMON: add_wer_context attached wer context\n");
return true;
}
#endif

/**
* Build native crash event with exception, mechanism, and debug_meta
*
Expand Down Expand Up @@ -2262,6 +2338,11 @@ build_native_crash_event(
event = sentry_value_new_event();
}

if (!sentry__string_empty(ctx->crash_event_id)) {
sentry_value_set_by_key(
event, "event_id", sentry_value_new_string(ctx->crash_event_id));
}

// Set platform to native
sentry_value_set_by_key(
event, "platform", sentry_value_new_string("native"));
Expand Down Expand Up @@ -2582,6 +2663,10 @@ build_native_crash_event(
SENTRY_WARN("No modules captured - debug_meta.images will be empty!");
}

#if defined(SENTRY_PLATFORM_WINDOWS)
add_wer_context(event, ctx);
#endif

return event;
}

Expand Down Expand Up @@ -2850,18 +2935,28 @@ write_envelope_with_minidump(const sentry_options_t *options,
// Read event JSON data
size_t event_size = 0;
char *event_json = NULL;
char *event_id = NULL;
const char *event_id = ctx->crash_event_id;
sentry_path_t *ev_path = sentry__path_from_str(event_msgpack_path);
if (ev_path) {
event_json = sentry__path_read_to_buffer(ev_path, &event_size);
sentry__path_free(ev_path);
#if defined(SENTRY_PLATFORM_WINDOWS)
if (event_json && event_size > 0) {
sentry_value_t event
= sentry__value_from_json(event_json, event_size);
event_id = sentry__string_clone(sentry_value_as_string(
sentry_value_get_by_key(event, "event_id")));
if (!sentry_value_is_null(event) && add_wer_context(event, ctx)) {
size_t new_event_size = 0;
char *new_event_json
= sentry__value_to_json(event, &new_event_size);
if (new_event_json) {
sentry_free(event_json);
event_json = new_event_json;
event_size = new_event_size;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WER context dropped on JSON failure

Low Severity

In minidump-only envelope construction, after add_wer_context mutates the parsed event, a failed sentry__value_to_json leaves the original on-disk JSON unchanged, so the uploaded envelope can omit the WER context even though enrichment succeeded in memory.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9dc23d7. Configure here.

sentry_value_decref(event);
}
#endif
}

// Open envelope file for writing
Expand All @@ -2878,7 +2973,6 @@ write_envelope_with_minidump(const sentry_options_t *options,
if (fd < 0) {
SENTRY_WARN("Failed to open envelope file for writing");
sentry_free(event_json);
sentry_free(event_id);
return false;
}

Expand All @@ -2899,7 +2993,6 @@ write_envelope_with_minidump(const sentry_options_t *options,
} else {
header_len = snprintf(header_buf, sizeof(header_buf), "{}\n");
}
sentry_free(event_id);
if (header_len > 0 && header_len < (int)sizeof(header_buf)) {
#if defined(SENTRY_PLATFORM_UNIX)
if (write(fd, header_buf, header_len) != header_len) {
Expand Down Expand Up @@ -3106,6 +3199,12 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
// Mark as processing
sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_PROCESSING);
SENTRY_DEBUG("Marked state as PROCESSING");
#if defined(SENTRY_PLATFORM_WINDOWS)
SENTRY_DEBUGF(
"crash-daemon: processing crash pid=%lu tid=%lu mode=%d wer=%d",
(unsigned long)ctx->crashed_pid, (unsigned long)ctx->crashed_tid,
ctx->crash_reporting_mode, ctx->platform.wer_enabled ? 1 : 0);
#endif

// Check crash reporting mode
int mode = ctx->crash_reporting_mode;
Expand All @@ -3117,6 +3216,8 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
// Mode 2 (NATIVE_WITH_MINIDUMP): Write minidump
bool need_minidump = (mode == SENTRY_CRASH_REPORTING_MODE_MINIDUMP
|| mode == SENTRY_CRASH_REPORTING_MODE_NATIVE_WITH_MINIDUMP);
SENTRY_DEBUGF("crash-daemon: need_minidump=%d minidump_mode=%d",
need_minidump ? 1 : 0, ctx->minidump_mode);

// Determine if we use native stacktrace mode
// Mode 0: Use minidump-only envelope (existing behavior)
Expand Down
Loading
Loading