diff --git a/CHANGELOG.md b/CHANGELOG.md index f19a4bc2f..5e6f2992e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +**Features**: + +- Native: add Android support. ([#1725](https://github.com/getsentry/sentry-native/pull/1725)) + **Fixes**: - Protect CMAKE_SYSTEM_VERSION to avoid empty values when cross-building. ([#1720](https://github.com/getsentry/sentry-native/pull/1720)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76b237719..1f16ba746 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -841,6 +841,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}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6086dbaaf..97baa46bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,7 +194,7 @@ elseif(SENTRY_BACKEND_NATIVE) target_include_directories(sentry PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/backends/native) # Platform-specific libraries for native backend - if(LINUX OR ANDROID) + if(LINUX AND NOT ANDROID) # Linux needs pthread and rt for shared memory target_link_libraries(sentry PRIVATE pthread rt) elseif(APPLE) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index b440aa1d2..002b06016 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -3237,7 +3237,11 @@ daemon_file_logger( fflush(log_file); // Flush immediately to ensure logs are written } -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +int +sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, int notify_eventfd, + int ready_eventfd, int shm_fd) +#elif defined(SENTRY_PLATFORM_LINUX) int sentry__crash_daemon_main( pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd) @@ -3253,7 +3257,10 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, { // Initialize IPC first (attach to shared memory created by parent) // We need this to get the database path for logging -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) + sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon( + app_pid, app_tid, notify_eventfd, ready_eventfd, shm_fd); +#elif defined(SENTRY_PLATFORM_LINUX) sentry_crash_ipc_t *ipc = sentry__crash_ipc_init_daemon( app_pid, app_tid, notify_eventfd, ready_eventfd); #elif defined(SENTRY_PLATFORM_MACOS) @@ -3532,7 +3539,11 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, return 0; } -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +pid_t +sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, int notify_eventfd, + int ready_eventfd, int shm_fd, const char *handler_path) +#elif defined(SENTRY_PLATFORM_LINUX) pid_t sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd, const char *handler_path) @@ -3653,6 +3664,12 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, if (ready_flags != -1) { fcntl(ready_eventfd, F_SETFD, ready_flags & ~FD_CLOEXEC); } +# if defined(SENTRY_PLATFORM_ANDROID) + int shm_flags = fcntl(shm_fd, F_GETFD); + if (shm_flags != -1) { + fcntl(shm_fd, F_SETFD, shm_flags & ~FD_CLOEXEC); + } +# endif // Convert arguments to strings for exec char pid_str[32], tid_str[32], notify_str[32], ready_str[32]; @@ -3661,8 +3678,15 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, snprintf(notify_str, sizeof(notify_str), "%d", notify_eventfd); snprintf(ready_str, sizeof(ready_str), "%d", ready_eventfd); +# if defined(SENTRY_PLATFORM_ANDROID) + char shm_str[32]; + snprintf(shm_str, sizeof(shm_str), "%d", shm_fd); + char *argv[] = { "sentry-crash", pid_str, tid_str, notify_str, + ready_str, shm_str, NULL }; +# else char *argv[] = { "sentry-crash", pid_str, tid_str, notify_str, ready_str, NULL }; +# endif if (!sentry__string_empty(handler_path)) { execv(handler_path, argv); @@ -3830,12 +3854,13 @@ main(int argc, char **argv) { // Expected arguments: // Linux: - // macOS: -# if defined(SENTRY_PLATFORM_MACOS) + // macOS/Android: + // +# if defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) if (argc < 6) { fprintf(stderr, - "Usage: sentry-crash " - " \n"); + "Usage: sentry-crash " + " \n"); return 1; } # else @@ -3851,7 +3876,13 @@ main(int argc, char **argv) pid_t app_pid = (pid_t)strtoul(argv[1], NULL, 10); uint64_t app_tid = strtoull(argv[2], NULL, 16); -# if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +# if defined(SENTRY_PLATFORM_ANDROID) + int notify_eventfd = atoi(argv[3]); + int ready_eventfd = atoi(argv[4]); + int shm_fd_arg = atoi(argv[5]); + return sentry__crash_daemon_main( + app_pid, app_tid, notify_eventfd, ready_eventfd, shm_fd_arg); +# elif defined(SENTRY_PLATFORM_LINUX) int notify_eventfd = atoi(argv[3]); int ready_eventfd = atoi(argv[4]); return sentry__crash_daemon_main( diff --git a/src/backends/native/sentry_crash_daemon.h b/src/backends/native/sentry_crash_daemon.h index 69c93e9a2..5228b8f8a 100644 --- a/src/backends/native/sentry_crash_daemon.h +++ b/src/backends/native/sentry_crash_daemon.h @@ -24,7 +24,11 @@ struct sentry_options_s; * @param ready_handle Ready signal handle * @return Daemon PID on success, -1 on failure */ -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +pid_t sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, + int notify_eventfd, int ready_eventfd, int shm_fd, + const char *handler_path); +#elif defined(SENTRY_PLATFORM_LINUX) pid_t sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd, const char *handler_path); #elif defined(SENTRY_PLATFORM_MACOS) @@ -44,7 +48,10 @@ pid_t sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, * @param notify_handle Notification handle for crash signals * @param ready_handle Ready signal handle to signal parent */ -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +int sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, + int notify_eventfd, int ready_eventfd, int shm_fd); +#elif defined(SENTRY_PLATFORM_LINUX) int sentry__crash_daemon_main( pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd); #elif defined(SENTRY_PLATFORM_MACOS) diff --git a/src/backends/native/sentry_crash_handler.c b/src/backends/native/sentry_crash_handler.c index 6b6671d45..d39a6936c 100644 --- a/src/backends/native/sentry_crash_handler.c +++ b/src/backends/native/sentry_crash_handler.c @@ -693,9 +693,9 @@ crash_signal_handler(int signum, siginfo_t *info, void *context) // Dump daemon log for debugging (uses stdio, safe after page allocator // enabled) // Extract the shm identifier for log path construction - // macOS: shm_path = "{tmpdir}/.sentry-shm-{id}", Linux: shm_name = - // "/s-{id}" -# if defined(SENTRY_PLATFORM_MACOS) + // macOS/Android: shm_path = "{tmpdir}/.sentry-shm-{id}", Linux: + // shm_name = "/s-{id}" +# if defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) const char *shm_id_src = ipc ? ipc->shm_path : ""; # else const char *shm_id_src = ipc ? ipc->shm_name : ""; diff --git a/src/backends/native/sentry_crash_ipc.c b/src/backends/native/sentry_crash_ipc.c index c929a07d5..f404006e3 100644 --- a/src/backends/native/sentry_crash_ipc.c +++ b/src/backends/native/sentry_crash_ipc.c @@ -7,7 +7,263 @@ #include #include -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) + +# include +# include +# include +# include +# include +# include + +sentry_crash_ipc_t * +sentry__crash_ipc_init_app( + const char *database_path, sentry_mutex_t *init_mutex) +{ + if (!database_path || !database_path[0]) { + SENTRY_WARN("Android crash IPC requires a database path"); + return NULL; + } + + sentry_crash_ipc_t *ipc = SENTRY_MAKE(sentry_crash_ipc_t); + if (!ipc) { + return NULL; + } + ipc->is_daemon = false; + ipc->init_mutex = init_mutex; + ipc->shm_fd = -1; + ipc->notify_fd = -1; + ipc->ready_fd = -1; + + uint64_t tid = (uint64_t)pthread_self(); + uint32_t id = (uint32_t)((getpid() ^ (tid & 0xFFFFFFFF)) & 0xFFFFFFFF); + snprintf(ipc->shm_path, sizeof(ipc->shm_path), "%s/.sentry-shm-%08x", + database_path, id); + + if (ipc->init_mutex) { + sentry__mutex_lock(ipc->init_mutex); + } + + bool shm_exists = false; + ipc->shm_fd + = open(ipc->shm_path, O_CREAT | O_RDWR | O_EXCL | O_CLOEXEC, 0600); + if (ipc->shm_fd < 0 && errno == EEXIST) { + shm_exists = true; + ipc->shm_fd = open(ipc->shm_path, O_RDWR | O_CLOEXEC); + } + + if (ipc->shm_fd < 0) { + SENTRY_WARNF("failed to open shared memory file: %s", strerror(errno)); + goto fail; + } + + if (shm_exists) { + struct stat st; + if (fstat(ipc->shm_fd, &st) < 0) { + SENTRY_WARNF( + "failed to stat shared memory file: %s", strerror(errno)); + goto fail; + } + if (st.st_size != SENTRY_CRASH_SHM_SIZE + && ftruncate(ipc->shm_fd, SENTRY_CRASH_SHM_SIZE) < 0) { + SENTRY_WARNF("failed to resize existing shared memory file: %s", + strerror(errno)); + goto fail; + } + } else if (ftruncate(ipc->shm_fd, SENTRY_CRASH_SHM_SIZE) < 0) { + SENTRY_WARNF( + "failed to resize shared memory file: %s", strerror(errno)); + goto fail; + } + + ipc->shmem = mmap(NULL, SENTRY_CRASH_SHM_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, ipc->shm_fd, 0); + if (ipc->shmem == MAP_FAILED) { + SENTRY_WARNF("failed to map shared memory file: %s", strerror(errno)); + ipc->shmem = NULL; + goto fail; + } + + ipc->notify_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (ipc->notify_fd < 0) { + SENTRY_WARNF("failed to create eventfd: %s", strerror(errno)); + goto fail; + } + + ipc->ready_fd = eventfd(0, EFD_CLOEXEC); + if (ipc->ready_fd < 0) { + SENTRY_WARNF("failed to create ready eventfd: %s", strerror(errno)); + goto fail; + } + + if (!shm_exists) { + memset(ipc->shmem, 0, SENTRY_CRASH_SHM_SIZE); + ipc->shmem->magic = SENTRY_CRASH_MAGIC; + ipc->shmem->version = SENTRY_CRASH_VERSION; + sentry__atomic_store(&ipc->shmem->state, SENTRY_CRASH_STATE_READY); + sentry__atomic_store(&ipc->shmem->sequence, 0); + } + + if (ipc->init_mutex) { + sentry__mutex_unlock(ipc->init_mutex); + } + + SENTRY_DEBUGF("initialized crash IPC (shm=%s, notify_fd=%d)", ipc->shm_path, + ipc->notify_fd); + + return ipc; + +fail: + if (ipc->init_mutex) { + sentry__mutex_unlock(ipc->init_mutex); + } + if (ipc->shmem) { + munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE); + } + if (ipc->notify_fd >= 0) { + close(ipc->notify_fd); + } + if (ipc->ready_fd >= 0) { + close(ipc->ready_fd); + } + if (ipc->shm_fd >= 0) { + close(ipc->shm_fd); + } + if (!shm_exists && ipc->shm_path[0]) { + unlink(ipc->shm_path); + } + sentry_free(ipc); + return NULL; +} + +sentry_crash_ipc_t * +sentry__crash_ipc_init_daemon(pid_t app_pid, uint64_t app_tid, + int notify_eventfd, int ready_eventfd, int shm_fd) +{ + sentry_crash_ipc_t *ipc = SENTRY_MAKE(sentry_crash_ipc_t); + if (!ipc) { + return NULL; + } + ipc->is_daemon = true; + ipc->shm_fd = shm_fd; + + ipc->shmem = mmap(NULL, SENTRY_CRASH_SHM_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, ipc->shm_fd, 0); + if (ipc->shmem == MAP_FAILED) { + SENTRY_WARNF( + "daemon: failed to map shared memory file: %s", strerror(errno)); + ipc->shmem = NULL; + close(ipc->shm_fd); + sentry_free(ipc); + return NULL; + } + + if (ipc->shmem->magic != SENTRY_CRASH_MAGIC) { + SENTRY_WARN("daemon: invalid shared memory magic"); + munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE); + close(ipc->shm_fd); + sentry_free(ipc); + return NULL; + } + + if (ipc->shmem->database_path[0]) { + uint32_t id + = (uint32_t)((app_pid ^ (app_tid & 0xFFFFFFFF)) & 0xFFFFFFFF); + snprintf(ipc->shm_path, sizeof(ipc->shm_path), "%s/.sentry-shm-%08x", + ipc->shmem->database_path, id); + } + + ipc->notify_fd = notify_eventfd; + ipc->ready_fd = ready_eventfd; + + SENTRY_DEBUGF("daemon: attached to crash IPC (shm_fd=%d, notify_fd=%d, " + "ready_notify_fd=%d)", + shm_fd, notify_eventfd, ready_eventfd); + + return ipc; +} + +void +sentry__crash_ipc_notify(sentry_crash_ipc_t *ipc) +{ + if (!ipc || ipc->notify_fd < 0) { + return; + } + + uint64_t val = 1; + ssize_t written = write(ipc->notify_fd, &val, sizeof(val)); + (void)written; +} + +bool +sentry__crash_ipc_wait(sentry_crash_ipc_t *ipc, int timeout_ms) +{ + if (!ipc || ipc->notify_fd < 0) { + return false; + } + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(ipc->notify_fd, &readfds); + + struct timeval timeout; + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + + int ret = select(ipc->notify_fd + 1, &readfds, NULL, NULL, + timeout_ms >= 0 ? &timeout : NULL); + + if (ret > 0 && FD_ISSET(ipc->notify_fd, &readfds)) { + uint64_t val; + ssize_t result = read(ipc->notify_fd, &val, sizeof(val)); + if (result < 0) { + SENTRY_WARN("Failed to read from notify_fd"); + } + return true; + } + + return false; +} + +void +sentry__crash_ipc_unlink(sentry_crash_ipc_t *ipc) +{ + if (ipc && ipc->shm_path[0]) { + unlink(ipc->shm_path); + } +} + +void +sentry__crash_ipc_free(sentry_crash_ipc_t *ipc) +{ + if (!ipc) { + return; + } + + if (ipc->shmem) { + munmap(ipc->shmem, SENTRY_CRASH_SHM_SIZE); + } + + if (ipc->shm_fd >= 0) { + close(ipc->shm_fd); + } + + if (!ipc->is_daemon) { + sentry__crash_ipc_unlink(ipc); + } + + if (ipc->notify_fd >= 0) { + close(ipc->notify_fd); + } + + if (ipc->ready_fd >= 0) { + close(ipc->ready_fd); + } + + sentry_free(ipc); +} + +#elif defined(SENTRY_PLATFORM_LINUX) # include # include diff --git a/src/backends/native/sentry_crash_ipc.h b/src/backends/native/sentry_crash_ipc.h index ad0e4fa6f..6c075951a 100644 --- a/src/backends/native/sentry_crash_ipc.h +++ b/src/backends/native/sentry_crash_ipc.h @@ -4,7 +4,11 @@ #include "sentry_boot.h" #include "sentry_crash_context.h" -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +# include "sentry_sync.h" +# include +# include +#elif defined(SENTRY_PLATFORM_LINUX) # include # include # include @@ -28,7 +32,13 @@ typedef pid_t sentry_process_handle_t; typedef struct { sentry_crash_context_t *shmem; -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) + int shm_fd; + int notify_fd; // Eventfd for crash notifications + int ready_fd; // Eventfd for daemon ready signal + char shm_path[SENTRY_CRASH_MAX_PATH]; // File-backed shm path + sentry_mutex_t *init_mutex; // Process-wide initialization mutex +#elif defined(SENTRY_PLATFORM_LINUX) int shm_fd; int notify_fd; // Eventfd for crash notifications int ready_fd; // Eventfd for daemon ready signal @@ -60,11 +70,15 @@ typedef struct { /** * Initialize IPC for application process. * Creates shared memory and notification mechanism. + * @param database_path Android-only directory for the file-backed mapping * @param init_sem Optional semaphore for synchronizing init (can be NULL) * @param init_mutex Optional mutex for synchronizing init on Windows (can be * NULL) */ -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +sentry_crash_ipc_t *sentry__crash_ipc_init_app( + const char *database_path, sentry_mutex_t *init_mutex); +#elif defined(SENTRY_PLATFORM_LINUX) sentry_crash_ipc_t *sentry__crash_ipc_init_app(sem_t *init_sem); #elif defined(SENTRY_PLATFORM_MACOS) sentry_crash_ipc_t *sentry__crash_ipc_init_app(sentry_mutex_t *init_mutex); @@ -84,7 +98,10 @@ sentry_crash_ipc_t *sentry__crash_ipc_init_app(void); * @param ready_handle Ready signal handle inherited from parent (eventfd on * Linux, pipe fd on macOS, event on Windows) */ -#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +#if defined(SENTRY_PLATFORM_ANDROID) +sentry_crash_ipc_t *sentry__crash_ipc_init_daemon(pid_t app_pid, + uint64_t app_tid, int notify_eventfd, int ready_eventfd, int shm_fd); +#elif defined(SENTRY_PLATFORM_LINUX) sentry_crash_ipc_t *sentry__crash_ipc_init_daemon( pid_t app_pid, uint64_t app_tid, int notify_eventfd, int ready_eventfd); #elif defined(SENTRY_PLATFORM_MACOS) diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index fe1af9322..e8cd5a8b2 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -44,9 +44,10 @@ // This lives for the entire backend lifetime and is shared across all threads #if defined(SENTRY_PLATFORM_WINDOWS) static HANDLE g_ipc_mutex = NULL; -#elif defined(SENTRY_PLATFORM_MACOS) -// macOS uses a plain pthread mutex instead of named semaphores (sem_open) -// because App Sandbox blocks POSIX named semaphores. +#elif defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) +// macOS/Android use a plain pthread mutex instead of named semaphores +// (sem_open) because App Sandbox blocks POSIX named semaphores and Android has +// no shm_open. static sentry_mutex_t g_ipc_sync_mutex = SENTRY__MUTEX_INIT; #else # include @@ -54,10 +55,12 @@ static sem_t *g_ipc_init_sem = SEM_FAILED; static char g_ipc_sem_name[64] = { 0 }; #endif -// Mutex to protect IPC initialization (Windows and Linux only, not macOS/iOS) -// macOS uses g_ipc_sync_mutex directly; iOS has no out-of-process daemon. +// Mutex to protect IPC initialization (Windows and Linux only, not macOS/iOS or +// Android) macOS/Android use g_ipc_sync_mutex directly; iOS has no +// out-of-process daemon. #if defined(SENTRY_PLATFORM_WINDOWS) \ - || (!defined(SENTRY_PLATFORM_MACOS) && !defined(SENTRY_PLATFORM_IOS)) + || (!defined(SENTRY_PLATFORM_MACOS) && !defined(SENTRY_PLATFORM_ANDROID) \ + && !defined(SENTRY_PLATFORM_IOS)) # ifdef SENTRY__MUTEX_INIT_DYN SENTRY__MUTEX_INIT_DYN(g_ipc_init_mutex) # else @@ -208,9 +211,10 @@ native_backend_startup( } sentry__mutex_unlock(&g_ipc_init_mutex); -#elif defined(SENTRY_PLATFORM_MACOS) - // macOS uses a plain pthread mutex (no sem_open which is blocked by App - // Sandbox). The mutex is statically initialized - no setup needed. +#elif defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) + // macOS/Android use a plain pthread mutex (no sem_open/shm_open which is + // blocked by App Sandbox, or missing on Android). The mutex is statically + // initialized - no setup needed. (void)0; #elif !defined(SENTRY_PLATFORM_IOS) // Create process-wide IPC initialization semaphore (singleton pattern) @@ -247,6 +251,10 @@ native_backend_startup( state->ipc = sentry__crash_ipc_init_app(g_ipc_mutex); #elif defined(SENTRY_PLATFORM_IOS) state->ipc = sentry__crash_ipc_init_app(NULL); +#elif defined(SENTRY_PLATFORM_ANDROID) + state->ipc = sentry__crash_ipc_init_app( + options->database_path ? options->database_path->path : NULL, + &g_ipc_sync_mutex); #elif defined(SENTRY_PLATFORM_MACOS) state->ipc = sentry__crash_ipc_init_app(&g_ipc_sync_mutex); #else @@ -273,7 +281,7 @@ native_backend_startup( return 1; } } -#elif defined(SENTRY_PLATFORM_MACOS) +#elif defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) sentry__mutex_lock(&g_ipc_sync_mutex); #elif !defined(SENTRY_PLATFORM_IOS) if (g_ipc_init_sem && sem_wait(g_ipc_init_sem) < 0) { @@ -428,7 +436,7 @@ native_backend_startup( if (g_ipc_mutex) { ReleaseMutex(g_ipc_mutex); } -#elif defined(SENTRY_PLATFORM_MACOS) +#elif defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) sentry__mutex_unlock(&g_ipc_sync_mutex); #elif !defined(SENTRY_PLATFORM_IOS) // Release semaphore after context configuration @@ -452,7 +460,12 @@ native_backend_startup( // Pass the notification handles (eventfd/pipe on Unix, events on Windows) const char *daemon_handler_path = options->handler_path ? options->handler_path->path : NULL; -# if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) +# if defined(SENTRY_PLATFORM_ANDROID) + uint64_t tid = (uint64_t)pthread_self(); + state->daemon_pid + = sentry__crash_daemon_start(getpid(), tid, state->ipc->notify_fd, + state->ipc->ready_fd, state->ipc->shm_fd, daemon_handler_path); +# elif defined(SENTRY_PLATFORM_LINUX) uint64_t tid = (uint64_t)pthread_self(); state->daemon_pid = sentry__crash_daemon_start(getpid(), tid, state->ipc->notify_fd, state->ipc->ready_fd, daemon_handler_path); @@ -628,10 +641,10 @@ native_backend_shutdown(sentry_backend_t *backend) } } #else - // On macOS: shm_path = "{tmpdir}/.sentry-shm-{id}" + // On macOS/Android: shm_path = "{tmpdir}/.sentry-shm-{id}" // On Linux: shm_name = "/s-{id}" // In both cases, the ID follows the last '-' -# if defined(SENTRY_PLATFORM_MACOS) +# if defined(SENTRY_PLATFORM_MACOS) || defined(SENTRY_PLATFORM_ANDROID) const char *shm_id_src = state->ipc->shm_path; # else const char *shm_id_src = state->ipc->shm_name; diff --git a/tests/conditions.py b/tests/conditions.py index 4e10e4c35..f11033a01 100644 --- a/tests/conditions.py +++ b/tests/conditions.py @@ -3,7 +3,7 @@ import shutil is_aix = sys.platform == "aix" or sys.platform == "os400" -is_android = os.environ.get("ANDROID_API") +is_android = int(os.environ.get("ANDROID_API") or "0") is_x86 = os.environ.get("TEST_X86") is_arm32 = bool(os.environ.get("TEST_ARM32")) is_qemu = bool(os.environ.get("TEST_QEMU")) @@ -50,6 +50,8 @@ # Native backend works on all platforms (lightweight, no external dependencies) # It's always available - tests explicitly set SENTRY_BACKEND: native in cmake # On macOS ASAN, the signal handling conflicts with ASAN's memory interception -has_native = has_http and not (is_asan and sys.platform == "darwin") +has_native = not (is_asan and sys.platform == "darwin") and ( + not is_android or is_android >= 23 +) has_sccache = os.environ.get("USE_SCCACHE") and shutil.which("sccache") diff --git a/tests/test_integration_android.py b/tests/test_integration_android.py new file mode 100644 index 000000000..2ac8f4ebd --- /dev/null +++ b/tests/test_integration_android.py @@ -0,0 +1,62 @@ +import time + +import pytest + +from . import adb, run +from .conditions import has_native, is_android + +pytestmark = pytest.mark.skipif( + not is_android, + reason="Tests need Android", +) + + +@pytest.mark.skipif( + not has_native, + reason="Tests need the native backend enabled", +) +@pytest.mark.skipif( + 0 < is_android < 23, + reason="Android native backend test needs API 23+ (process_vm_readv)", +) +def test_native_android(cmake): + database_path = "/data/local/tmp/.sentry-native" + + def find_minidumps(timeout=10): + deadline = time.time() + timeout + last_result = None + while last_result is None or time.time() < deadline: + last_result = adb( + "shell", + f'for f in {database_path}/*.dmp; do [ -f "$f" ] && echo "$f"; done', + capture_output=True, + text=True, + check=False, + ) + if last_result.returncode == 0 and last_result.stdout.strip(): + return [ + line.strip() + for line in last_result.stdout.splitlines() + if line.strip() + ] + time.sleep(0.5) + if last_result is not None: + print(last_result.stdout) + print(last_result.stderr) + return [] + + tmp_path = cmake( + ["sentry_example"], {"SENTRY_BACKEND": "native", "SENTRY_TRANSPORT": "none"} + ) + + adb("shell", f"rm -rf {database_path}", check=True) + assert find_minidumps(timeout=0) == [] + + run(tmp_path, "sentry_example", ["log", "crash"], expect_failure=True) + + minidumps = find_minidumps() + assert minidumps, "native backend should create a minidump on Android" + + local_minidump = tmp_path / "android.dmp" + adb("pull", minidumps[0], str(local_minidump), check=True, capture_output=True) + assert local_minidump.stat().st_size > 0 diff --git a/tests/test_integration_logger.py b/tests/test_integration_logger.py index 43fe80149..7a17dbb50 100644 --- a/tests/test_integration_logger.py +++ b/tests/test_integration_logger.py @@ -127,7 +127,8 @@ def parse_logger_output(output): "native", marks=[ pytest.mark.skipif( - not has_native or is_qemu, reason="native backend not available" + not has_native or is_qemu or is_android, + reason="native backend not available", ), ], ), @@ -175,7 +176,8 @@ def test_logger_enabled_when_crashed(backend, cmake): "native", marks=[ pytest.mark.skipif( - not has_native or is_qemu, reason="native backend not available" + not has_native or is_qemu or is_android, + reason="native backend not available", ), ], ), diff --git a/tests/test_integration_macos_sandbox.py b/tests/test_integration_macos_sandbox.py index d678e905f..de090b6d9 100644 --- a/tests/test_integration_macos_sandbox.py +++ b/tests/test_integration_macos_sandbox.py @@ -18,10 +18,10 @@ from . import make_dsn, Envelope from .assertions import wait_for_file -from .conditions import has_native +from .conditions import has_native, is_android pytestmark = pytest.mark.skipif( - not (has_native and sys.platform == "darwin"), + not (has_native and sys.platform == "darwin" and not is_android), reason="macOS App Sandbox tests require native backend on macOS", ) diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index 6d0c697b7..a3563fd7d 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -26,11 +26,19 @@ wait_for_file, assert_user_feedback, ) -from .conditions import has_native, has_oom, is_kcov, is_asan, is_tsan, is_qemu +from .conditions import ( + has_http, + has_native, + has_oom, + is_kcov, + is_asan, + is_tsan, + is_qemu, +) pytestmark = pytest.mark.skipif( - not has_native or is_qemu, - reason="Tests need the native backend enabled", + not has_native or not has_http or is_qemu, + reason="Tests need the native backend and HTTP support", ) # Sanitizer builds are slower, so selected native crash tests use the same 10s