【我的Android进阶之旅】Android使用JNI的时候报native crash: A/libc: Fatal signal 4 (SIGILL), code 2 (ILL_ILLOPN)

一、问题描述

最近在JNI开发中,【我的Android进阶之旅】Android 如何防止 so库文件被未知应用盗用?

抛了一个异常,然后运行的时候报如下所示的错误:

2021-01-08 14:25:58.170 10974-10974/com.csdn.ouyangpeng.jni D/ouyangpeng-jni-log: hex_sha  6A68B6BDBBB7E79772B2A075A7815537CCA57F6F 
2021-01-08 14:25:58.170 10974-10974/com.csdn.ouyangpeng.jni D/ouyangpeng-jni-log: Verification Failed!
    
    --------- beginning of crash
2021-01-08 14:25:58.170 10974-10974/com.csdn.ouyangpeng.jni A/libc: Fatal signal 4 (SIGILL), code 2 (ILL_ILLOPN), fault addr 0xd1649e40 in tid 10974 (.ouyangpeng.jni), pid 10974 (.ouyangpeng.jni)

但是我抛异常的代码如下所示:

//用来抛出一个指定名字的异常。
void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg);
void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) {
   
   
    jclass cls = (*env).FindClass(name);
    /*if cls is NULL, an exception has already been thrown */
    if (cls) {
   
   
        (*env).ThrowNew(cls, msg);
    }
    /* free the local ref */
    (*env).DeleteLocalRef(cls);
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_csdn_ouyangpeng_jni_SignatureVerificationUtil_getTokenFromC(
        JNIEnv *env,
        jobject,
        jobject contextObject,
        jstring userId) {
   
   
    // 检验签名是否正确
    char *sha1 = getSha1(env, contextObject);
    jboolean result = checkValidity(env, sha1);

    // 如果签名正确,则返回正确的token
    if (result) {
   
   
        return env->NewStringUTF("获取Token成功,token为 ouyangpeng");
    } else {
   
   
        // 验证不通过 直接抛异常
        JNU_ThrowByName(env, "java/lang/IllegalArgumentException","Failed to obtain Token, You are a thief, please do not use my so files!");
    }
}

按理来说,应该是抛一个IllegalArgumentException异常,异常信息是Failed to obtain Token, You are a thief, please do not use my so files!。但是和我想的不一样。

二、解决问题

2.1 问题原因

上面的问题是,有返回值的函数没有返回值。Java_com_csdn_ouyangpeng_jni_SignatureVerificationUtil_getTokenFromC函数要返回一个jstring字符串的,但是我直接抛异常之后就没有return了,所以就出现了上面的异常。

2.2 解决方法

Java_com_csdn_ouyangpeng_jni_SignatureVerificationUtil_getTokenFromC 添加一句return一个字符串即可。

比如: return env->NewStringUTF("获取Token失败,请检查valid.cpp文件配置的sha1值");

extern "C"
JNIEXPORT jstring JNICALL
Java_com_csdn_ouyangpeng_jni_SignatureVerificationUtil_getTokenFromC(
        JNIEnv *env,
        jobject,
        jobject contextObject,
        jstring userId) {
   
   
    // 检验签名是否正确
    char *sha1 = getSha1(env, contextObject);
    jboolean result = checkValidity(env, sha1);

    // 如果签名正确,则返回正确的token
    if (result) {
   
   
        return env->NewStringUTF("获取Token成功,token为 ouyangpeng");
    } else {
   
   
        // 验证不通过 直接抛异常
        JNU_ThrowByName(env, "java/lang/IllegalArgumentException","Failed to obtain Token, You are a thief, please do not use my so files!");

        // 如果不加下面的return,会报异常: A/libc: Fatal signal 4 (SIGILL), code 2 (ILL_ILLOPN), fault addr 0xd1655e40 in tid 9399 (.ouyangpeng.jni), pid 9399 (.ouyangpeng.jni)
        //一定记得有return语句,即使你的return值没有用!

        // 如果签名不正确,则返回错误的结果,迷惑窃取方。
        return env->NewStringUTF("获取Token失败,请检查valid.cpp文件配置的sha1值");
    }
}

2.3 重新运行

重新运行后,报错如下所示:
出现了我定义的异常

2021-01-08 14:35:20.199 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545] JNI DETECTED ERROR IN APPLICATION: JNI NewStringUTF called with pending exception java.lang.IllegalArgumentException: Failed to obtain Token, You are a thief, please do not use my so files!

