Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d7983aa
feat(native): Add Android support
jpnurmi May 15, 2026
92d076e
fix(native): Avoid leaking Android daemon IPC fds
jpnurmi May 15, 2026
25beed6
Update CHANGELOG.md
jpnurmi May 15, 2026
bdb7cbf
Fix skipifs
jpnurmi May 15, 2026
b984030
fix(native): Remove stale Android daemon parameter casts
jpnurmi May 15, 2026
0930ddb
default is_android to 0 instead of None
jpnurmi May 15, 2026
2c9a5d9
fix(native): Require Android IPC database path
jpnurmi May 15, 2026
c499043
test: Handle empty Android API in conditions
jpnurmi May 15, 2026
9c5c7df
skip mac sandbox tests on android
jpnurmi May 15, 2026
03a811f
WIP
jpnurmi May 19, 2026
11f94c3
curl
jpnurmi May 19, 2026
a410bf0
clean up
jpnurmi May 19, 2026
3e9a856
Merge remote-tracking branch 'upstream/master' into jpnurmi/feat/nati…
jpnurmi May 19, 2026
f823609
fix warnings
jpnurmi May 19, 2026
9ffa097
fix(android): Unwind native daemon crashes with libunwindstack
jpnurmi May 19, 2026
bbe8817
fix(android): Preserve previous native signal handlers
jpnurmi May 19, 2026
1251b9a
fix(android): Queue native crashes for tombstone merging
jpnurmi May 19, 2026
71655dc
fix(android): Use native crash reporting without minidumps
jpnurmi May 19, 2026
1ecaa52
feat(android): Add native backend preload support
jpnurmi May 20, 2026
f6b5caf
fix(android): Respect tombstone setting in NDK crash mode
jpnurmi May 20, 2026
0d6923e
test(android): Clean native test database after crash
jpnurmi May 20, 2026
57004b7
feat(transport): Add stdout transport
jpnurmi May 20, 2026
6460208
WIP: tombstone merge support
jpnurmi May 20, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

**Features**:

