一、问题描述
最近在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文件,然后退出 |