【我的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文件,然後退出
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章