Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
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,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
90 changes: 89 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) {
sentry_uuid_t integrator_id
= sentry__uuid_from_native(&report_data.ReportIntegratorId);
if (memcmp(&integrator_id, report_id, sizeof(sentry_uuid_t)) == 0) {
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
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.
Outdated
}
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,16 @@ write_envelope_with_native_stacktrace(const sentry_options_t *options,
}
}

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, "application/octet-stream");
sentry__path_free(report_path);
}
}

#if defined(SENTRY_PLATFORM_UNIX)
close(fd);
#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -3288,7 +3336,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 +3349,45 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
enumerate_threads_from_process(ctx);
}
}

if (ctx->platform.wer_enabled) {
SENTRY_DEBUG("Waiting for WER");
sentry__atomic_store(&ctx->state, SENTRY_CRASH_STATE_POSTPROCESSING);

Comment thread
sentry[bot] marked this conversation as resolved.
Outdated
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
62 changes: 45 additions & 17 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,12 +127,6 @@ 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)) {
return FALSE;
Expand All @@ -131,6 +139,26 @@ process_wer_exception(
return FALSE;
}

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) {
return FALSE;
}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated

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.
Outdated
}

BOOL claimed = FALSE;
Comment thread
cursor[bot] marked this conversation as resolved.
if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING,
SENTRY_CRASH_STATE_READY)
Expand Down
13 changes: 7 additions & 6 deletions src/backends/sentry_backend_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,29 +124,29 @@ wer_unregister_module(void)
memset(&g_wer_registration, 0, sizeof(g_wer_registration));
}

static void
static bool
wer_register_module(uint64_t app_tid)
{
windows_version_t win_ver;
if (!sentry__get_windows_version(&win_ver) || win_ver.build < 19041) {
SENTRY_WARN("Native WER module not registered, because Windows "
"doesn't meet version requirements (build >= 19041).");
return;
return false;
}

sentry_path_t *wer_path = wer_default_path();
if (!wer_path || !sentry__path_is_file(wer_path)) {
SENTRY_WARN("Native WER module not found");
sentry__path_free(wer_path);
return;
return false;
}

const DWORD one = 1;
LSTATUS reg_res = wer_set_registry_value(wer_path, one);
if (reg_res != ERROR_SUCCESS) {
SENTRY_WARN("registering native WER module in registry failed");
sentry__path_free(wer_path);
return;
return false;
}

g_wer_registration.version = 1;
Expand All @@ -160,11 +160,12 @@ wer_register_module(uint64_t app_tid)
wer_delete_registry_value(wer_path);
sentry__path_free(wer_path);
memset(&g_wer_registration, 0, sizeof(g_wer_registration));
return;
return false;
}

SENTRY_DEBUGF("registered native WER module \"%s\"", wer_path->path);
g_wer_path = wer_path;
return true;
}

#endif
Expand Down Expand Up @@ -526,7 +527,7 @@ native_backend_startup(
}

# if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX)
wer_register_module(tid);
state->ipc->shmem->platform.wer_enabled = wer_register_module(tid);
# endif

if (sentry__crash_handler_init(state->ipc) < 0) {
Expand Down
37 changes: 36 additions & 1 deletion tests/test_integration_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_native_capture_crash(cmake, httpserver):
)
@pytest.mark.with_wer
@pytest.mark.parametrize("crash_arg", ["fastfail", "stack-buffer-overrun"])
def test_native_wer(cmake, httpserver, crash_arg):
def test_native_wer_crash(cmake, httpserver, crash_arg):
"""Test WER crash capture with native backend"""
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"})

Expand All @@ -108,6 +108,41 @@ def test_native_wer(cmake, httpserver, crash_arg):
envelope = Envelope.deserialize(httpserver.log[0][0].get_data())
assert_native_crash(envelope, exception_code=0xC0000409)

has_wer_report = any(
Comment thread
jpnurmi marked this conversation as resolved.
item.headers.get("type") == "attachment"
and item.headers.get("filename") == "Report.wer"
for item in envelope.items
)
assert has_wer_report, "Should include WER report"


@pytest.mark.with_wer
def test_native_wer_report(cmake, httpserver):
"""Test WER report capture with native backend"""
tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"})

httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK")

with httpserver.wait(timeout=10) as waiting:
run_crash(
tmp_path,
"sentry_example",
["log", "stdout", "crash"],
env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)),
)
assert waiting.result

assert len(httpserver.log) >= 1
envelope = Envelope.deserialize(httpserver.log[0][0].get_data())
assert_native_crash(envelope)

has_wer_report = any(
item.headers.get("type") == "attachment"
and item.headers.get("filename") == "Report.wer"
for item in envelope.items
)
assert has_wer_report, "Should include WER report"


@pytest.mark.skipif(not has_oom, reason="OOM test unreliable in this environment")
def test_native_oom(cmake, httpserver):
Expand Down
Loading