From 8b4744f9fe88225ace2d3b52452ce9181a2a0177 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 29 Oct 2026 14:01:17 +0100 Subject: [PATCH] feat(native): Support the chain-at-start handler strategy on Android Allow `SENTRY_HANDLER_STRATEGY_CHAIN_AT_START` in the native backend on Android, as in the inproc backend, so that a signal that Mono turns into a managed exception, such as a `NullReferenceException`, is not captured as a native crash. As in the inproc backend, fall back to the default strategy below API 26, where debuggerd kills the process after the chained handler runs. This prepares the native backend for Android support (#1725). --- include/sentry.h | 4 ++-- src/backends/native/sentry_crash_handler.c | 2 +- src/backends/sentry_backend_native.c | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/include/sentry.h b/include/sentry.h index c3ecc8494..d9231a51d 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -3510,8 +3510,8 @@ SENTRY_API sentry_metrics_result_t sentry_scope_capture_metric( /** * Returns the currently set strategy for the handler. * - * This option works for the `inproc` backend on `Linux` and `Android`, and for - * the `native` backend on `Linux`. + * This option works for the `inproc` and `native` backends on `Linux` and + * `Android`. * * The main use-case is when the Native SDK is used in the context of the * CLR/Mono runtimes which convert some POSIX signals into managed-code diff --git a/src/backends/native/sentry_crash_handler.c b/src/backends/native/sentry_crash_handler.c index f9a83e60c..febefcd27 100644 --- a/src/backends/native/sentry_crash_handler.c +++ b/src/backends/native/sentry_crash_handler.c @@ -403,7 +403,7 @@ crash_signal_handler(int signum, siginfo_t *info, void *context) sentry__enter_crash_handler(); ucontext_t *uctx = (ucontext_t *)context; -# if defined(SENTRY_PLATFORM_LINUX) && !defined(SENTRY_PLATFORM_ANDROID) +# if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) if (g_handler_strategy == SENTRY_HANDLER_STRATEGY_CHAIN_AT_START && signum != SIGABRT && uctx) { uintptr_t ip = sentry__ucontext_get_ip(uctx); diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 601ae7dee..0479dbc54 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -15,6 +15,9 @@ # include # include # endif +# if defined(SENTRY_PLATFORM_ANDROID) +# include +# endif #endif #include @@ -1006,10 +1009,20 @@ native_backend_startup( } sentry_handler_strategy_t strategy = -# if defined(SENTRY_PLATFORM_LINUX) && !defined(SENTRY_PLATFORM_ANDROID) +# if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) options ? sentry_options_get_handler_strategy(options) : # endif SENTRY_HANDLER_STRATEGY_DEFAULT; +# if defined(SENTRY_PLATFORM_ANDROID) + // CHAIN_AT_START invokes the previous handler and expects to regain + // control. On Android API < 26, the old debuggerd daemon kills the + // crashing process via SIGKILL after the chained handler triggers + // it, so we fall back to DEFAULT which chains at the end instead. + if (strategy == SENTRY_HANDLER_STRATEGY_CHAIN_AT_START + && android_get_device_api_level() < 26) { + strategy = SENTRY_HANDLER_STRATEGY_DEFAULT; + } +# endif if (sentry__crash_handler_init(state->ipc, strategy, NULL) < 0) { SENTRY_WARN("failed to initialize crash handler"); # if defined(SENTRY_PLATFORM_UNIX)