diff --git a/CHANGELOG.md b/CHANGELOG.md index b85251c3a6..e3de4d2a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Auto-populate `event.user.id` with a persistent per-installation UUID when no explicit user ID is set. ([#1661](https://github.com/getsentry/sentry-native/pull/1661)) +**Fixes**: + +- Native/Windows: capture fast-fail and stack buffer overrun crashes via WER. ([#1710](https://github.com/getsentry/sentry-native/pull/1710)) + ## 0.14.0 **Breaking / Important behavior changes**: diff --git a/CMakeLists.txt b/CMakeLists.txt index cf2fb45b0f..12c2ba0097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -767,6 +767,25 @@ elseif(SENTRY_BACKEND_NATIVE) # Native backend sources and configuration are in src/CMakeLists.txt # The native backend requires C11 for atomics (set in src/CMakeLists.txt) + if(WIN32) + add_library(sentry-wer SHARED + src/backends/native/sentry_wer.c + src/backends/native/sentry_wer.def + ) + target_include_directories(sentry-wer PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/backends/native + ) + target_link_libraries(sentry-wer 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) + set_property(TARGET sentry-wer PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() + sentry_add_version_resource(sentry-wer "Native WER Module") + endif() + # Build sentry-crash executable for native backend # Get all sources that were added to sentry target get_target_property(SENTRY_SOURCES sentry SOURCES) @@ -837,11 +856,25 @@ elseif(SENTRY_BACKEND_NATIVE) # Make sentry library depend on crash daemon so it's always built together add_dependencies(sentry sentry-crash) + if(WIN32) + add_dependencies(sentry sentry-wer) + endif() # Install daemon install(TARGETS sentry-crash RUNTIME DESTINATION bin ) + if(WIN32) + install(TARGETS sentry-wer + RUNTIME DESTINATION bin + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ) + if(MSVC) + sentry_install(FILES $ + DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + endif() + endif() if(DEFINED SENTRY_FOLDER) # Native backend doesn't have separate targets to organize @@ -928,6 +961,7 @@ if(SENTRY_BUILD_EXAMPLES) # to test handling SEH by-passing exceptions we need to enable the control flow guard target_compile_options(sentry_example PRIVATE $) + target_link_options(sentry_example PRIVATE $) else() # Disable all optimizations for the `sentry_example` in gcc/clang. This allows us to keep crash triggers simple. # The effects besides reproducible code-gen across compiler versions, will be negligible for build- and runtime. diff --git a/scripts/run_tests.ps1 b/scripts/run_tests.ps1 index a923e6e084..8dfaa46f85 100644 --- a/scripts/run_tests.ps1 +++ b/scripts/run_tests.ps1 @@ -13,8 +13,8 @@ param ( [string]$Keyword = "", ## Defines the number of parallel runners via pytest-xdist. This is highly experimental since tests remove database paths. (default: 1) [int]$Parallelism = 1, - ## Disables tests that require the crashpad WER module. (default: false) - [switch]$WithoutCrashpadWer = $false, + ## Disables tests that require WER modules. (default: false) + [switch]$WithoutWer = $false, ## Disables stdout/stderr capture through pytest (default: false) [switch]$DisableCapture = $false, ## Defines the maximum number of failing tests before the test session is stopped. 0 means infinite. Will not do what you expect, together with Parallelism > 1 (default: 0) @@ -44,9 +44,9 @@ if ($Parallelism -gt 1) $pytestCommand += " -n $Parallelism" } -if (-not $WithoutCrashpadWer -and -not $Unit) +if (-not $WithoutWer -and -not $Unit) { - $pytestCommand += " --with_crashpad_wer" + $pytestCommand += " --with_wer" } if ($Keyword) diff --git a/src/backends/native/minidump/sentry_minidump_windows.c b/src/backends/native/minidump/sentry_minidump_windows.c index 48c74cdf04..b56de6af9f 100644 --- a/src/backends/native/minidump/sentry_minidump_windows.c +++ b/src/backends/native/minidump/sentry_minidump_windows.c @@ -69,13 +69,27 @@ sentry__write_minidump( // Prepare exception information using original pointers from crashed // process + EXCEPTION_POINTERS local_exception_pointers = { 0 }; + EXCEPTION_RECORD local_exception_record = { 0 }; MINIDUMP_EXCEPTION_INFORMATION exception_info = { 0 }; exception_info.ThreadId = ctx->crashed_tid; - // Use original exception pointers from crashed process's address space - exception_info.ExceptionPointers = ctx->platform.exception_pointers; - // ClientPointers=TRUE tells Windows these pointers are in the target - // process - exception_info.ClientPointers = TRUE; + if (ctx->platform.exception_pointers) { + // Use original exception pointers from crashed process's address space + exception_info.ExceptionPointers = ctx->platform.exception_pointers; + // ClientPointers=TRUE tells Windows these pointers are in the target + // process + exception_info.ClientPointers = TRUE; + } else { + // WER copies exception data into shared memory, so ClientPointers must + // be false when using the copied record/context. + local_exception_record = ctx->platform.exception_record; + local_exception_record.ExceptionRecord = NULL; + local_exception_pointers.ExceptionRecord = &local_exception_record; + local_exception_pointers.ContextRecord + = (PCONTEXT)&ctx->platform.context; + exception_info.ExceptionPointers = &local_exception_pointers; + exception_info.ClientPointers = FALSE; + } // Determine minidump type based on configuration MINIDUMP_TYPE dump_type; diff --git a/src/backends/native/sentry_crash_context.h b/src/backends/native/sentry_crash_context.h index da386da9a7..01c3ebbe04 100644 --- a/src/backends/native/sentry_crash_context.h +++ b/src/backends/native/sentry_crash_context.h @@ -242,6 +242,12 @@ typedef struct { sentry_thread_context_windows_t threads[SENTRY_CRASH_MAX_THREADS]; } sentry_crash_platform_windows_t; +typedef struct { + DWORD version; + DWORD app_pid; + uint64_t app_tid; +} sentry_wer_registration_t; + # ifdef _MSC_VER # pragma warning(pop) # endif diff --git a/src/backends/native/sentry_wer.c b/src/backends/native/sentry_wer.c new file mode 100644 index 0000000000..85a0b23d19 --- /dev/null +++ b/src/backends/native/sentry_wer.c @@ -0,0 +1,229 @@ +#include "sentry_crash_context.h" + +#include +#include +#include +#include +#include + +#ifndef STATUS_FAIL_FAST_EXCEPTION +# define STATUS_FAIL_FAST_EXCEPTION ((DWORD)0xC0000602) +#endif + +#ifndef STATUS_STACK_BUFFER_OVERRUN +# define STATUS_STACK_BUFFER_OVERRUN ((DWORD)0xC0000409) +#endif + +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)) { + return FALSE; + } + + return ((const WER_RUNTIME_EXCEPTION_INFORMATION_19041 *)info)->bIsFatal; +} + +static BOOL +is_native_wer_exception(DWORD code) +{ + return code == STATUS_FAIL_FAST_EXCEPTION + || code == STATUS_STACK_BUFFER_OVERRUN; +} + +static BOOL +read_registration( + HANDLE process, PVOID context, sentry_wer_registration_t *registration) +{ + if (!process || !context || !registration) { + return FALSE; + } + + if (!ReadProcessMemory( + process, context, registration, sizeof(*registration), NULL)) { + return FALSE; + } + + return registration->version == 1 && registration->app_pid != 0; +} + +static BOOL +open_native_crash_objects(const sentry_wer_registration_t *registration, + HANDLE *mapping, HANDLE *event, sentry_crash_context_t **ctx) +{ + wchar_t shm_name[SENTRY_CRASH_IPC_NAME_SIZE]; + wchar_t event_name[SENTRY_CRASH_IPC_NAME_SIZE]; + + swprintf(shm_name, SENTRY_CRASH_IPC_NAME_SIZE, + L"Local\\SentryCrash-%lu-%llx", (unsigned long)registration->app_pid, + (unsigned long long)registration->app_tid); + swprintf(event_name, SENTRY_CRASH_IPC_NAME_SIZE, + L"Local\\SentryCrashEvent-%lu-%llx", + (unsigned long)registration->app_pid, + (unsigned long long)registration->app_tid); + + *mapping = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, shm_name); + if (!*mapping) { + return FALSE; + } + + *ctx = MapViewOfFile( + *mapping, FILE_MAP_ALL_ACCESS, 0, 0, SENTRY_CRASH_SHM_SIZE); + if (!*ctx) { + CloseHandle(*mapping); + *mapping = NULL; + return FALSE; + } + + if ((*ctx)->magic != SENTRY_CRASH_MAGIC) { + UnmapViewOfFile(*ctx); + CloseHandle(*mapping); + *ctx = NULL; + *mapping = NULL; + return FALSE; + } + + *event = OpenEventW(EVENT_MODIFY_STATE, FALSE, event_name); + if (!*event) { + UnmapViewOfFile(*ctx); + CloseHandle(*mapping); + *ctx = NULL; + *mapping = NULL; + return FALSE; + } + + return TRUE; +} + +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; + } + + sentry_wer_registration_t registration = { 0 }; + if (!read_registration(exception_info->hProcess, context, ®istration)) { + return FALSE; + } + + HANDLE mapping = NULL; + HANDLE event = NULL; + sentry_crash_context_t *ctx = NULL; + if (!open_native_crash_objects(®istration, &mapping, &event, &ctx)) { + return FALSE; + } + + BOOL claimed = FALSE; + if (InterlockedCompareExchange(&ctx->state, SENTRY_CRASH_STATE_PROCESSING, + SENTRY_CRASH_STATE_READY) + == SENTRY_CRASH_STATE_READY) { + ctx->crashed_pid = GetProcessId(exception_info->hProcess); + ctx->crashed_tid = GetThreadId(exception_info->hThread); + ctx->platform.exception_code + = exception_info->exceptionRecord.ExceptionCode; + ctx->platform.exception_record = exception_info->exceptionRecord; + ctx->platform.context = exception_info->context; + ctx->platform.exception_pointers = NULL; + ctx->platform.num_threads = 1; + ctx->platform.threads[0].thread_id = ctx->crashed_tid; + ctx->platform.threads[0].context = exception_info->context; + + InterlockedExchange(&ctx->state, SENTRY_CRASH_STATE_CRASHED); + if (SetEvent(event)) { + claimed = TRUE; + uint64_t timeout_ms = ctx->shutdown_timeout + ? ctx->shutdown_timeout + : SENTRY_CRASH_HANDLER_WAIT_TIMEOUT_MS; + for (uint64_t waited_ms = 0; waited_ms < timeout_ms; + waited_ms += SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS) { + if (InterlockedCompareExchange(&ctx->state, + SENTRY_CRASH_STATE_DONE, SENTRY_CRASH_STATE_DONE) + == SENTRY_CRASH_STATE_DONE) { + break; + } + Sleep(SENTRY_CRASH_HANDLER_POLL_INTERVAL_MS); + } + TerminateProcess(exception_info->hProcess, + exception_info->exceptionRecord.ExceptionCode); + } + } + + CloseHandle(event); + UnmapViewOfFile(ctx); + CloseHandle(mapping); + return claimed; +} + +BOOL WINAPI +DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved) +{ + (void)instance; + (void)reason; + (void)reserved; + return TRUE; +} + +HRESULT WINAPI +OutOfProcessExceptionEventCallback(PVOID context, + const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info, + BOOL *ownership_claimed, PWSTR event_name, PDWORD event_name_size, + PDWORD signature_count) +{ + (void)event_name; + (void)event_name_size; + (void)signature_count; + + *ownership_claimed = FALSE; + if (process_wer_exception(context, exception_info)) { + *ownership_claimed = TRUE; + } + return S_OK; +} + +HRESULT WINAPI +OutOfProcessExceptionEventSignatureCallback(PVOID context, + const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info, DWORD index, + PWSTR name, PDWORD name_size, PWSTR value, PDWORD value_size) +{ + (void)context; + (void)exception_info; + (void)index; + (void)name; + (void)name_size; + (void)value; + (void)value_size; + return E_FAIL; +} + +HRESULT WINAPI +OutOfProcessExceptionEventDebuggerLaunchCallback(PVOID context, + const PWER_RUNTIME_EXCEPTION_INFORMATION exception_info, + PBOOL is_custom_debugger, PWSTR debugger_launch, + PDWORD debugger_launch_size, PBOOL is_debugger_autolaunch) +{ + (void)context; + (void)exception_info; + (void)is_custom_debugger; + (void)debugger_launch; + (void)debugger_launch_size; + (void)is_debugger_autolaunch; + return E_FAIL; +} diff --git a/src/backends/native/sentry_wer.def b/src/backends/native/sentry_wer.def new file mode 100644 index 0000000000..452a14015d --- /dev/null +++ b/src/backends/native/sentry_wer.def @@ -0,0 +1,6 @@ +LIBRARY "sentry-wer.dll" + +EXPORTS + OutOfProcessExceptionEventCallback + OutOfProcessExceptionEventSignatureCallback + OutOfProcessExceptionEventDebuggerLaunchCallback diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index d9bbcf038a..b2141fb2eb 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -11,6 +11,8 @@ # if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) # include # endif +#elif defined(SENTRY_PLATFORM_WINDOWS) +# include #endif #include @@ -29,6 +31,7 @@ #include "sentry_logs.h" #include "sentry_metrics.h" #include "sentry_options.h" +#include "sentry_os.h" #include "sentry_path.h" #include "sentry_scope.h" @@ -62,6 +65,108 @@ static sentry_mutex_t g_ipc_init_mutex = SENTRY__MUTEX_INIT; # endif #endif +#if defined(SENTRY_PLATFORM_WINDOWS) +static sentry_wer_registration_t g_wer_registration = { 0 }; + +static sentry_path_t *g_wer_path = NULL; + +static LSTATUS +wer_set_registry_value(const sentry_path_t *wer_path, DWORD value) +{ + return RegSetKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\Windows Error Reporting\\" + L"RuntimeExceptionHelperModules", + wer_path->path_w, REG_DWORD, &value, sizeof(value)); +} + +static LSTATUS +wer_delete_registry_value(const sentry_path_t *wer_path) +{ + return RegDeleteKeyValueW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\Windows Error Reporting\\" + L"RuntimeExceptionHelperModules", + wer_path->path_w); +} + +static sentry_path_t * +wer_default_path(void) +{ + sentry_path_t *current_exe = sentry__path_current_exe(); + if (!current_exe) { + return NULL; + } + + sentry_path_t *exe_dir = sentry__path_dir(current_exe); + sentry__path_free(current_exe); + if (!exe_dir) { + return NULL; + } + + sentry_path_t *wer_path = sentry__path_join_str(exe_dir, "sentry-wer.dll"); + sentry__path_free(exe_dir); + return wer_path; +} + +static void +wer_unregister_module(void) +{ + if (!g_wer_path) { + return; + } + + WerUnregisterRuntimeExceptionModule( + g_wer_path->path_w, &g_wer_registration); + wer_delete_registry_value(g_wer_path); + sentry__path_free(g_wer_path); + g_wer_path = NULL; + memset(&g_wer_registration, 0, sizeof(g_wer_registration)); +} + +static void +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; + } + + 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; + } + + 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; + } + + g_wer_registration.version = 1; + g_wer_registration.app_pid = GetCurrentProcessId(); + g_wer_registration.app_tid = app_tid; + + HRESULT hr = WerRegisterRuntimeExceptionModule( + wer_path->path_w, &g_wer_registration); + if (FAILED(hr)) { + SENTRY_WARN("registering native WER module failed"); + wer_delete_registry_value(wer_path); + sentry__path_free(wer_path); + memset(&g_wer_registration, 0, sizeof(g_wer_registration)); + return; + } + + SENTRY_DEBUGF("registered native WER module \"%s\"", wer_path->path); + g_wer_path = wer_path; +} + +#endif + /** * Native backend state */ @@ -412,11 +517,16 @@ native_backend_startup( SENTRY_DEBUG("Daemon signaled ready"); } +# if defined(SENTRY_PLATFORM_WINDOWS) + wer_register_module(tid); +# endif + if (sentry__crash_handler_init(state->ipc) < 0) { SENTRY_WARN("failed to initialize crash handler"); # if defined(SENTRY_PLATFORM_UNIX) kill(state->daemon_pid, SIGTERM); # elif defined(SENTRY_PLATFORM_WINDOWS) + wer_unregister_module(); // On Windows, terminate the daemon process HANDLE hDaemon = OpenProcess(PROCESS_TERMINATE, FALSE, state->daemon_pid); @@ -446,6 +556,10 @@ native_backend_shutdown(sentry_backend_t *backend) return; } +#if defined(SENTRY_PLATFORM_WINDOWS) + wer_unregister_module(); +#endif + // Shutdown crash handlers (signal handlers on Linux/macOS, Mach exception // handler on iOS) sentry__crash_handler_shutdown(); diff --git a/tests/assertions.py b/tests/assertions.py index 7710b25e0a..ba77b400d4 100644 --- a/tests/assertions.py +++ b/tests/assertions.py @@ -423,6 +423,20 @@ def assert_inproc_crash(envelope): assert_stacktrace(envelope, inside_exception=True, check_size=False) +def assert_native_crash(envelope, exception_code=None): + event = envelope.get_event() + assert event is not None + assert_matches(event, {"level": "fatal"}) + + exc = event["exception"]["values"][0] + assert exc["mechanism"]["type"] == "signalhandler" + assert exc["mechanism"]["handled"] is False + if exception_code is not None: + assert exc["mechanism"]["meta"]["signal"]["number"] == exception_code + assert "stacktrace" in exc + assert len(exc["stacktrace"]["frames"]) > 0 + + def assert_crash_timestamp(has_files, tmp_path): # The crash file should survive a `sentry_init` and should still be there # even after restarts. diff --git a/tests/cmake.py b/tests/cmake.py index 9d043b10bd..350e4570bb 100644 --- a/tests/cmake.py +++ b/tests/cmake.py @@ -271,6 +271,7 @@ def cmake_build(cwd, targets, options): from tests.win_utils import check_binary_version check_binary_version(Path(cwd) / "sentry.dll") + check_binary_version(Path(cwd) / "sentry-wer.dll") check_binary_version(Path(cwd) / "crashpad_wer.dll") check_binary_version(Path(cwd) / "crashpad_handler.exe") diff --git a/tests/conftest.py b/tests/conftest.py index 6582737c2d..56e27eaff7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -104,9 +104,9 @@ def _record_test_start(): def pytest_addoption(parser): parser.addoption( - "--with_crashpad_wer", + "--with_wer", action="store_true", - help="Enables tests for the crashpad WER module on Windows", + help="Enables tests for the native and crashpad WER modules on Windows", ) parser.addoption( "--benchmark_out", @@ -116,16 +116,14 @@ def pytest_addoption(parser): def pytest_runtest_setup(item): - if "with_crashpad_wer" in item.keywords and not item.config.getoption( - "--with_crashpad_wer" - ): - pytest.skip("need --with_crashpad_wer to run this test") + if "with_wer" in item.keywords and not item.config.getoption("--with_wer"): + pytest.skip("need --with_wer to run this test") def pytest_configure(config): config.addinivalue_line( "markers", - "with_crashpad_wer: mark test to only run when WER testing is enabled", + "with_wer: mark test to only run when WER testing is enabled", ) diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index c91724ee65..871b3e3232 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -285,8 +285,8 @@ def wait_for_no_werfault(timeout=30.0, poll_interval=0.5): reason="Test covers Windows-specific crashes which can only be covered via the Crashpad WER module", ) # this test currently can't run on CI because the Windows-image doesn't properly support WER, if you want to run the -# test locally, invoke pytest with the --with_crashpad_wer option which is matched with this marker in the runtest setup -@pytest.mark.with_crashpad_wer +# test locally, invoke pytest with the --with_wer option which is matched with this marker in the runtest setup +@pytest.mark.with_wer @pytest.mark.parametrize( "run_args", [ @@ -803,8 +803,8 @@ def test_crashpad_external_crash_reporter(cmake, httpserver, run_args): reason="Test covers Windows-specific crashes which can only be covered via the Crashpad WER module", ) # this test currently can't run on CI because the Windows-image doesn't properly support WER, if you want to run the -# test locally, invoke pytest with the --with_crashpad_wer option which is matched with this marker in the runtest setup -@pytest.mark.with_crashpad_wer +# test locally, invoke pytest with the --with_wer option which is matched with this marker in the runtest setup +@pytest.mark.with_wer @pytest.mark.parametrize( "run_args", [ diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index 0267d294c1..86389c9287 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -21,6 +21,7 @@ from .assertions import ( assert_breadcrumb, assert_meta, + assert_native_crash, assert_session, wait_for_file, assert_user_feedback, @@ -87,6 +88,32 @@ def test_native_capture_crash(cmake, httpserver): assert waiting.result +@pytest.mark.skipif( + sys.platform != "win32" or bool(os.environ.get("TEST_MINGW")), + reason="WER crash tests are only available in MSVC Windows builds", +) +@pytest.mark.with_wer +@pytest.mark.parametrize("crash_arg", ["fastfail", "stack-buffer-overrun"]) +def test_native_wer(cmake, httpserver, crash_arg): + """Test WER crash 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_arg], + 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, exception_code=0xC0000409) + + @pytest.mark.skipif(not has_oom, reason="OOM test unreliable in this environment") def test_native_oom(cmake, httpserver): """Test OOM crash capture with native backend""" diff --git a/tests/test_integration_screenshot.py b/tests/test_integration_screenshot.py index ad6c5472cd..450ae006ed 100644 --- a/tests/test_integration_screenshot.py +++ b/tests/test_integration_screenshot.py @@ -125,8 +125,8 @@ def test_capture_screenshot_crashpad(cmake, httpserver, run_args): reason="Screenshots are only supported on Windows", ) # this test currently can't run on CI because the Windows-image doesn't properly support WER, if you want to run the -# test locally, invoke pytest with the --with_crashpad_wer option which is matched with this marker in the runtest setup -@pytest.mark.with_crashpad_wer +# test locally, invoke pytest with the --with_wer option which is matched with this marker in the runtest setup +@pytest.mark.with_wer @pytest.mark.parametrize( "run_args", [