AndroidのJNIでSIGSEGV(セグメンテーション違反)をキャッチしてスタックトレースを取得するにはどうすればよいですか?


92

プロジェクトを新しいAndroid Native Development Kit(JNI)に移動します。SIGSEGVが発生した場合(おそらくSIGILL、SIGABRT、SIGFPEも)、代わりに、素敵なクラッシュレポートダイアログを表示したいのですが。 (または以前に)現在発生していること:プロセスの即時の異常な終了と、OSによるプロセスの再起動の試み。(編集: JVM / Dalvik VMはシグナルをキャッチし、スタックトレースやその他の有用な情報をログに記録します。その情報をメールで送信するオプションをユーザーに提供したいだけです。)

状況は次のとおりです。私が記述していない大量のCコードが、このアプリケーション(すべてのゲームロジック)でほとんどの作業を行います。他の多くのプラットフォームで十分にテストされていますが、Androidで移植すると、ガベージをフィードし、ネイティブコードでクラッシュを引き起こすので、現在Androidログに表示されるクラッシュダンプ(ネイティブとJavaの両方)が必要です(Android以外の状況ではstderrになると思います)。私は自由にCとJavaの両方のコードを変更できますが、コールバック(JNIに出入りするコールバックの両方)の数は約40で、明らかに小さな差分にはボーナスポイントがあります。

J2SEのシグナルチェーンライブラリであるlibjsig.soを聞いたことがあります。Androidにそのようなシグナルハンドラーを安全にインストールできれば、私の疑問の一部を解決できますが、Android / Dalvikにはそのようなライブラリはありません。


ラッパースクリプトを使用してJava VMを起動できる場合は、アプリが異常終了したかどうかを確認し、エラー報告を行うことができます。これにより、SIGSEGV、SIGKILLなど、あらゆる種類の異常終了をきれいにキャッチできます。しかし、これはストックAndroidアプリでは可能ではないと思うので、これをコメントとして投稿します(回答から変換)。
sleske 2015

ラッパースクリプト(adbシェル内)を使用してAndroidアプリを起動する方法については、ValgrindJava Androidプログラムを実行できないことも参照してください。
sleske

1
答えを更新する必要があります。受け入れられた回答で提供されているソースコードは、非同期シグナルセーフではない関数の呼び出しにより、未定義の動作を引き起こします。こちらをご覧ください:stackoverflow.com/questions/34547199/...
user1506104

回答:


82