2021-01-08 14:35:20.199 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545] JNI DETECTED ERROR IN APPLICATION: JNI NewStringUTF called with pending exception java.lang.IllegalArgumentException: Failed to obtain Token, You are a thief, please do not use my so files!
2021-01-08 14:35:20.199 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at java.lang.String com.csdn.ouyangpeng.jni.SignatureVerificationUtil.getTokenFromC(android.content.Context, java.lang.String) (SignatureVerificationUtil.kt:-2)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void com.csdn.ouyangpeng.jni.MainActivity.onClick(android.view.View) (MainActivity.kt:53)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at boolean android.view.View.performClick() (View.java:6597)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at boolean android.view.View.performClickInternal() (View.java:6574)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at boolean android.view.View.access$3100(android.view.View) (View.java:778)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void android.view.View$PerformClick.run() (View.java:25885)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void android.os.Handler.handleCallback(android.os.Message) (Handler.java:873)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void android.os.Handler.dispatchMessage(android.os.Message) (Handler.java:99)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void android.os.Looper.loop() (Looper.java:193)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void android.app.ActivityThread.main(java.lang.String[]) (ActivityThread.java:6669)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run() (RuntimeInit.java:493)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]   at void com.android.internal.os.ZygoteInit.main(java.lang.String[]) (ZygoteInit.java:858)
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545] 
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]     in call to NewStringUTF
2021-01-08 14:35:20.200 11479-11479/com.csdn.ouyangpeng.jni A/.ouyangpeng.jn: java_vm_ext.cc:545]     from java.lang.String com.csdn.ouyangpeng.jni.SignatureVerificationUtil.getTokenFromC(android.content.Context, java.lang.String)

这样问题算解决了。

三、深入研究

在博客信号机制和Android natvie crash捕捉 中有详细的介绍。

下面内容摘自博客信号机制和Android natvie crash捕捉


3.1 信号机制

在这里插入图片描述

函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换。

3.1.1、信号的接收

接收信号的任务是由内核代理的,当内核接收到信号后,会将其放到对应进程的信号队列中,同时向进程发送一个中断,使其陷入内核态。

此时信号还只是在队列中,对进程来说暂时是不知道有信号到来的。

3.1.2、信号的检测

进程陷入内核态后,有两种场景会对信号进行检测:

  • 进程从内核态返回到用户态前进行信号检测
  • 进程在内核态中,从睡眠状态被唤醒的时候进行信号检测
    当发现有新信号时,便会进入下一步,信号的处理。

3.1.3、信号的处理

信号处理函数是运行在用户态的,调用信号处理函数前,内核会将当前的内核栈的内容备份拷贝到用户栈,然后修改命令寄存器(eip)指向信号处理函数。
接下来进程返回到用户态中,执行相应的信号处理函数。
信号处理函数执行完成后,还需要返回内核态,检查是否还有其它信号未处理。
如果所有信号都处理完成,就会将内核栈恢复(从用户栈的备份拷贝回来),同时恢复指令寄存器(eip)将其指向中断前的运行位置,最后回到用户态继续执行进程。


一个完整的信号处理流程便结束了,如果同时有多个信号到达,上面的处理流程会在第2步和第3步骤间重复进行。

3.2、信号定义和行为

3.2.1、信号的定义

所有的符合Unix规范(如POSIX)的系统都统一定义了SIGNAL的数量、含义和行为。
Android代码中,signal的定义一般在 signum.h(Android代码中,signal的定义一般在 signum.h)中。

一共有31个信号,其中1~15号信号为常用信号

