面试题十五:如何全局捕获Native异常?(待完善)

如何全局捕获Native异常?

一、面试官视角:这道题想考察什么?

  • 是否熟悉Linux的信号
  • 是否熟悉Native层任意位置获取jclass的方法
  • 是否熟悉底层线程与Java虚拟机的关系
  • 通过实现细节的考察,确认候选人的项目经验

二、题目剖析

1、如何捕获Native异常?

  • 捕获Native异常

Native异常每次都能看到signal(信号),那我们直接捕获signal就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// old_signalhandlers:类似于在Java层的全局的ExceptionHandler
static struct sigaction old_signalhandlers[NSIG];
void setUpGlobalSignalHandler() {
struct sigaction handler;
memset(&handler, 0, sizeof(struct sigaction));
handler.sa_sigaction = android_signal_handler;
handler.sa_flags = SA_RESETHAND;
// 通过调用sigaction方法,可以给信号设置一个新的处理函数,同时也可以把旧的函数保存起来,为啥要保存旧的呢?因为旧的人家里面可能有一些系统的处理,比如在某些不可恢复的错误的情况下,需要把进程给干掉,同时会把异常给抛出来。那么我们捕获了signal之后,就开始调用我们自己设置的android_signal_handler,同时在自己的signalhandler里面,再去调用人家原来的old_signalhandlers。
// CATCHSIG(×):宏
#define CATCHSIG(×) sigaction(x, &handler, &old_signalhandlers[x])
CATCHSIG(SIGQUIT);
CATCHSIG(SIGILL);
CATCHSIG(SIGABRT);
CATCHSIG(SIGBUS);
CATCHSIG(SIGFPE);
CATCHSIG(SIGSEGV);
CATCHSIG(SIGPIPE);
CATCHSIG(SIGTERM);
#undef CATCHSIG
}
  • 传递异常到Java层

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 当signal触发的时候,会调用此函数
    // signum:表示信号
    static void android_signal_handler(int signum, siginfo_t *info, void *reserved) {
    if(javaVM) {
    JNIEnvHelper jniEnvHelper;
    jclass errorHandleClass = findClass(jniEnvHelper.env, "io/github/apkok/nativec/HandleNativeError");
    if(errorHandleClass == NULL) {
    LOGE("Cannot get error handler class.");
    }else {
    jmethodID errorHandleMethod = jniEnvHelper.env -> GetStaticMethodID(errorHandleClass, "nativeErrorCallback", "(I)V");
    if(errorHandleMethod == NULL) {
    LOGE("Cannot get error handle method.");
    }else {
    LOGE("Call java back to notify a native crash!");
    jniEnvHelper.env -> CallStaticVoidMethod(errorHandleClass, errorHandleMethod, signum);
    }
    }
    }else {
    LOGE("Jni unloaded.");
    }
    old_signalhandlers[signum].sa_handler(signum);
    }
    • 捕获Native异常堆栈

      捕获Native异常需要获取到底层的堆栈

      • 设置备用栈,防止SIGSEGV因栈溢出而出现堆栈被破环
      • 创建独立线程专门用于堆栈收集并回调至Java层
      • 收集堆栈信息
        • [4.1.1, 5.0]使用内置libcorkscrew.so
        • [5.0, +∞]使用自己编译的libunwind
      • 通过线程关联Native异常对应的Java堆栈

2、如何清理Native层和Java层的资源?

3、如何为问题的排查提供支持?

三、题目结论

  • 通过捕获信号来捕获Native异常
  • 使用 JNIEnv 的 FindClass 的原理以及限制
  • Native线程绑定到 Jvm 及解绑的细节
  • Java 对象的全局引用的应用
  • Native 层调用 Java 方法