1. ホーム
  2. アンドロイド

致命的なシグナル11 (SIGSEGV) エラーをキャッチしてJAVAに投げる

2022-03-17 07:47:15





このプロジェクトのAndroid名刺ローカル認識モジュールは、JNIにチューニングされており、画像を認識する下の部分は今のところソースコードがありません。悪いことに、一部の機種でモジュールが点滅し、エラーを報告します。Fatal signal 11 (SIGSEGV) at .... (コード=1)

エラーの場所を特定する方法とそれを修正する方法について、すべてGoogleで十数ページ調べました。しかし、私は基礎となるコードを持っていないので、場所を特定しても変更することはできません。そして、プロジェクトは本番を待っているので、後でソースコードが欲しくなっても遅すぎるのです。



だから、CがJavaにエラーを投げて、プログラムをフラッシュさせないようにすればいいだけなんです。

ここでは、C言語の2つのライブラリ関数、signal、setjmp/longjmpを使用する必要があります。

シグナルはプログラム処理における例外です。シグナルが発生すると、システムはいくつかのデフォルトの処理を実行します。シグナル関数を使うと、これらのデフォルトの処理を自分で定義した処理に変更することができます。



signal関数のヘッダーファイルです。

#include 
#include 
{
    "data": {
        "cardId": 72193540
    },
    "message": "process",
    "status": 0
}




シグナル関数の定義です。

static __inline__ __sighandler_t signal(int s, __sighandler_t f)


シグナル関数が呼び出されます。

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


ここで、SIGSEGVはエラーによって促されるシグナルであり、signal.hヘッダーファイルで11と定義されています。 _



エラーが発生する可能性のある関数に signal(SIGSEGV, handler) と記述する必要があります。



システムがSIGSEGVを受信すると、ハンドラ関数が自動的に実行されます。

ハンドラ関数はカスタムで、その引数はシグナル関数の引数の1つです。それを改良してみましょう。

void handler(int sig) {
    //unbind to the signal
    signal(sig, SIG_DFL);
    throwJNIException(penv);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
    // note the path of the JNIException
    jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
    if (lClass ! = NULL) {
        pEnv->ThrowNew(lClass, "Throw JNIException");
        // If we no longer need to refer to this exception class for a long time, we can use DeleteLocalRef() to unlock it.
        pEnv->DeleteLocalRef(lClass);
    }
}


ハンドラ関数の最初の行は、対応するシグナルのアクションをシステムのデフォルトアクションに戻すためのものです。



SIG_DFLはシステムデフォルトの動作を行うことを意味し、同様にSIG_INGはシグナルを無視することを意味します。



penvは、記事末のまとめにあるように、うまくいかなかったメソッドの環境です。



throwJNIException関数は、JAVAにエラーを投げるための関数で、この場合、新しいJAVA ExceptionクラスJNIExceptionを作成する必要があります、ここではコードは掲載しません。

C言語では、JAVAのように根本的なエラーを投げて放置するのではなく、関数が正常に戻ることが必要です。そこで、setjmp/longjmpという関数が必要になります。この2つの関数はgotoのようなもので、お互いにジャンプすることができます。



setjmp/longjmp関数のヘッダーファイルです。

#include 


setjmp/longjmp 関数の定義です。

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


Wikipediaからコピーした典型的な例を見てみましょう。

#include 
#include 

static jmp_buf buf;

void second(void) { 
  printf("second\n"); // print 
  longjmp(buf,1); // jump back to the setjmp call - so that setjmp returns a value of 1
}
void first(void) { 
  second(); 
  printf("first\n"); // impossible to execute to this line
}
int main() {
  if (!setjmp(buf)) { 
    first(); // setjmp returns 0 before entering this line 
  } else { 
    // when longjmp jumps back, setjmp returns 1, so go to this line 
    printf("main\n"); // print 
  } 
  return 0;
}

Print the result.
second
main


main関数が最初にif文まで実行され、setjmpがプログラムの呼び出し環境をバッファbufに格納します。setjmpは、初めて呼ばれた場合は0を返すので、プログラムは最初の関数に入ります。



1番目の関数から2番目の関数に入り、longjmpに遭遇した後、プログラムはmain関数のif文に戻ります。このとき、setjmpの戻り値がlongjmpの第2引数になるので、"main"と出力して終了します。

というわけで、最終的にプログラムはこんな感じになります。

#include 
#include 
#include 

static jmp_buf jumpflg;
JNIEnv* penv = NULL;
...
void handler(int sig) {
    //unbind to the signal
    signal(sig, SIG_DFL);
    throwJNIException(penv);
    longjmp (jumpflg, 1);
}
JNIEXPORT void throwJNIException(JNIEnv* pEnv) {
    // note the path of the JNIException
    jclass lClass = pEnv->FindClass("com/jingwei/ocrs/JNIException");
    if (lClass ! = NULL) {
        pEnv->ThrowNew(lClass, "Throw JNIException");
        // If we no longer need to refer to this exception class for a long time, we can use DeleteLocalRef() to unlock it.
        pEnv->DeleteLocalRef(lClass);
    }
}

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

    penv = env;
    if (!setjmp(jumpflg)) {
          // there may be a wrong statement written here
          return 1;
    } else {
          return 0;
    }
}


この時点で、外側のJAVAレイヤーはJNIExceptionをキャッチする準備ができていますが、デバッグの結果、いくつかのモデルが原因不明のStackOverflowErrorを投げることがわかりましたので、外側のレイヤーはStackOverflowErrorだけでなくJNIExceptionもキャッチしなければならないのです。