Fatal signal 11 (SIGSEGV) 錯誤捕獲并拋出給JAVA

公司從人人網(wǎng)接收了經(jīng)緯名片通,開始進行后續(xù)開發(fā)。
該項目安卓的名片本地識別模塊是調(diào)JNI的,最底層識別圖片的部分暫時沒有源代碼。糟糕的是,該模塊在部分機型上出現(xiàn)閃退,并報錯:Fatal signal 11 (SIGSEGV) at .... (code=1)

谷歌翻了十幾頁,都是講怎么定位錯誤,怎么修改錯誤??墒堑讓拥拇a我沒有啊,就算定位了也改不了。而且項目等著上線,就算后續(xù)要到源碼也來不及了。
所以,我要做的只是讓C把錯誤拋給Java,別讓程序閃退即可。

這里,我們需要用到C的兩組庫函數(shù):signal、setjmp/longjmp

信號是程序處理中的異常情況,信號產(chǎn)生時,系統(tǒng)會進行一些默認的操作。通過signal函數(shù),可以把這些默認操作更改為自己定義的操作。
signal函數(shù)的頭文件:

#include <iostream>
#include <csignal>
{
    "data": {
        "cardId": 72193540
    },
    "message": "sucess",
    "status": 0
}


signal函數(shù)定義:

static __inline__ __sighandler_t signal(int s, __sighandler_t f)

signal函數(shù)調(diào)用:

void handler(int sig){...}
signal(SIGSEGV, handler);

其中,SIGSEGV就是錯誤里面提示的那個信號,在signal.h的頭文件里,它被定義為11。是不是有豁然開朗的感覺?_
我們需要在可能出錯的函數(shù)里寫上:signal(SIGSEGV, handler)
一旦系統(tǒng)接收到SIGSEGV,就會自動執(zhí)行handler函數(shù)。

handler函數(shù)是自定義的,它的參數(shù)就是signal函數(shù)的一個參數(shù)。我們來完善一下它。

void handler(int sig) {
    //解除綁定到信號上的方法
    signal(sig, SIG_DFL);
    throwJNIException(penv);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
    //注意JNIException的路徑
    jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
    if (lClass != NULL) {
        pEnv->ThrowNew(lClass, "Throw JNIException");
        //如果我們長時間不再需要引用這個異常類時,可以使用DeleteLocalRef()來解除它。
        pEnv->DeleteLocalRef(lClass);
    }
}

handler函數(shù)的第一行,是將相應信號的操作重新改為系統(tǒng)默認的操作。
SIG_DFL代表執(zhí)行系統(tǒng)默認操作,類似地,SIG_ING 代表忽略信號。
penv是出錯的方法的環(huán)境,在文末總結(jié)的時候可以看到。
throwJNIException函數(shù)是把錯誤拋給JAVA,本例中需要新建一個JAVA的Exception類JNIException,這里不再貼代碼。

C語言不像JAVA,JAVA底層拋出錯誤后就可以不管了,而C語言需要函數(shù)正常返回。這時候我們就需要setjmp/longjmp函數(shù)。這兩個函數(shù)有點像goto一樣,可以互相跳轉(zhuǎn)。
setjmp/longjmp函數(shù)的頭文件:

#include <setjmp.h>

setjmp/longjmp函數(shù)定義:

int     setjmp(jmp_buf);
void    longjmp(jmp_buf, int);

來看一個從維基百科抄來的經(jīng)典例子:

#include <stdio.h>
#include <setjmp.h>

static jmp_buf buf;

void second(void) { 
  printf("second\n"); // 打印 
  longjmp(buf,1); // 跳回setjmp的調(diào)用處 - 使得setjmp返回值為1
}
void first(void) { 
  second(); 
  printf("first\n"); // 不可能執(zhí)行到此行
}
int main() {
  if (!setjmp(buf)) { 
    first(); // 進入此行前,setjmp返回0 
  } else { 
    // 當longjmp跳轉(zhuǎn)回,setjmp返回1,因此進入此行 
    printf("main\n"); // 打印 
  } 
  return 0;
}

打印結(jié)果:
second
main

main函數(shù)先執(zhí)行到if語句,setjmp將程序的調(diào)用環(huán)境存儲在緩沖區(qū)buf中。setjmp如果是首次調(diào)用會返回0,于是程序進入first函數(shù)。
從first函數(shù)進入second函數(shù)并遇到longjmp后,程序會返回main函數(shù)的if語句。此時,setjmp的返回值會變成longjmp的第二個參數(shù),于是打印"main"后結(jié)束。

所以最終,程序看起來應該是這個樣子:

#include <iostream>
#include <csignal>
#include <setjmp.h>

static jmp_buf jumpflg;
JNIEnv* penv = NULL;
...
void handler(int sig) {
    //解除綁定到信號上的方法
    signal(sig, SIG_DFL);
    throwJNIException(penv);
    longjmp (jumpflg, 1);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
    //注意JNIException的路徑
    jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
    if (lClass != NULL) {
        pEnv->ThrowNew(lClass, "Throw JNIException");
        //如果我們長時間不再需要引用這個異常類時,可以使用DeleteLocalRef()來解除它。
        pEnv->DeleteLocalRef(lClass);
    }
}

JNIEXPORT jint JNICALL Java_com_jingwei_ocrs_OCRHandler(JNIEnv* env, jobject thiz) {
    signal(SIGSEGV, handler);

    penv = env;
    if (!setjmp(jumpflg)) {
          //可能有錯誤的語句寫在這里
          return 1;
    } else {
          return 0;
    }
}

至此,外層JAVA即可捕捉JNIException。不過調(diào)試中發(fā)現(xiàn),部分機型會拋出StackOverflowError,具體原因不明,所以外層不得不在捕捉JNIException的同時,也捕捉StackOverflowError。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容