NDK开发:JNI使用sigaction以及sigsetjmp的方式捕获以及处理异常

2 篇文章 0 订阅
1 篇文章 0 订阅

JNI的异常,会在Linux系统层发生崩溃的时候发送一些特定的信号,通过捕捉这些特定的信号来避免Jni 异常崩溃的发生,并回调到JAVA层去处理这个异常

项目中Native层的结构体有点庞大和复杂,在和java对象进行互相传递的过程中,如果对内存的使用不当(开辟新地址以及释放地址等)容易出现内存地址使用异常的情况,从而导致很匪夷所思的地方出现崩溃,如一下异常日志:

2021-07-20 10:41:09.868 3868-3968/? A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x7f93e00000 in tid 3968 (Thread-13)
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: Build fingerprint: 'Android/msm8953_64/msm8953_64:7.1.2/N2G47H/root05281558:userdebug/JL-fly-keys-V1.0.2'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: Revision: '0'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: ABI: 'arm64'
2021-07-20 10:41:09.974 4270-4270/? A/DEBUG: pid: 3868, tid: 3968, name: Thread-13  >>> com.medlander.b40tablet <<<
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x7f93e00000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x0   0000007f93dfff10  x1   0000007f940e7270  x2   0000007f94000000  x3   0000000000000000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x4   00000000000000e7  x5   0000000071a62432  x6   0000000012ff7864  x7   0000007f93929e94
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x8   0000007f91afebb4  x9   0000000000001088  x10  0000000000001108  x11  0000000000001158
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x12  0000000000000007  x13  0000000000000008  x14  0000000000000000  x15  00000000fff10000
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x16  0000007fb63875a0  x17  0000007fb632ebb4  x18  0000000000000000  x19  0000007f93c29b00
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x20  0000000000000000  x21  0000000000000003  x22  0000000012fe0640  x23  0000000012fdaeb0
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x24  0000000000000000  x25  0000000012fe05e0  x26  0000007f9392b4cc  x27  0000007f9392b4d4
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     x28  0000007f9392b4d0  x29  0000007f9392b380  x30  0000007fb1513550
2021-07-20 10:41:09.975 4270-4270/? A/DEBUG:     sp   0000007f9392b260  pc   0000007fb15135fc  pstate 0000000020000000
2021-07-20 10:41:09.978 4270-4270/? A/DEBUG: backtrace:
2021-07-20 10:41:09.979 4270-4270/? A/DEBUG:     #00 pc 000000000001a5fc  /data/app/com.medlander.b40tablet-1/lib/arm64/libmedlander-lib_V1.1.so (Java_android_1medlander_1lib_1api_course_CommonCourseDetailsAddress_setCourseItemNew+564)
2021-07-20 10:41:09.979 4270-4270/? A/DEBUG:     #01 pc 000000000113a254  /data/app/com.medlander.b40tablet-1/oat/arm64/base.odex (offset 0x110a000)

根本很难定位问题具体出现在哪里 ,这时候我们可以捕获这个异常信号signal 11 (SIGSEGV)来处理,这里使用sigaction以及sigsetjmp结合的方式来检测和捕获。

 随便自定义一个Exception

package com.medlander.candjavaobject;

/**
 * FileName: JniException
 * Author: Jokey
 * Date: 2021/7/23 15:38
 * Description: 描述
 * Version 版本号
 */
public class JniException extends Exception {

    public JniException() {
        super();
    }

    public JniException(String message) {
        super(message);
    }
}

Mainactivity中的关键代码

 btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    add();
                } catch (JniException e) {
                    e.printStackTrace();
                }
            }
        });

Native实现add方法

jint add() {
    char *a = NULL;
    int v1 = a[1] - '0';
    __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "v1 %d", v1);
    char *b = NULL;
    int v2 = b[1] - '0';
    __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "v2 %d", v2);

    return v1 + v2;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_medlander_candjavaobject_MainActivity_add(JNIEnv *env, jobject thiz) {

    // 代码跳转锚点
    int flag = sigsetjmp(JUMP_ANCHOR, 1);
    __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "flag = %d", flag);
    if (flag != 0) {
        __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "sigsetjmp != 0");
        env->ExceptionClear();
        jclass cls = env->FindClass("com/medlander/candjavaobject/JniException");
        /* 如果这个异常类没有找到,VM会抛出一个NowClassDefFoundError异常 */
        if (cls != NULL) {
            env->ThrowNew(cls, "异常了");  // 抛出指定名字的异常
        }
        /* 释放局部引用 */
        env->DeleteLocalRef(cls);
        return -1;
    }
    // 注册要捕捉的系统信号量
    struct sigaction sigact;
    struct sigaction old_action;
    sigaction(SIGINT, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigset_t block_mask;
        sigemptyset(&block_mask);
        sigaddset(&block_mask, SIGABRT); // handler处理捕捉到的信号量时,需要阻塞的信号
        sigaddset(&block_mask, SIGSEGV); // handler处理捕捉到的信号量时,需要阻塞的信号

        sigemptyset(&sigact.sa_mask);
        sigact.sa_flags = 0;
        sigact.sa_mask = block_mask;
        sigact.sa_handler = exception_handler;
        sigaction(SIGINT, &sigact, NULL); // 注册要捕捉的信号
        sigaction(SIGABRT, &sigact, NULL); // 注册要捕捉的信号
        sigaction(SIGSEGV, &sigact, NULL); // 注册要捕捉的信号
    }

    int sum =  add();
    __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "sum %d", sum);
    return sum;
}
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
/*
jni捕获异常的方法之二:捕捉系统崩溃信号,适用于代码量大的情况。
*/

// 定义代码跳转锚点
sigjmp_buf JUMP_ANCHOR;
volatile sig_atomic_t error_cnt = 0;

void exception_handler(int errorCode) {

    error_cnt += 1;
    __android_log_print(ANDROID_LOG_INFO, "jni_jokey", "JNI_ERROR, error code %d, cnt %d",
                        errorCode, error_cnt);

    // DO SOME CLEAN STAFF HERE...

    // jump to main function to do exception process
    siglongjmp(JUMP_ANCHOR, 1);

}

很明显add()会报异常崩溃,我们在native开头注册了捕捉的信号,当异常发生时会跳转到exception_handler,再通过siglongjmp跳转到sigsetjmp段,进行处理,记得env->ExceptionClear();来清除异常阻止崩溃,然后env->ThrowNew(cls, "异常了");来抛给Java处理

注意: sigsetjmp(JUMP_ANCHOR, 1);的返回值就是siglongjmp(JUMP_ANCHOR, 1);的第二个参数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值