/* package ndk/jni/crash_hook.c */ /********************************************************************* * crash_hook.c * Created at: Wed 20 May 2026 14:31:55 +0200 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8 * Contributors: * - Anton Afanasyeu (2 commits, 130 lines) * - Cursor Agent (project assistant) * Digest: SHA256 3ae2b4ff82863a890f6b5a592d9dd3d93bd28afbf93817a4ecae9e5c2cf03811 **********************************************************************/ #include #include #include #include #include #include #include #include #include #include #define LOG_TAG "androidcast_crash" #define REPORT_DIR_MAX 512 #define MAX_FRAMES 64 static char g_report_dir[REPORT_DIR_MAX]; static volatile sig_atomic_t g_handling; typedef struct { void **frames; int max; int count; } backtrace_state_t; static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context, void *arg) { backtrace_state_t *state = (backtrace_state_t *) arg; uintptr_t pc = _Unwind_GetIP(context); if (pc == 0) { return _URC_NO_REASON; } if (state->count < state->max) { state->frames[state->count++] = (void *) pc; } return _URC_NO_REASON; } static int capture_backtrace(void **frames, int max) { backtrace_state_t state = {frames, max, 0}; _Unwind_Backtrace(unwind_callback, &state); return state.count; } static void write_native_report(const char *signal_name) { if (g_report_dir[0] == '\0') { return; } char path[REPORT_DIR_MAX + 64]; snprintf(path, sizeof(path), "%s/native_%d_%ld.txt", g_report_dir, (int) getpid(), (long) time(NULL)); FILE *f = fopen(path, "w"); if (!f) { return; } fprintf(f, "signal=%s\n", signal_name); fprintf(f, "pid=%d\n", (int) getpid()); void *frames[MAX_FRAMES]; int n = capture_backtrace(frames, MAX_FRAMES); for (int i = 0; i < n; i++) { Dl_info info; memset(&info, 0, sizeof(info)); if (dladdr(frames[i], &info) && info.dli_fname) { uintptr_t offset = (uintptr_t) frames[i] - (uintptr_t) info.dli_fbase; fprintf(f, "#%02d %p %s", i, frames[i], info.dli_fname); if (info.dli_sname) { fprintf(f, " %s+0x%lx", info.dli_sname, (unsigned long) ((uintptr_t) frames[i] - (uintptr_t) info.dli_saddr)); } else { fprintf(f, "+0x%lx", (unsigned long) offset); } fprintf(f, "\n"); } else { fprintf(f, "#%02d %p\n", i, frames[i]); } } fclose(f); } static void crash_signal_handler(int sig) { if (g_handling) { _exit(128 + sig); } g_handling = 1; const char *name = "UNKNOWN"; switch (sig) { case SIGSEGV: name = "SIGSEGV"; break; case SIGABRT: name = "SIGABRT"; break; case SIGBUS: name = "SIGBUS"; break; case SIGFPE: name = "SIGFPE"; break; case SIGILL: name = "SIGILL"; break; } write_native_report(name); signal(sig, SIG_DFL); raise(sig); } static void install_handlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = crash_signal_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESETHAND; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGABRT, &sa, NULL); sigaction(SIGBUS, &sa, NULL); sigaction(SIGFPE, &sa, NULL); sigaction(SIGILL, &sa, NULL); } JNIEXPORT void JNICALL Java_com_foxx_androidcast_crash_CrashNativeBridge_nativeInstallCrashHook( JNIEnv *env, jclass clazz, jstring reportDir) { (void) clazz; const char *dir = (*env)->GetStringUTFChars(env, reportDir, NULL); if (!dir) { return; } strncpy(g_report_dir, dir, REPORT_DIR_MAX - 1); g_report_dir[REPORT_DIR_MAX - 1] = '\0'; (*env)->ReleaseStringUTFChars(env, reportDir, dir); install_handlers(); __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "native crash hook installed: %s", g_report_dir); }