/* Signals.  */  
#define SIGHUP      1   /* Hangup (POSIX).  */   
#define SIGINT      2   /* Interrupt (ANSI).  */   
#define SIGQUIT     3   /* Quit (POSIX).  */   
#define SIGILL      4   /* Illegal instruction (ANSI).  */   
#define SIGTRAP     5   /* Trace trap (POSIX).  */   
#define SIGABRT     6   /* Abort (ANSI).  */   
#define SIGIOT      6   /* IOT trap (4.2 BSD).  */   
#define SIGBUS      7   /* BUS error (4.2 BSD).  */   
#define SIGFPE      8   /* Floating-point exception (ANSI).  */   
#define SIGKILL     9   /* Kill, unblockable (POSIX).  */   
#define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */   
#define SIGSEGV     11  /* Segmentation violation (ANSI).  */   
#define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */   
#define SIGPIPE     13  /* Broken pipe (POSIX).  */   
#define SIGALRM     14  /* Alarm clock (POSIX).  */   
#define SIGTERM     15  /* Termination (ANSI).  */   
#define SIGSTKFLT   16  /* Stack fault.  */   
#define SIGCLD      SIGCHLD /* Same as SIGCHLD (System V).  */   
#define SIGCHLD     17  /* Child status has changed (POSIX).  */   
#define SIGCONT     18  /* Continue (POSIX).  */   
#define SIGSTOP     19  /* Stop, unblockable (POSIX).  */   
#define SIGTSTP     20  /* Keyboard stop (POSIX).  */   
#define SIGTTIN     21  /* Background read from tty (POSIX).  */   
#define SIGTTOU     22  /* Background write to tty (POSIX).  */   
#define SIGURG      23  /* Urgent condition on socket (4.2 BSD).  */   
#define SIGXCPU     24  /* CPU limit exceeded (4.2 BSD).  */   
#define SIGXFSZ     25  /* File size limit exceeded (4.2 BSD).  */   
#define SIGVTALRM   26  /* Virtual alarm clock (4.2 BSD).  */   
#define SIGPROF     27  /* Profiling alarm clock (4.2 BSD).  */   
#define SIGWINCH    28  /* Window size change (4.3 BSD, Sun).  */   
#define SIGPOLL     SIGIO   /* Pollable event occurred (System V).  */   
#define SIGIO       29  /* I/O now possible (4.2 BSD).  */   
#define SIGPWR      30  /* Power failure restart (System V).  */   
#define SIGSYS      31  /* Bad system call.  */   
#define SIGUNUSED   31  
   插曲:什么是POSIX
    POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准。
    POSIX标准意在期望获得源代码级别的软件可移植性。换句话说,为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行。

    简单来说:
    完成同一功能,不同内核提供的系统调用(也就是一个函数)是不同的。例如创建进程,linux下是fork函数,windows下是creatprocess函数。
    POSIX 要求 linux和windows都要实现基本的posix标准,linux把fork函数封装成posix_fork(随便说的),windows把creatprocess函数也封装成posix_fork,都声明在unistd.h里。这样,程序员编写普通应用时候,只用包含unistd.h,调用posix_fork函数,程序就在源代码级别可移植了。

3.2.2、常见信号的含义

SIGHUP - 1

本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。
登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。
此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

SIGINT - 2

程序终止(interrupt)信号,通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

SIGQUI - 3

和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制.进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

SIGILL - 4

执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

SIGTRAP - 5

由断点指令或其它trap指令产生. 由debugger使用。

SIGABRT - 6

调用abort函数生成的信号。

SIGBUS - 7

非法地址,包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。
它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

SIGFPE - 8

算术运算错误。不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。

SIGKILL - 9

用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

SIGUSR1 - 10

用户自定义的信号1

SIGSEGV - 11

访问非法地址。试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

SIGUSR2 -12

用户自定义的信号1

SIGPIPE - 13

管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

SIGALRM -14

时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

SIGTERM - 15

程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出

SIGILL - 4

执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

3.3 android 捕捉native crash 需要注册下面6个信号

Android 平台 捕捉natvie crash 一般只需要安装6个信号处理函数即可。

信号 信号值 含义 备注 在Android中默认行为
SIGSEGV 11 访问无效地址 如试图访问未分配给自己的内存 生成tombstone文件,然后退出
SIGBUS 7 非法地址 包括内存地址对齐(alignment)出错。 生成tombstone文件,然后退出
SIGABRT 6 调用abort函数生成的信号。 生成tombstone文件,然后退出
SIGFPE 8 浮点计算错误。 包括浮点运算错误, 还包括溢出及除数为0等算数运算错误 生成tombstone文件,然后退出
SIGILL 4 非法指令错误。 非法指令错误。 生成tombstone文件,然后退出
SIGTRAP 5 硬件错误(通常为断点指令) 生成tombstone文件,然后退出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章