在安卓系统中,替换JNI崩溃为异常

9
我开发了一个使用本地C库的Android应用程序。 我可以成功地使用JNI编译整个项目,一切都运行顺利。然而,时不时地,本地C库会崩溃(最常见的是SIGSEGV)。这反过来会导致我的应用程序崩溃,对用户没有任何有意义的通知。我想要实现以下目标:
  1. 使用信号处理程序(sigaction)在本地代码中捕获信号,以防止随机崩溃
  2. 在C库中抛出Java可以捕获的异常
  3. 在Java中捕获异常,为用户生成有意义的警告消息并保持应用程序运行
如果这对你有用的话 - JNI代码在单独的线程中运行(更精确地说,在AsyncTask内部)。
我已经检查过http://blog.httrack.com/blog/2013/08/23/catching-posix-signals-on-android/https://github.com/xroche/coffeecatch,但我无法将其编译。

根据Best way to throw exceptions in JNI code?How to catch JNI Crashes as exceptions using Signal handling based mechanism in Java以及https://www.developer.com/java/data/exception-handling-in-jni.html的建议,我执行了以下步骤:

在我的本地代码中,我添加了以下函数(据我理解)设置一个信号处理程序:

void initializeSignalHandler(JNIEnv* env){
    int watched_signals[] = { SIGABRT, SIGILL, SIGSEGV, SIGINT, SIGKILL }; // 6, 4, 11, 2, 9
    struct sigaction sighandler;
    memset(&sighandler, 0, sizeof(sighandler));

    sighandler.sa_sigaction = &sighandler_func;
    sighandler.sa_mask = 0;
    sighandler.sa_flags = SA_SIGINFO | SA_ONSTACK;
    for(int ii=0; ii<5; ii++){
        int signal = watched_signals[ii];
        sigaction(signal, &sighandler, NULL);        
    }
    env = env;
}

我处理这些信号的函数如下:

void sighandler_func(int sig, siginfo_t* sig_info, void* ptr){
    printerr("Sighandler: ", sig);
    jclass jcls = (*env)->FindClass(env, "java/lang/Error");
    jboolean flag = (*env)->ExceptionCheck(env);
    if (flag) {
        (*env)->ExceptionClear(env);
        /* code to handle exception */
    }
    if (jcls!=NULL){
        printerr("Throwing exception");
        (*env)->ThrowNew(env, jcls, "error message");
    }
}

我的关键JNI函数从配置信号处理程序开始:

JNIEXPORT jint JNICALL Java_android_playground_criticalFuction
    (JNIEnv *env, jclass c, jlong handle, jshortArray out_buffer){


    // new signal handler
    struct sigaction sighandler;
    initializeSignalHandler(env);

    // ...here goes the critical code
}

当我的本地C代码发生SIGILL时,会出现以下情况:
1)在我的调试终端上,我会收到以下四条消息:
  • Sighandler: 4(对应SIGILL)
  • 抛出异常
  • Sighandler: 4
  • Sighandler: 11
2)应用程序窗口关闭,但我没有收到Android消息“不幸地...已关闭”,这通常是应用程序崩溃时出现的。
我真的不明白为什么我会收到第三和第四个信号消息,因为我认为异常已经被抛出。此外,我认为异常实际上从未被真正抛出(到Java)。
我很困惑,非常感谢任何帮助。

在信号处理程序中可以做的事情相当受限制。例如,在信号处理程序中使用动态内存分配(ThrowNew听起来像是会尝试分配一些内存)在C++中被认为是未定义行为。我知道这是C语言,但很可能类似的限制也适用于那里。 - Michael
请参考 https://dev59.com/3JLea4cB1Zd3GeqP35F9#34553070 获取更多信息。 - Michael
1个回答

5
我不知道你在这里尝试做的事情是否在技术上可行。@Michael的评论暗示它是不可能的。但如果确实有可能,那么这是一个坏主意。
当你使用的本地库触发SIGSEGV时,它可能会造成无法预料的损坏,例如在堆中覆盖对象等。如果你试图恢复,先前的损坏可能会导致意外行为或不正确的结果……或者GC稍后由于堆损坏而崩溃。
这就是为什么JVM在本地代码或Java代码中发生意外SIGSEGV时出现恐慌的标准行为。
虽然给用户提供有意义的错误信息很好,但如果你遇到随机的SIGSEGV错误和其他JVM恐慌,你可以告诉他们的信息是有限的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接