- Native: add Android support. ([#1725](https://github.com/getsentry/sentry-native/pull/1725))

**Fixes**:

- Reject overly deep msgpack payloads during deserialization. ([#1727](https://github.com/getsentry/sentry-native/pull/1727))
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ elseif(SENTRY_TRANSPORT STREQUAL "curl")
set(SENTRY_TRANSPORT_CURL TRUE)
elseif(SENTRY_TRANSPORT STREQUAL "none")
set(SENTRY_TRANSPORT_NONE TRUE)
elseif(SENTRY_TRANSPORT STREQUAL "stdout")
set(SENTRY_TRANSPORT_STDOUT TRUE)
elseif(SENTRY_TRANSPORT STREQUAL "pshttp")
# Not implemented here, but in the downstream SDK
if(NOT PROSPERO)
Expand Down Expand Up @@ -841,6 +843,11 @@ elseif(SENTRY_BACKEND_NATIVE)
${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/backends/native
)
if(SENTRY_WITH_LIBUNWINDSTACK)
target_include_directories(sentry-crash PRIVATE
${PROJECT_SOURCE_DIR}/external/libunwindstack-ndk/include
)
endif()

# Link same libraries as sentry
target_link_libraries(sentry-crash PRIVATE ${_SENTRY_PLATFORM_LIBS})
Expand Down
3 changes: 3 additions & 0 deletions ndk/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ systemProp.org.gradle.internal.http.socketTimeout=120000
android.nonTransitiveRClass=true

android.suppressUnsupportedCompileSdk=34

# TODO: clean up
sentryBackend=native
Comment thread
cursor[bot] marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POC debug configuration committed in gradle properties

Medium Severity

The sentryBackend=native property in gradle.properties with a # TODO: clean up comment overrides the default "inproc" backend for all NDK library builds. This POC-only configuration changes the default behavior for every consumer of the NDK module. Similarly, the unconditional implementation("io.github.vvb2060.ndk:curl:8.18.0") dependency (with its own # TODO) bloats the AAR for non-native-backend builds. Both TODO comments indicate these are temporary and not meant to be permanent.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3e9a856. Configure here.

18 changes: 18 additions & 0 deletions ndk/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,27 @@ endif()
set(BUILD_SHARED_LIBS ON)
set(SENTRY_BUILD_SHARED_LIBS ON)

if(SENTRY_BACKEND STREQUAL "native")
find_package(curl REQUIRED CONFIG)
if(NOT TARGET CURL::libcurl)
add_library(CURL::libcurl INTERFACE IMPORTED)
target_link_libraries(CURL::libcurl INTERFACE curl::curl_static)
endif()
set(SENTRY_TRANSPORT "curl" CACHE STRING "" FORCE)
endif()

# Adding sentry-native project
add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build)

if(SENTRY_BACKEND STREQUAL "native")
set_target_properties(sentry-crash PROPERTIES
PREFIX "lib"
OUTPUT_NAME "sentry-crash"
SUFFIX ".so"
)
target_link_options(sentry-crash PRIVATE "-Wl,-z,max-page-size=16384")
endif()

# Android logging library
find_library(LOG_LIB log)

Expand Down
2 changes: 2 additions & 0 deletions ndk/lib/api/sentry-native-ndk.api
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ public final class io/sentry/ndk/NdkOptions {
public fun getSdkName ()Ljava/lang/String;
public fun getTracesSampleRate ()F
public fun isDebug ()Z
public fun isTombstoneEnabled ()Z
public fun setNdkHandlerStrategy (Lio/sentry/ndk/NdkHandlerStrategy;)V
public fun setTombstoneEnabled (Z)V
public fun setTracesSampleRate (F)V
}

Expand Down
12 changes: 10 additions & 2 deletions ndk/lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
}

var sentryNativeSrc: String = "${project.projectDir}/../.."
val sentryBackend = providers.gradleProperty("sentryBackend").orElse("inproc").get()

android {
compileSdk = 35
Expand All @@ -18,6 +19,7 @@ android {
externalNativeBuild {
cmake {
arguments.add(0, "-DANDROID_STL=c++_static")
arguments.add(0, "-DSENTRY_BACKEND=$sentryBackend")
arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc")
}
}
Expand Down Expand Up @@ -51,6 +53,7 @@ android {
}

buildFeatures {
prefab = true
prefabPublishing = true
buildConfig = true
}
Expand Down Expand Up @@ -100,6 +103,9 @@ android {
}

dependencies {
// TODO: this was the first match on maven central..
implementation("io.github.vvb2060.ndk:curl:8.18.0")

compileOnly("org.jetbrains:annotations:23.0.0")

testImplementation("androidx.test.ext:junit:1.3.0")
Expand Down Expand Up @@ -137,10 +143,12 @@ dependencies {
*
*/
afterEvaluate {
tasks.getByName("prefabReleasePackage") {
tasks.matching { it.name.startsWith("prefab") && it.name.endsWith("Package") }.configureEach {
doLast {
project.fileTree("build/intermediates/prefab_package/") {
project.fileTree("build/intermediates/") {
include("**/abi.json")
include("**/prefab_publication.json/debug")
include("**/prefab_publication.json/release")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Malformed glob patterns will never match files

Medium Severity

The include patterns **/prefab_publication.json/debug and **/prefab_publication.json/release treat prefab_publication.json as a directory containing files named debug/release. Since prefab_publication.json is a regular file (not a directory), these Ant-style glob patterns will never match anything. The c++_staticnone replacement intended for those files silently won't happen, which can cause STL-related linking issues for AAR consumers. The patterns likely need to be **/prefab_publication/debug/** and **/prefab_publication/release/**, or simply **/prefab_publication.json.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 03a811f. Configure here.

}.forEach { file ->
file.writeText(file.readText().replace("c++_static", "none"))
}
Expand Down
9 changes: 9 additions & 0 deletions ndk/lib/src/main/java/io/sentry/ndk/NdkOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public final class NdkOptions {
private NdkHandlerStrategy ndkHandlerStrategy =
NdkHandlerStrategy.SENTRY_HANDLER_STRATEGY_DEFAULT;
private float tracesSampleRate = 0;
private boolean tombstoneEnabled = false;

public NdkOptions(
@NotNull String dsn,
Expand Down Expand Up @@ -88,4 +89,12 @@ public void setTracesSampleRate(final float tracesSampleRate) {
public float getTracesSampleRate() {
return tracesSampleRate;
}

public void setTombstoneEnabled(final boolean tombstoneEnabled) {
this.tombstoneEnabled = tombstoneEnabled;
}

public boolean isTombstoneEnabled() {
return tombstoneEnabled;
}
}
34 changes: 34 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 @@ -2,6 +2,7 @@

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class SentryNdk {
Expand All @@ -20,6 +21,16 @@ private SentryNdk() {}
*/
private static native int initSentryNative(@NotNull final NdkOptions options);

private static native void nativeInitCrashDaemon(
@NotNull String shmPath, int notifyFd, int readyFd);

private static native @Nullable String nativeRunCrashDaemon(
int appPid, long appTid, int notifyFd, int readyFd, @NotNull String shmPath);

private static native boolean nativeSendEnvelope(@NotNull String path, long timeout);

private static native void nativeCloseCrashDaemon();

private static native void shutdown();

/**
Expand Down Expand Up @@ -57,6 +68,28 @@ public static void init(@NotNull final NdkOptions options) {
}
}

public static void initCrashDaemon(
@NotNull String shmPath, int notifyFd, int readyFd) {
loadNativeLibraries();
nativeInitCrashDaemon(shmPath, notifyFd, readyFd);
}

public static @Nullable String runCrashDaemon(
int appPid, long appTid, int notifyFd, int readyFd, @NotNull String shmPath) {
loadNativeLibraries();
return nativeRunCrashDaemon(appPid, appTid, notifyFd, readyFd, shmPath);
}

public static boolean sendEnvelope(@NotNull String path, long timeout) {
loadNativeLibraries();
return nativeSendEnvelope(path, timeout);
}

public static void closeCrashDaemon() {
loadNativeLibraries();
nativeCloseCrashDaemon();
}

/** Closes the NDK integration */
public static void close() {
loadNativeLibraries();
Expand All @@ -80,4 +113,5 @@ public static synchronized void loadNativeLibraries() {
nativeLibrariesLoaded = true;
}
}

}
111 changes: 111 additions & 0 deletions ndk/lib/src/main/jni/sentry.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <sentry.h>
#include <jni.h>

extern void sentry_android_crash_daemon_init(
const char *shm_path, int notify_fd, int ready_fd) __attribute__((weak));
extern char *sentry_android_crash_daemon_run(int app_pid,
uint64_t app_tid, int notify_fd, int ready_fd,
const char *shm_path) __attribute__((weak));
extern bool sentry_android_crash_daemon_send(
const char *path, uint64_t timeout) __attribute__((weak));
extern void sentry_android_crash_daemon_close(void) __attribute__((weak));

#define ENSURE(Expr) \
if (!(Expr)) \
return
Expand Down Expand Up @@ -333,6 +343,7 @@ Java_io_sentry_ndk_SentryNdk_initSentryNative(
jmethodID handler_strategy_mid = (*env)->GetMethodID(env, options_cls, "getNdkHandlerStrategy", "()I");

jmethodID traces_sample_rate_mid = (*env)->GetMethodID(env, options_cls, "getTracesSampleRate", "()F");
jmethodID tombstone_enabled_mid = (*env)->GetMethodID(env, options_cls, "isTombstoneEnabled", "()Z");

(*env)->DeleteLocalRef(env, options_cls);

Expand All @@ -350,6 +361,15 @@ Java_io_sentry_ndk_SentryNdk_initSentryNative(
options = sentry_options_new();
ENSURE_OR_FAIL(options);

// Minidumps take precedence over native envelopes during processing. When
// tombstone merging is enabled, use native-only mode so the native envelope
// can be enriched; otherwise attach a minidump for extra crash context.
jboolean tombstone_enabled =
(jboolean)(*env)->CallBooleanMethod(env, sentry_ndk_options, tombstone_enabled_mid);
sentry_options_set_crash_reporting_mode(options,
tombstone_enabled ? SENTRY_CRASH_REPORTING_MODE_NATIVE
: SENTRY_CRASH_REPORTING_MODE_NATIVE_WITH_MINIDUMP);

// session tracking is enabled by default, but the Android SDK already handles it
sentry_options_set_auto_session_tracking(options, 0);

Expand Down Expand Up @@ -433,6 +453,97 @@ Java_io_sentry_ndk_SentryNdk_initSentryNative(
return (jint) -1;
}

JNIEXPORT void JNICALL
Java_io_sentry_ndk_SentryNdk_nativeInitCrashDaemon(
JNIEnv *env,
jclass cls,
jstring shm_path,
jint notify_fd,
jint ready_fd) {
if (!shm_path) {
return;
}

const char *shm_path_chars = (*env)->GetStringUTFChars(env, shm_path, 0);
if (!shm_path_chars) {
return;
}

if (sentry_android_crash_daemon_init) {
sentry_android_crash_daemon_init(
shm_path_chars, (int) notify_fd, (int) ready_fd);
}

(*env)->ReleaseStringUTFChars(env, shm_path, shm_path_chars);
}

JNIEXPORT jstring JNICALL
Java_io_sentry_ndk_SentryNdk_nativeRunCrashDaemon(
JNIEnv *env,
jclass cls,
jint app_pid,
jlong app_tid,
jint notify_fd,
jint ready_fd,
jstring shm_path) {
if (!shm_path) {
return NULL;
}

const char *shm_path_chars = (*env)->GetStringUTFChars(env, shm_path, 0);
if (!shm_path_chars) {
return NULL;
}

char *envelope_path = NULL;
if (sentry_android_crash_daemon_run) {
envelope_path = sentry_android_crash_daemon_run((int) app_pid,
(uint64_t) app_tid, (int) notify_fd, (int) ready_fd,
shm_path_chars);
}

(*env)->ReleaseStringUTFChars(env, shm_path, shm_path_chars);
if (!envelope_path) {
return NULL;
}

jstring rv = (*env)->NewStringUTF(env, envelope_path);
sentry_free(envelope_path);
return rv;
}

JNIEXPORT jboolean JNICALL
Java_io_sentry_ndk_SentryNdk_nativeSendEnvelope(
JNIEnv *env,
jclass cls,
jstring path,
jlong timeout) {
if (!path) {
return JNI_FALSE;
}

const char *path_chars = (*env)->GetStringUTFChars(env, path, 0);
if (!path_chars) {
return JNI_FALSE;
}

bool sent = false;
if (sentry_android_crash_daemon_send) {
sent = sentry_android_crash_daemon_send(
path_chars, (uint64_t) timeout);
}

(*env)->ReleaseStringUTFChars(env, path, path_chars);
return sent ? JNI_TRUE : JNI_FALSE;
}

JNIEXPORT void JNICALL
Java_io_sentry_ndk_SentryNdk_nativeCloseCrashDaemon(JNIEnv *env, jclass cls) {
if (sentry_android_crash_daemon_close) {
sentry_android_crash_daemon_close();
}
}

JNIEXPORT void JNICALL
Java_io_sentry_ndk_NativeModuleListLoader_nativeClearModuleList(JNIEnv *env, jclass cls) {
sentry_clear_modulecache();
Expand Down
6 changes: 3 additions & 3 deletions ndk/sample/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ set(SENTRY_BUILD_SHARED_LIBS ON)

add_library(ndk-sample SHARED src/main/cpp/ndk-sample.cpp)

# Adding sentry-native project
add_subdirectory(${SENTRY_NATIVE_SRC} sentry_build)
# Use sentry-native from the NDK library's Prefab package
find_package(sentry-native-ndk REQUIRED CONFIG)

# Android logging library
find_library(LOG_LIB log)

target_link_libraries(ndk-sample PRIVATE
${LOG_LIB}
$<BUILD_INTERFACE:sentry::sentry>
sentry-native-ndk::sentry
)
# Support 16KB page sizes
target_link_options(ndk-sample PRIVATE "-Wl,-z,max-page-size=16384")
8 changes: 4 additions & 4 deletions ndk/sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ plugins {
kotlin("android")
}

var sentryNativeSrc: String = "${project.projectDir}/../.."

android {
compileSdk = 35
namespace = "io.sentry.ndk.sample"
buildFeatures.buildConfig = true
buildFeatures {
buildConfig = true
prefab = true
}

defaultConfig {
applicationId = "io.sentry.ndk.sample"
Expand All @@ -20,7 +21,6 @@ android {
externalNativeBuild {
cmake {
arguments.add(0, "-DANDROID_STL=c++_shared")
arguments.add(0, "-DSENTRY_NATIVE_SRC=$sentryNativeSrc")
}
}

Expand Down
Loading
Loading