編集:以降ジェリービーンからは、スタックトレースを取得することはできませんが、理由はREAD_LOGS去っていきました。:-(

実際、シグナルハンドラーをエキゾチックなことをせずに機能させ、それを使用してコードをリリースしました。これはgithubで確認できます(編集:過去のリリースへのリンク。それ以降、クラッシュハンドラーは削除しました)。方法は次のとおりです。

  1. sigaction()シグナルをキャッチし、古いハンドラーを格納するために使用します。(android.c:570
  2. 時間が経過すると、セグメンテーション違反が発生します。
  3. シグナルハンドラーで、最後にもう一度JNIを呼び出してから、古いハンドラーを呼び出します。(android.c:528
  4. そのJNI呼び出しで、有用なデバッグ情報をログに記録startActivity()し、独自のプロセスにある必要があるとフラグが立てられたアクティビティーを呼び出します。(SGTPuzzles.java:962AndroidManifest.xmlを:28
  5. Javaから戻ってその古いハンドラーを呼び出すと、Androidフレームワークがに接続しdebuggerdて素晴らしいネイティブトレースをログに記録し、プロセスが終了します。(debugger.cdebuggerd.c
  6. その間、クラッシュ処理アクティビティが起動しています。実際には、ステップ5が完了するのを待つことができるように、PIDを渡す必要があります。私はこれをしません。ここで、ユーザーに謝罪し、ログを送信できるかどうか尋ねます。もしそうなら、の出力を収集logcat -d -v threadtimeし、起動ACTION_SEND中に満たされた受信者、件名と本文で。ユーザーは、プレス送信する必要があります。(CrashHandler.javaSGTPuzzles.java:462 : strings.xml:41
  7. logcat失敗するか、数秒以上かかることに注意してください。1つのデバイス、T-Mobile Pulse / Huawei U8220に遭遇しました。logcatはすぐにT(トレースされた)状態になり、ハングします。(CrashHandler.java:70のstrings.xml:51

Android以外の状況では、これのいくつかは異なります。自分のネイティブトレースを収集する必要があります。使用しているlibcの種類に応じて、この別の質問を参照してください。そのトレースのダンプ、個別のクラッシュハンドラプロセスの起動、プラットフォームに適したいくつかの方法での電子メールの送信を処理する必要がありますが、一般的なアプローチは引き続き機能するはずです。


2
ライブラリでクラッシュが発生したかどうかを確認するのが理想的です。それが別の場所(たとえば、VM内)で発生した場合、シグナルハンドラーからのJNI呼び出しが混乱を招く可能性があります。とにかくクラッシュしているので、それは世界の終わりではありませんが、VMクラッシュの診断をより困難にする可能性があります(または、Androidバグレポートになって、みんなを困惑させる奇妙なVMクラッシュを引き起こす可能性があります)。
10

あなたはこれに関するあなたの研究プロジェクトを共有するための素晴らしい@Chrisです!
olafure

おかげで、これは私のJNIがどこに行き着くのかを見つけるのに役立ちました。また、DCSの卒業生からこんにちは!
ニック

3
また、サービスから新しいプロセスでの活動を開始すると、次のコードを必要としますnewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
グレアム

1
このソリューションはJelly Beanでも有効ですか?ステップ6はdebuggerd出力のログに失敗しませんか?
Josh、

14

私は後半に少しだけど、私は正確に同じニーズを持っていた、と私は(一般的なクラッシュを引くことで、それに対処するための小さなライブラリを開発したSEGVSIBGUS内部の、など)JNIコード、および定期的なことでそれらを交換java.lang.Error 例外。おまけ、クライアントがAndroid> = 4.1.1で実行されている場合、スタックトレースはクラッシュの解決されたバックトレース(完全なネイティブスタックトレースを含む疑似トレース)を埋め込みます。悪質なクラッシュから回復することはできません(つまり、アロケータを破損した場合など)が、少なくともそれらのほとんどから回復できるはずです。(成功と失敗を報告してください。コードは新品です)

詳しくはhttps://github.com/xroche/coffeecatchをご覧ください (コードはBSD 2-Clausesライセンスです


6

FWIW、Google BreakpadはAndroidで正常に動作します。私は移植作業を行い、Firefox Mobileの一部として出荷します。クライアント側でスタックトレースを提供しないため、少し設定が必要ですが、生のスタックメモリを送信し、サーバー側でスタックウォーキングを実行します(そのため、アプリにデバッグシンボルを含める必要はありません。 )。


1
ドキュメントが完全に欠けていることを考慮してBreakpadを構成することはほとんど不可能です
シェーダー

それほど難しくはありません。プロジェクトのwikiにはたくさんのドキュメントがあります。実際には、AndroidのためにNDKビルドはMakefileの今そこだとそれが使用する超簡単でなければなりません:code.google.com/p/google-breakpad/source/browse/trunk/...
テッドMielczarek

また、Androidのデバッグシンボルファイルを前処理するモジュールをコンパイルする必要があります。コンパイルできるのはLinuxのみです。Macでコンパイルすると、Mac / iOS dSymプリプロセッサのみがビルドされます。
シェーダー

5

私の限られた経験(Android以外)では、制御がJavaコードに戻る前に、JNIコードのSIGSEGVが通常JVMをクラッシュさせます。SIGSEGVをキャッチできるSun以外のJVMについて聞いたことがあるのは漠然と覚えていますが、AFAICRではそうすることは期待できません。

プロセスの進行中の動作は公式には定義されていないため、SIGSEGV(またはSIGFPEまたはSIGILL)ハンドラーの後にはほとんど実行できませんが、Cでそれらをキャッチしようとすることができます(sigaction(2)を参照)。


まあ、動作は「kill(2)またはraise(3)によって生成されなかったSIGFPE、SIGILL、またはSIGSEGVシグナルを無視」した後は未定義ですが、必ずしもそのようなシグナルをキャッチしている間ではありません。現在の計画では、Javaにコールバックし、プロセスを終了せずにスレッドを終了するCシグナルハンドラーを試すことです。これは可能かもしれませんし、可能でないかもしれません。:-)
クリスボイル


1
...私がbacktrace()を使用できないことを除いて、Androidはglibcを使用しないため、Bionicを使用します。:-(関わる何か_Unwind_Backtraceからunwind.h代わりに必要となります。
クリス・ボイル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.