diff --git a/CHANGELOG.md b/CHANGELOG.md index 2710fdee4b..fd647816ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Add `on_crashed_last_run` callback for inspecting crash envelopes from previous runs. ([#1985](https://github.com/getsentry/sentry-native/pull/1985)) - Add `sentry_get_last_event_id` and `sentry_scope_get_last_event_id` for retrieving the last event ID captured with the global or given scope, respectively. ([#1992](https://github.com/getsentry/sentry-native/pull/1992)) - Native/Unix: The native crash daemon now loads `libcurl` dynamically at runtime by default when `SENTRY_LINK_CURL=AUTO`, avoiding `libcurl` linker work during process startup and significantly speeding up startup time. Explicitly set `SENTRY_LINK_CURL=ON` to link it directly. ([#1955](https://github.com/getsentry/sentry-native/pull/1955)) +- Add `sentry_crash` for deliberately crashing the current process to test its crash reporting configuration. ([#2013](https://github.com/getsentry/sentry-native/pull/2013)) **Deprecations**: diff --git a/examples/example.c b/examples/example.c index 9d166c8d36..e0ead509f3 100644 --- a/examples/example.c +++ b/examples/example.c @@ -504,36 +504,6 @@ trigger_fastfail_crash() #endif -#ifdef SENTRY_PLATFORM_AIX -// AIX has a null page mapped to the bottom of memory, which means null derefs -// don't segfault. try dereferencing the top of memory instead; the top nibble -// seems to be unusable. -static void *invalid_mem = (void *)0xFFFFFFFFFFFFFF9B; // -100 for memset -#else -static void *invalid_mem = (void *)1; -#endif - -// Detect Address Sanitizer (works for both GCC and Clang) -#if defined(__SANITIZE_ADDRESS__) -# define SENTRY_ASAN_ACTIVE 1 -#elif defined(__has_feature) -# if __has_feature(address_sanitizer) -# define SENTRY_ASAN_ACTIVE 1 -# endif -#endif - -static void -trigger_crash() -{ -#ifdef SENTRY_ASAN_ACTIVE - // Under ASAN, raise signal directly to bypass ASAN's memory interception. - // ASAN intercepts memset and would abort before our signal handler runs. - raise(SIGSEGV); -#else - memset((char *)invalid_mem, 1, 100); -#endif -} - static void trigger_stack_overflow() { @@ -1313,7 +1283,7 @@ main(int argc, char **argv) } if (has_arg(argc, argv, "crash")) { - trigger_crash(); + sentry_crash(); } if (has_arg(argc, argv, "stack-overflow")) { trigger_stack_overflow(); @@ -1522,7 +1492,7 @@ main(int argc, char **argv) } if (has_arg(argc, argv, "crash-after-shutdown")) { - trigger_crash(); + sentry_crash(); } return EXIT_SUCCESS; diff --git a/include/sentry.h b/include/sentry.h index 991f1e1840..c13b3ec2b1 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -144,16 +144,19 @@ extern "C" { #endif #if defined(__GNUC__) || defined(__clang__) +# define SENTRY_NORETURN __attribute__((noreturn)) # define SENTRY_SUPPRESS_DEPRECATED \ _Pragma("GCC diagnostic push"); \ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") # define SENTRY_RESTORE_DEPRECATED _Pragma("GCC diagnostic pop") #elif defined(_MSC_VER) +# define SENTRY_NORETURN __declspec(noreturn) # define SENTRY_SUPPRESS_DEPRECATED \ __pragma(warning(push)); \ __pragma(warning(disable : 4996)) # define SENTRY_RESTORE_DEPRECATED __pragma(warning(pop)) #else +# define SENTRY_NORETURN # define SENTRY_SUPPRESS_DEPRECATED # define SENTRY_RESTORE_DEPRECATED #endif @@ -2520,6 +2523,17 @@ SENTRY_API sentry_uuid_t sentry_capture_minidumpw_n( SENTRY_EXPERIMENTAL_API void sentry_handle_exception( const sentry_ucontext_t *uctx); +/** + * Deliberately crashes the current process. + * + * This is intended for testing that crash reporting is correctly configured. + * To capture the crash, call this only after `sentry_init` returns + * successfully. + * + * This function does not return and must not be used in production. + */ +SENTRY_EXPERIMENTAL_API SENTRY_NORETURN void sentry_crash(void); + /** * Type of the `before_breadcrumb` callback. * diff --git a/src/sentry_core.c b/src/sentry_core.c index b87e36c599..4285d070c6 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -1,6 +1,8 @@ #include "sentry_boot.h" +#include #include +#include #include #include "sentry_app_hang_latch.h" @@ -929,6 +931,37 @@ sentry_handle_exception(const sentry_ucontext_t *uctx) } } +// Detect Address Sanitizer (works for both GCC and Clang). +#if defined(__SANITIZE_ADDRESS__) +# define SENTRY_ASAN_ACTIVE 1 +#elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# define SENTRY_ASAN_ACTIVE 1 +# endif +#endif + +void +sentry_crash(void) +{ +#ifdef SENTRY_ASAN_ACTIVE + // ASAN intercepts memory writes and would abort before the crash handler + // runs, so bypass its memory instrumentation. + raise(SIGSEGV); +#else +# ifdef SENTRY_PLATFORM_AIX + // AIX maps its null page, so use an address near the top of memory. + void *volatile invalid_mem = (void *)0xFFFFFFFFFFFFFF9B; +# else + void *volatile invalid_mem = (void *)1; +# endif + memset((char *)invalid_mem, 1, 100); +#endif + + // A user-installed signal or exception handler could allow execution to + // continue. Ensure this API never returns in that case. + abort(); +} + sentry_uuid_t sentry__new_event_id(void) { diff --git a/tests/fixtures/dotnet_signal/Program.cs b/tests/fixtures/dotnet_signal/Program.cs index cf526d45c4..8334ede9b5 100644 --- a/tests/fixtures/dotnet_signal/Program.cs +++ b/tests/fixtures/dotnet_signal/Program.cs @@ -5,8 +5,8 @@ namespace dotnet_signal; class Program { - [DllImport("crash", EntryPoint = "native_crash")] - static extern void native_crash(); + [DllImport("sentry", EntryPoint = "sentry_crash")] + static extern void sentry_crash(); [DllImport("crash", EntryPoint = "enable_sigaltstack")] static extern void enable_sigaltstack(); @@ -72,7 +72,7 @@ public static void RunTest(string[] args, string? databasePath = null) if (args.Contains("native-crash")) { - native_crash(); + sentry_crash(); } else if (args.Contains("managed-exception")) { diff --git a/tests/fixtures/dotnet_signal/crash.c b/tests/fixtures/dotnet_signal/crash.c index c76fd1a4f8..3dead10d0c 100644 --- a/tests/fixtures/dotnet_signal/crash.c +++ b/tests/fixtures/dotnet_signal/crash.c @@ -1,9 +1,5 @@ #include #include -void native_crash(void) -{ - *(int *)10 = 100; -} void enable_sigaltstack(void) { diff --git a/tests/fixtures/inproc_stress/concurrent_crash.c b/tests/fixtures/inproc_stress/concurrent_crash.c index ebf621b3da..a423672618 100644 --- a/tests/fixtures/inproc_stress/concurrent_crash.c +++ b/tests/fixtures/inproc_stress/concurrent_crash.c @@ -5,9 +5,10 @@ * race conditions in the signal handler / handler thread synchronization. */ +#include + #include #include -#include #ifndef _WIN32 # include @@ -21,8 +22,6 @@ #define CRASH_THREADS 20 -static void *invalid_mem = (void *)1; - // Barrier for synchronizing threads #ifndef _WIN32 static volatile int g_barrier = 0; @@ -40,7 +39,7 @@ __declspec(noinline) void do_crash(void) { - memset((char *)invalid_mem, 1, 100); + sentry_crash(); } static void diff --git a/tests/fixtures/inproc_stress/main.c b/tests/fixtures/inproc_stress/main.c index 2ab4b0734a..00e596cdae 100644 --- a/tests/fixtures/inproc_stress/main.c +++ b/tests/fixtures/inproc_stress/main.c @@ -270,12 +270,6 @@ test_concurrent_crash(PATH_TYPE database_path) return 1; } -static void -trigger_crash(void) -{ - memset((char *)invalid_mem, 1, 100); -} - static int setup_sentry_with_crashing_on_crash(PATH_TYPE database_path) { @@ -330,7 +324,7 @@ test_handler_thread_crash(PATH_TYPE database_path) // This will crash, trigger the handler thread, which will call // on_crash callback, which will crash the handler thread. // The fallback should then process in the signal handler. - trigger_crash(); + sentry_crash(); fprintf(stderr, "ERROR: Should have crashed\n"); sentry_close(); @@ -352,7 +346,7 @@ test_handler_abort_crash(PATH_TYPE database_path) // This will crash, trigger the handler thread, which will call // on_crash callback, which will call abort(). abort() resets the // signal mask, so this tests a different code path. - trigger_crash(); + sentry_crash(); fprintf(stderr, "ERROR: Should have crashed\n"); sentry_close(); @@ -418,7 +412,7 @@ test_simple_crash(PATH_TYPE database_path) fprintf(stderr, "Starting simple crash test\n"); fflush(stderr); - trigger_crash(); + sentry_crash(); fprintf(stderr, "ERROR: Should have crashed\n"); sentry_close(); diff --git a/tests/fixtures/screenshot/screenshot_win32.c b/tests/fixtures/screenshot/screenshot_win32.c index d8ded11a39..cef9dc6e8f 100644 --- a/tests/fixtures/screenshot/screenshot_win32.c +++ b/tests/fixtures/screenshot/screenshot_win32.c @@ -4,7 +4,6 @@ #include #include #include -#include enum { IDT_TIMER_CRASH = 1, @@ -23,14 +22,6 @@ has_arg(int argc, LPWSTR *argv, LPCWSTR arg) return false; } -static void *invalid_mem = (void *)1; - -static void -trigger_crash() -{ - memset((char *)invalid_mem, 1, 100); -} - static void trigger_stack_overflow() { @@ -61,7 +52,7 @@ wnd_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_TIMER: switch (wParam) { case IDT_TIMER_CRASH: - trigger_crash(); + sentry_crash(); break; case IDT_TIMER_STACK_OVERFLOW: trigger_stack_overflow();