Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Add HTTP retry with exponential backoff, opt-in via `sentry_options_set_http_retry`. ([#1520](https://github.com/getsentry/sentry-native/pull/1520))
- Store minidump attachments as separate `.dmp` files in the offline cache for direct debugger access. ([#1607](https://github.com/getsentry/sentry-native/pull/1607))
- Enable metrics by default; metrics are now opt-out via `sentry_options_set_enable_metrics(options, false)`. ([#1609](https://github.com/getsentry/sentry-native/pull/1609))
- Android: allow Sentry.NET to preload the NDK integration to install signal handlers before the .NET runtime. ([#1613](https://github.com/getsentry/sentry-native/pull/1613))

**Fixes**:

Expand Down
11 changes: 11 additions & 0 deletions ndk/lib/api/sentry-native-ndk.api
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,16 @@ public final class io/sentry/ndk/SentryNdk {
public static fun close ()V
public static fun init (Lio/sentry/ndk/NdkOptions;)V
public static fun loadNativeLibraries ()V
public static fun preload ()V
}

public final class io/sentry/ndk/SentryNdkPreloadProvider : android/content/ContentProvider {
public fun <init> ()V
public fun delete (Landroid/net/Uri;Ljava/lang/String;[Ljava/lang/String;)I
public fun getType (Landroid/net/Uri;)Ljava/lang/String;
public fun insert (Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri;
public fun onCreate ()Z
public fun query (Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;
public fun update (Landroid/net/Uri;Landroid/content/ContentValues;Ljava/lang/String;[Ljava/lang/String;)I
}

12 changes: 12 additions & 0 deletions ndk/lib/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- initOrder must be higher than AppInitOrder-1 (1999999999) used by the .NET runtime:
https://github.com/dotnet/android/blob/3c50ff4573b5/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs#L724 -->
<provider
android:name="io.sentry.ndk.SentryNdkPreloadProvider"
android:authorities="${applicationId}.SentryNdkPreloadProvider"
android:initOrder="2000000000"
android:exported="false" />
</application>
</manifest>
8 changes: 8 additions & 0 deletions ndk/lib/src/main/java/io/sentry/ndk/SentryNdk.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public final class SentryNdk {

private SentryNdk() {}

private static native void preloadSentryNative();

/**
* Initializes sentry-native and returns 0 on success, non-zero on failure.
*
Expand All @@ -20,6 +22,12 @@ private SentryNdk() {}

private static native void shutdown();

/** Preloads the NDK integration */
public static void preload() {
loadNativeLibraries();
preloadSentryNative();
}

/**
* Init the NDK integration
*
Expand Down
82 changes: 82 additions & 0 deletions ndk/lib/src/main/java/io/sentry/ndk/SentryNdkPreloadProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.sentry.ndk;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Preloads the NDK integration before the Mono runtime. This installs and
* chains the Native SDK's signal handlers before Mono, allowing Mono to
* correctly handle managed exceptions while chaining native crashes to the
* Native SDK.
*
* <p>Enabled by setting {@code io.sentry.ndk.preload} to {@code true} in
* AndroidManifest.xml metadata. The high {@code initOrder} ensures this runs
* before {@code mono.MonoRuntimeProvider}.
*/
public final class SentryNdkPreloadProvider extends ContentProvider {

@Override
public boolean onCreate() {
final Context context = getContext();
if (context == null) {
return false;
}
try {
final ApplicationInfo info =
context
.getPackageManager()
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
final Bundle metadata = info.metaData;
if (metadata != null && metadata.getBoolean("io.sentry.ndk.preload", false)) {
android.util.Log.d("sentry", "io.sentry.ndk.preload read: true");
SentryNdk.preload();
android.util.Log.d("sentry", "SentryNdk.preload() completed");
}
} catch (Throwable e) {
android.util.Log.e("sentry", "SentryNdk.preload() failed", e);
}
return true;
}

@Override
public @Nullable Cursor query(
@NotNull Uri uri,
@Nullable String[] projection,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder) {
return null;
}

@Override
public @Nullable String getType(@NotNull Uri uri) {
return null;
}

@Override
public @Nullable Uri insert(@NotNull Uri uri, @Nullable ContentValues values) {
return null;
}

@Override
public int delete(@NotNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}

@Override
public int update(
@NotNull Uri uri,
@Nullable ContentValues values,
@Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}
}
9 changes: 9 additions & 0 deletions ndk/lib/src/main/jni/sentry.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,15 @@ static void send_envelope(sentry_envelope_t *envelope, void *data) {
sentry_envelope_free(envelope);
}

// sentry_backend.h
extern void sentry__backend_preload(void);

JNIEXPORT void JNICALL
Java_io_sentry_ndk_SentryNdk_preloadSentryNative(JNIEnv *env, jclass cls)
{
sentry__backend_preload();
}

JNIEXPORT jint JNICALL
Java_io_sentry_ndk_SentryNdk_initSentryNative(
JNIEnv *env,
Expand Down
5 changes: 5 additions & 0 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,11 @@ breakpad_backend_except(

extern "C" {

void
sentry__backend_preload(void)
{
}

sentry_backend_t *
sentry__backend_new(void)
{
Expand Down
5 changes: 5 additions & 0 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,11 @@ crashpad_backend_remove_attachment(
}
#endif

void
sentry__backend_preload(void)
{
}

sentry_backend_t *
sentry__backend_new(void)
{
Expand Down
91 changes: 65 additions & 26 deletions src/backends/sentry_backend_inproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ static volatile long g_handler_has_work = 0;
#define CRASH_STATE_DONE 2
static volatile long g_crash_handling_state = CRASH_STATE_IDLE;

#ifdef SENTRY_PLATFORM_UNIX
static volatile long g_preloaded = 0;
#endif

// trigger/schedule primitives that block the other side until this side is done
#ifdef SENTRY_PLATFORM_UNIX
static int g_handler_pipe[2] = { -1, -1 };
Expand Down Expand Up @@ -468,6 +472,37 @@ invoke_signal_handler(int signum, siginfo_t *info, void *user_context)
}
}

static int
install_signal_handlers(void)
{
if (sentry__atomic_fetch(&g_preloaded)) {
return 0;
}

memset(g_previous_handlers, 0, sizeof(g_previous_handlers));
for (size_t i = 0; i < SIGNAL_COUNT; ++i) {
if (sigaction(
SIGNAL_DEFINITIONS[i].signum, NULL, &g_previous_handlers[i])
== -1) {
return 1;
}
}

setup_sigaltstack(&g_signal_stack, "init");

sigemptyset(&g_sigaction.sa_mask);
g_sigaction.sa_sigaction = handle_signal;
// SA_NODEFER allows the signal to be delivered while the handler is
// running. This is needed for recursive crash detection to work -
// without it, a crash during crash handling would block the signal
// and leave the process in an undefined state.
g_sigaction.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
for (size_t i = 0; i < SIGNAL_COUNT; ++i) {
sigaction(SIGNAL_DEFINITIONS[i].signum, &g_sigaction, NULL);
}
return 0;
}

static int
startup_inproc_backend(
sentry_backend_t *backend, const sentry_options_t *options)
Expand Down Expand Up @@ -503,39 +538,18 @@ startup_inproc_backend(
return 1;
}

// save the old signal handlers
memset(g_previous_handlers, 0, sizeof(g_previous_handlers));
for (size_t i = 0; i < SIGNAL_COUNT; ++i) {
if (sigaction(
SIGNAL_DEFINITIONS[i].signum, NULL, &g_previous_handlers[i])
== -1) {
return 1;
}
}

setup_sigaltstack(&g_signal_stack, "init");

// install our own signal handler
sigemptyset(&g_sigaction.sa_mask);
g_sigaction.sa_sigaction = handle_signal;
// SA_NODEFER allows the signal to be delivered while the handler is
// running. This is needed for recursive crash detection to work -
// without it, a crash during crash handling would block the signal
// and leave the process in an undefined state.
g_sigaction.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
for (size_t i = 0; i < SIGNAL_COUNT; ++i) {
sigaction(SIGNAL_DEFINITIONS[i].signum, &g_sigaction, NULL);
}
return 0;
return install_signal_handlers();
}

static void
shutdown_inproc_backend(sentry_backend_t *backend)
{
stop_handler_thread();

teardown_sigaltstack(&g_signal_stack);
reset_signal_handlers();
if (!sentry__atomic_fetch(&g_preloaded)) {
teardown_sigaltstack(&g_signal_stack);
reset_signal_handlers();
}

if (backend) {
backend->data = NULL;
Expand Down Expand Up @@ -1643,6 +1657,21 @@ process_ucontext(const sentry_ucontext_t *uctx)
"multiple recursive crashes detected, bailing out");
goto cleanup;
}

// If signal handlers were installed via sentry__backend_preload() but
// sentry_init() hasn't been called yet (no handler thread), skip crash
// processing and fall through to the previous handler.
if (sentry__atomic_fetch(&g_preloaded)
&& !sentry__atomic_fetch(&g_handler_thread_ready)) {
SENTRY_SIGNAL_SAFE_LOG(
"handler thread not ready, falling through to previous handler");
reset_signal_handlers();
sentry__atomic_store(&g_preloaded, 0);
sentry__leave_signal_handler();
invoke_signal_handler(
uctx->signum, uctx->siginfo, (void *)uctx->user_context);
return;
}
#endif

if (!g_backend_config.enable_logging_when_crashed) {
Expand Down Expand Up @@ -1742,6 +1771,16 @@ handle_except(sentry_backend_t *UNUSED(backend), const sentry_ucontext_t *uctx)
process_ucontext(uctx);
}

void
sentry__backend_preload(void)
{
#ifdef SENTRY_PLATFORM_UNIX
if (install_signal_handlers() == 0) {
sentry__atomic_store(&g_preloaded, 1);
}
#endif
}

sentry_backend_t *
sentry__backend_new(void)
{
Expand Down
5 changes: 5 additions & 0 deletions src/backends/sentry_backend_native.c
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,11 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx)
}
}

void
sentry__backend_preload(void)
{
}

/**
* Create native backend
*/
Expand Down
5 changes: 5 additions & 0 deletions src/backends/sentry_backend_none.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#include "sentry_backend.h"

void
sentry__backend_preload(void)
{
}

sentry_backend_t *
sentry__backend_new(void)
{
Expand Down
13 changes: 13 additions & 0 deletions src/sentry_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ struct sentry_backend_s {
bool can_capture_after_shutdown;
};

/**
* Backend-specific pre-initialization that can be called before sentry_init().
*
* Currently only used from the NDK's SentryNdkPreloadProvider via JNI to
* preload the inproc backend on Android. This installs and chains the Native
* SDK's signal handlers before the Mono runtime, allowing Mono to correctly
* handle managed exceptions while chaining native crashes to the Native SDK.
*
* If a crash occurs before sentry_init() is called, the handler will fall
* through to the previously installed handler.
*/
SENTRY_API void sentry__backend_preload(void);

/**
* This will free a previously allocated backend.
*/
Expand Down
15 changes: 14 additions & 1 deletion tests/fixtures/dotnet_signal/Platforms/Android/MainActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,33 @@ namespace dotnet_signal;
[Activity(Name = "dotnet_signal.MainActivity", MainLauncher = true)]
public class MainActivity : Activity
{
static MainActivity()
{
Java.Lang.JavaSystem.LoadLibrary("sentry");
Java.Lang.JavaSystem.LoadLibrary("crash");
}

protected override void OnResume()
{
base.OnResume();

var arg = Intent?.GetStringExtra("arg");
var strategy = Intent?.GetStringExtra("strategy") ?? "";
var reinit = Intent?.GetStringExtra("reinit") ?? "";
if (!string.IsNullOrEmpty(arg))
{
var databasePath = FilesDir?.AbsolutePath + "/.sentry-native";
var args = new List<string> { arg };
if (!string.IsNullOrEmpty(strategy))
args.Add(strategy);
if (!string.IsNullOrEmpty(reinit))
args.Add("reinit");

// Post to the message queue so the activity finishes starting
// before the crash test runs. Without this, "am start -W" may hang.
new Handler(Looper.MainLooper!).Post(() =>
{
Program.RunTest(new[] { arg }, databasePath);
Program.RunTest(args.ToArray(), databasePath);
FinishAndRemoveTask();
Java.Lang.JavaSystem.Exit(0);
});
Expand Down
Loading
Loading