Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
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