ネイティブ コードでのクラッシュを診断する

以下のセクションでは、ネイティブ コードでのクラッシュ(以下「ネイティブ クラッシュ」)の一般的なタイプを示し、サンプル クラッシュ ダンプの分析と Tombstone の調査について説明します。クラッシュの各タイプの説明では、debuggerd のサンプル出力の中で、特定の種類のクラッシュを識別するために役立つ重要な証跡を強調表示しています。

中止(abort)

中止は、意図的に行われる点で、興味深い手法です。中止を行うには、abort(3) を呼び出す、assert(3) を失敗させる、Android 固有の「fatal」なロギングタイプの 1 つを使用するなど、さまざま方法がありますが、どの方法でも abort を呼び出します。abort の呼び出しは、呼び出し元スレッドに SIGABRT シグナルを送ります。したがって、debuggerd 出力でこのケースを識別するには、libc.so に「abort」が表示されているフレームと SIGABRT を探します。

「abort message」と明示された行が見つかるかもしれません。また、logcat 出力を調べて、このスレッドが意図的に中止される前に何が記録されたかを確認する必要があります。assert(3) または高レベルの fatal ロギング機能と異なり、abort(3) はメッセージを受け入れないからです。

Android の最近のバージョンは tgkill(2) システムコールをインライン化するので、スタックが大変読みやすくなっており、一番上に abort(3) 呼び出しが表示されます。

pid: 4637, tid: 4637, name: crasher  >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'some_file.c:123: some_function: assertion "false" failed'
    r0  00000000  r1  0000121d  r2  00000006  r3  00000008
    r4  0000121d  r5  0000121d  r6  ffb44a1c  r7  0000010c
    r8  00000000  r9  00000000  r10 00000000  r11 00000000
    ip  ffb44c20  sp  ffb44a08  lr  eace2b0b  pc  eace2b16
backtrace:
    #00 pc 0001cb16  /system/lib/libc.so (abort+57)
    #01 pc 0001cd8f  /system/lib/libc.so (__assert2+22)
    #02 pc 00001531  /system/bin/crasher (do_action+764)
    #03 pc 00002301  /system/bin/crasher (main+68)
    #04 pc 0008a809  /system/lib/libc.so (__libc_init+48)
    #05 pc 00001097  /system/bin/crasher (_start_main+38)

古いバージョンの Android では、元の abort 呼び出し(この例ではフレーム 4)と実際のシグナル送信(この例ではフレーム 0)との間で複雑なパスを経由していました。32 ビット ARM では特に複雑で、__libc_android_abort(この例ではフレーム 3)が他のプラットフォームの raise / pthread_kill / tgkill のシーケンスに追加されていました。

pid: 1656, tid: 1656, name: crasher  >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'some_file.c:123: some_function: assertion "false" failed'
    r0 00000000  r1 00000678  r2 00000006  r3 f70b6dc8
    r4 f70b6dd0  r5 f70b6d80  r6 00000002  r7 0000010c
    r8 ffffffed  r9 00000000  sl 00000000  fp ff96ae1c
    ip 00000006  sp ff96ad18  lr f700ced5  pc f700dc98  cpsr 400b0010
backtrace:
    #00 pc 00042c98  /system/lib/libc.so (tgkill+12)
    #01 pc 00041ed1  /system/lib/libc.so (pthread_kill+32)
    #02 pc 0001bb87  /system/lib/libc.so (raise+10)
    #03 pc 00018cad  /system/lib/libc.so (__libc_android_abort+34)
    #04 pc 000168e8  /system/lib/libc.so (abort+4)
    #05 pc 0001a78f  /system/lib/libc.so (__libc_fatal+16)
    #06 pc 00018d35  /system/lib/libc.so (__assert2+20)
    #07 pc 00000f21  /system/xbin/crasher
    #08 pc 00016795  /system/lib/libc.so (__libc_init+44)
    #09 pc 00000abc  /system/xbin/crasher

このタイプのクラッシュのインスタンスを再現するには、crasher abort を使用します。

単なる null ポインタ逆参照(デリファレンス)

これは古典的なネイティブ クラッシュであり、次に示すクラッシュ タイプの特殊ケースにすぎませんが、あえて別に取り上げる価値があります。通常、簡単に見つけられるからです。

次の例では、文字列関数は指定されたポインタで動作するだけなので、クラッシュした関数が libc.so 内に存在するにもかかわらず、strlen(3) が null ポインタで呼び出されたと推論できます。つまり、このクラッシュの原因は呼び出し元コードに帰せられます。この例では、不適切な呼び出し元はフレーム #01 です。

pid: 25326, tid: 25326, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
    r0 00000000  r1 00000000  r2 00004c00  r3 00000000
    r4 ab088071  r5 fff92b34  r6 00000002  r7 fff92b40
    r8 00000000  r9 00000000  sl 00000000  fp fff92b2c
    ip ab08cfc4  sp fff92a08  lr ab087a93  pc efb78988  cpsr 600d0030

backtrace:
    #00 pc 00019988  /system/lib/libc.so (strlen+71)
    #01 pc 00001a8f  /system/xbin/crasher (strlen_null+22)
    #02 pc 000017cd  /system/xbin/crasher (do_action+948)
    #03 pc 000020d5  /system/xbin/crasher (main+100)
    #04 pc 000177a1  /system/lib/libc.so (__libc_init+48)
    #05 pc 000010e4  /system/xbin/crasher (_start+96)

このタイプのクラッシュのインスタンスを再現するには、crasher strlen-NULL を使用します。

低アドレスの null ポインタ逆参照

多くの場合、フォールト アドレスは 0 ではなく、他の低い番号です。特に 2 桁または 3 桁のアドレスはよくありますが、6 桁のアドレスはほぼ確実に null ポインタ逆参照ではありません。それには 1 MiB のオフセットが必要です。通常これは、null ポインタを有効な構造体であるかのように見なして逆参照するコードが存在する場合に発生します。一般的な関数は、fprintf(3)(または FILE* を受け取る他の任意の関数)と readdir(3) です。なぜなら、fopen(3) または opendir(3) の呼び出しが実際は最初に成功したことをコードが確認できない場合がしばしばあるからです。

readdir の例を次に示します。

pid: 25405, tid: 25405, name: crasher  >>> crasher <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc
    r0 0000000c  r1 00000000  r2 00000000  r3 3d5f0000
    r4 00000000  r5 0000000c  r6 00000002  r7 ff8618f0
    r8 00000000  r9 00000000  sl 00000000  fp ff8618dc
    ip edaa6834  sp ff8617a8  lr eda34a1f  pc eda618f6  cpsr 600d0030

backtrace:
    #00 pc 000478f6  /system/lib/libc.so (pthread_mutex_lock+1)
    #01 pc 0001aa1b  /system/lib/libc.so (readdir+10)
    #02 pc 00001b35  /system/xbin/crasher (readdir_null+20)
    #03 pc 00001815  /system/xbin/crasher (do_action+976)
    #04 pc 000021e5  /system/xbin/crasher (main+100)
    #05 pc 000177a1  /system/lib/libc.so (__libc_init+48)
    #06 pc 00001110  /system/xbin/crasher (_start+96)

この例では、クラッシュの直接の原因は、pthread_mutex_lock(3) がアドレス 0xc(フレーム 0)にアクセスしようとしたことです。しかし、pthread_mutex_lock が最初に行ったのは、与えられた pthread_mutex_t*state 要素の逆参照です。ソースを見ると、その要素が構造体のオフセット 0 にあることがわかります。これは、pthread_mutex_lock に無効なポインタ 0xc が渡されたことを示しています。フレーム 1 から、readdir(与えられた DIR* から mutex_ フィールドを抽出します)によってそのポインタが渡されたことがわかります。その構造体を見ると、mutex_struct DIR へのオフセット sizeof(int) + sizeof(size_t) + sizeof(dirent*) にあることがわかります。これは、32 ビットデバイスでは 4 + 4 + 4 = 12 = 0xc です。これで、呼び出し元から readdir に null ポインタが渡されているバグが見つかりました。この時点で、スタックをスタックツールに貼り付けて、logcat のどこで問題が発生したかを解明できます。

  struct DIR {
    int fd_;
    size_t available_bytes_;
    dirent* next_;
    pthread_mutex_t mutex_;
    dirent buff_[15];
    long current_pos_;
  };

実際には、ほとんどの場合この分析はスキップできます。一般的に、フォールト アドレスが十分に低い番号であれば、スタック内の libc.so フレームをすべてスキップして、呼び出し元コードを直接見つけられます。ただし、必ずしもそうとは限らず、上記のような分析が必要になるケースもあります。

このタイプのクラッシュのインスタンスを再現するには、crasher fprintf-NULL または crasher readdir-NULL を使用します。

FORTIFY エラー

FORTIFY エラーは、C ライブラリがセキュリティ脆弱性につながる可能性がある問題を検出した場合に発生する、中止の特殊なケースです。多くの C ライブラリ関数には FORTIFY が実装されています。これらは、バッファの実際の大きさを知らせる追加の引数を受け取り、実行しようとしているオペレーションが実際に適切かどうかを実行時にチェックします。実際には 10 バイトしかないバッファに対して read(fd, buf, 32) を実行しようとするコードの例を次に示します。

pid: 25579, tid: 25579, name: crasher  >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'FORTIFY: read: prevented 32-byte write into 10-byte buffer'
    r0 00000000  r1 000063eb  r2 00000006  r3 00000008
    r4 ff96f350  r5 000063eb  r6 000063eb  r7 0000010c
    r8 00000000  r9 00000000  sl 00000000  fp ff96f49c
    ip 00000000  sp ff96f340  lr ee83ece3  pc ee86ef0c  cpsr 000d0010

backtrace:
    #00 pc 00049f0c  /system/lib/libc.so (tgkill+12)
    #01 pc 00019cdf  /system/lib/libc.so (abort+50)
    #02 pc 0001e197  /system/lib/libc.so (__fortify_fatal+30)
    #03 pc 0001baf9  /system/lib/libc.so (__read_chk+48)
    #04 pc 0000165b  /system/xbin/crasher (do_action+534)
    #05 pc 000021e5  /system/xbin/crasher (main+100)
    #06 pc 000177a1  /system/lib/libc.so (__libc_init+48)
    #07 pc 00001110  /system/xbin/crasher (_start+96)

このタイプのクラッシュのインスタンスを再現するには、crasher fortify を使用します。

-fstack-protector によって検出されるスタックの破損

コンパイラの -fstack-protector オプションは、バッファ オーバーランを防ぐために、オンスタック バッファを使用する関数にチェックを挿入します。このオプションは、プラットフォーム コードではデフォルトで有効になっていますが、アプリでは有効になっていません。このオプションを有効にすると、コンパイラによって関数プロローグに命令が追加され、スタック上の最後のローカルの直後にランダム値が書き込まれます。また、関数エピローグに命令が追加され、その値が再び読み取られて変更されていないかどうかがチェックされます。値が変更されていれば、バッファ オーバーランによって上書きされたことがわかるので、エピローグは __stack_chk_fail を呼び出してメッセージをログに記録し、実行を中止します。

pid: 26717, tid: 26717, name: crasher  >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'stack corruption detected'
    r0 00000000  r1 0000685d  r2 00000006  r3 00000008
    r4 ffd516d8  r5 0000685d  r6 0000685d  r7 0000010c
    r8 00000000  r9 00000000  sl 00000000  fp ffd518bc
    ip 00000000  sp ffd516c8  lr ee63ece3  pc ee66ef0c  cpsr 000e0010

backtrace:
    #00 pc 00049f0c  /system/lib/libc.so (tgkill+12)
    #01 pc 00019cdf  /system/lib/libc.so (abort+50)
    #02 pc 0001e07d  /system/lib/libc.so (__libc_fatal+24)
    #03 pc 0004863f  /system/lib/libc.so (__stack_chk_fail+6)
    #04 pc 000013ed  /system/xbin/crasher (smash_stack+76)
    #05 pc 00001591  /system/xbin/crasher (do_action+280)
    #06 pc 00002219  /system/xbin/crasher (main+100)
    #07 pc 000177a1  /system/lib/libc.so (__libc_init+48)
    #08 pc 00001144  /system/xbin/crasher (_start+96)

この種類の中止は、バックトレースに __stack_chk_fail が存在することと、特定の abort メッセージによって、他の種類の中止から区別できます。

このタイプのクラッシュのインスタンスを再現するには、crasher smash-stack を使用します。

許可されていないシステムコールからの Seccomp SIGSYS

seccomp システム(特に seccomp-bpf)は、システムコールへのアクセスを制限します。プラットフォーム デベロッパー向けの seccomp の詳細については、Android O の seccomp フィルタに関するブログ投稿をご覧ください。制限されたシステムコールを呼び出すスレッドは、コード SYS_SECCOMP を含む SIGSYS シグナルを受信します。システムコール番号は、アーキテクチャとともに、原因を示す行に表示されます。システムコール番号は、アーキテクチャによって異なることにご注意ください。たとえば、readlinkat(2) システムコールの番号は、x86 では 305、x86-64 では 267 です。コール番号は、arm と arm64 でも異なります。システムコール番号はアーキテクチャによって異なるので、ヘッダーでシステムコール番号を探すよりも、スタック トレースを使用して許可されていないシステムコールを調べるほうが簡単です。

pid: 11046, tid: 11046, name: crasher  >>> crasher <<<
signal 31 (SIGSYS), code 1 (SYS_SECCOMP), fault addr --------
Cause: seccomp prevented call to disallowed arm system call 99999
    r0 cfda0444  r1 00000014  r2 40000000  r3 00000000
    r4 00000000  r5 00000000  r6 00000000  r7 0001869f
    r8 00000000  r9 00000000  sl 00000000  fp fffefa58
    ip fffef898  sp fffef888  lr 00401997  pc f74f3658  cpsr 600f0010

backtrace:
    #00 pc 00019658  /system/lib/libc.so (syscall+32)
    #01 pc 00001993  /system/bin/crasher (do_action+1474)
    #02 pc 00002699  /system/bin/crasher (main+68)
    #03 pc 0007c60d  /system/lib/libc.so (__libc_init+48)
    #04 pc 000011b0  /system/bin/crasher (_start_main+72)

許可されていないシステムコールは、シグナルの行に SYS_SECCOMP が存在すること、および原因を示す行の説明によって、他のクラッシュから区別できます。

このタイプのクラッシュのインスタンスを再現するには、crasher seccomp を使用します。

実行専用メモリ違反(Android 10 のみ)

Android 10 の arm64 では、バイナリとライブラリの実行可能セグメントは、コード再利用攻撃に対するセキュリティ強化対策として、実行専用(読み取り不可)メモリにマッピングされていました。この緩和策は、他の緩和策とうまく調和しないため、その後削除されました。

コードを読み取り不可にすると、実行専用としてマークされたメモリ セグメントに対する意図的な読み取りと意図的でない読み取りによって、コード SEGV_ACCERR を含む SIGSEGV がスローされます。これは、バグ、脆弱性、コードが混在するデータ(リテラルプールなど)、または意図的なメモリ イントロスペクションの結果として起こります。

コンパイラはコードとデータが混在していないと想定しますが、手書きのアセンブリが原因で問題が発生することがあります。多くの場合、この問題は定数を .data セクションに移動するだけで解決できます。実行可能コード セクションでコード イントロスペクションが必要不可欠な場合は、最初に mprotect(2) を呼び出してコードを読み取り可能としてマークし、オペレーションの完了後に再度読み取り不可としてマークする必要があります。

pid: 2938, tid: 2940, name: crasher64  >>> crasher64 <<<
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x5f2ced24a8
Cause: execute-only (no-read) memory access error; likely due to data in .text.
    x0  0000000000000000  x1  0000005f2cecf21f  x2  0000000000000078  x3  0000000000000053
    x4  0000000000000074  x5  8000000000000000  x6  ff71646772607162  x7  00000020dcf0d16c
    x8  0000005f2ced24a8  x9  000000781251c55e  x10 0000000000000000  x11 0000000000000000
    x12 0000000000000014  x13 ffffffffffffffff  x14 0000000000000002  x15 ffffffffffffffff
    x16 0000005f2ced52f0  x17 00000078125c0ed8  x18 0000007810e8e000  x19 00000078119fbd50
    x20 00000078125d6020  x21 00000078119fbd50  x22 00000b7a00000b7a  x23 00000078119fbdd8
    x24 00000078119fbd50  x25 00000078119fbd50  x26 00000078119fc018  x27 00000078128ea020
    x28 00000078119fc020  x29 00000078119fbcb0
    sp  00000078119fba40  lr  0000005f2ced1b94  pc  0000005f2ced1ba4

backtrace:
      #00 pc 0000000000003ba4  /system/bin/crasher64 (do_action+2348)
      #01 pc 0000000000003234  /system/bin/crasher64 (thread_callback+44)
      #02 pc 00000000000e2044  /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+36)
      #03 pc 0000000000083de0  /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64)

実行専用メモリ違反は、原因を示す行によって他のクラッシュから区別できます。

このタイプのクラッシュのインスタンスを再現するには、crasher xom を使用します。

fdsan によって検出されるエラー

Android のファイル記述子サニタイザー(fdsan)は、use-after-close や double-close などのファイル記述子によくある誤りをキャッチするのに役立ちます。この種類のエラーのデバッグと防止については、fdsan のドキュメントをご覧ください。

pid: 32315, tid: 32315, name: crasher64  >>> crasher64 <<<
signal 35 (), code -1 (SI_QUEUE), fault addr --------
Abort message: 'attempted to close file descriptor 3, expected to be unowned, actually owned by FILE* 0x7d8e413018'
    x0  0000000000000000  x1  0000000000007e3b  x2  0000000000000023  x3  0000007fe7300bb0
    x4  3033313465386437  x5  3033313465386437  x6  3033313465386437  x7  3831303331346538
    x8  00000000000000f0  x9  0000000000000000  x10 0000000000000059  x11 0000000000000034
    x12 0000007d8ebc3a49  x13 0000007fe730077a  x14 0000007fe730077a  x15 0000000000000000
    x16 0000007d8ec9a7b8  x17 0000007d8ec779f0  x18 0000007d8f29c000  x19 0000000000007e3b
    x20 0000000000007e3b  x21 0000007d8f023020  x22 0000007d8f3b58dc  x23 0000000000000001
    x24 0000007fe73009a0  x25 0000007fe73008e0  x26 0000007fe7300ca0  x27 0000000000000000
    x28 0000000000000000  x29 0000007fe7300c90
    sp  0000007fe7300860  lr  0000007d8ec2f22c  pc  0000007d8ec2f250

backtrace:
      #00 pc 0000000000088250  /bionic/lib64/libc.so (fdsan_error(char const*, ...)+384)
      #01 pc 0000000000088060  /bionic/lib64/libc.so (android_fdsan_close_with_tag+632)
      #02 pc 00000000000887e8  /bionic/lib64/libc.so (close+16)
      #03 pc 000000000000379c  /system/bin/crasher64 (do_action+1316)
      #04 pc 00000000000049c8  /system/bin/crasher64 (main+96)
      #05 pc 000000000008021c  /bionic/lib64/libc.so (_start_main)

この種類の中止は、バックトレースに fdsan_error が存在することと、特定の abort メッセージによって、他の種類の中止から区別できます。

このタイプのクラッシュのインスタンスを再現するには、crasher fdsan_file または crasher fdsan_dir を使用します。

クラッシュ ダンプを調査する

現在、調査対象の具体的なクラッシュがない場合は、プラットフォーム ソースに含まれているクラッシャーというツールで debuggerd をテストできます。system/core/debuggerd/mm を実行すると、パス上の crashercrasher64 の両方が取得されます(後者では 64 ビットのクラッシュをテストできます)。クラッシャーは、指定するコマンドライン引数に基づいて、さまざまな興味深い方法でクラッシュします。crasher --help を使用すると、現在サポートされている選択肢が表示されます。

クラッシュ ダンプのさまざまな部分を調べるため、次のクラッシュ ダンプの例を見てみましょう。

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys'
Revision: '0'
ABI: 'arm'
pid: 1656, tid: 1656, name: crasher  >>> crasher <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: 'some_file.c:123: some_function: assertion "false" failed'
    r0 00000000  r1 00000678  r2 00000006  r3 f70b6dc8
    r4 f70b6dd0  r5 f70b6d80  r6 00000002  r7 0000010c
    r8 ffffffed  r9 00000000  sl 00000000  fp ff96ae1c
    ip 00000006  sp ff96ad18  lr f700ced5  pc f700dc98  cpsr 400b0010
backtrace:
    #00 pc 00042c98  /system/lib/libc.so (tgkill+12)
    #01 pc 00041ed1  /system/lib/libc.so (pthread_kill+32)
    #02 pc 0001bb87  /system/lib/libc.so (raise+10)
    #03 pc 00018cad  /system/lib/libc.so (__libc_android_abort+34)
    #04 pc 000168e8  /system/lib/libc.so (abort+4)
    #05 pc 0001a78f  /system/lib/libc.so (__libc_fatal+16)
    #06 pc 00018d35  /system/lib/libc.so (__assert2+20)
    #07 pc 00000f21  /system/xbin/crasher
    #08 pc 00016795  /system/lib/libc.so (__libc_init+44)
    #09 pc 00000abc  /system/xbin/crasher
Tombstone written to: /data/tombstones/tombstone_06
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

スペースを含むアスタリスクの行は、ネイティブ クラッシュのログを検索する際に役立ちます。ログの中で、「*** ***」という文字列がネイティブ クラッシュの冒頭以外で表示されることはほとんどありません。

Build fingerprint:
'Android/aosp_flounder/flounder:5.1.51/AOSP/enh08201009:eng/test-keys'

フィンガープリントにより、クラッシュが発生したビルドを正確に特定できます。これは、ro.build.fingerprint システム プロパティとまったく同じです。

Revision: '0'

リビジョンは、ソフトウェアではなくハードウェアを指します。これは通常は使用されませんが、ハードウェアの不具合により発生したことがわかっているバグを自動的に無視するのに役立ちます。これは、ro.revision システム プロパティとまったく同じです。

ABI: 'arm'

ABI は、arm、arm64、x86、x86-64 のいずれかです。これは、主に上記の stack スクリプトで、使用すべきツールチェーンを知るために役立ちます。

pid: 1656, tid: 1656, name: crasher >>> crasher <<<

この行は、クラッシュしたプロセス内の特定のスレッドを示します。この場合は、プロセスのメインスレッドであるため、プロセス ID とスレッド ID は一致します。最初の名前はスレッド名で、>>> と <<< で囲まれた名前はプロセス名です。アプリでは、プロセス名は一般的に完全修飾パッケージ名(com.facebook.katana など)です。これは、バグの報告や Google Play でのアプリの検索に便利です。pid と tid は、クラッシュに先行する関連ログ行を見つける際にも役立ちます。

signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------

この行は、受信されたシグナル(SIGABRT)と、シグナルが受信された方法(SI_TKILL)を示します。debuggerd によってレポートされるシグナルは、SIGABRT、SIGBUS、SIGFPE、SIGILL、SIGSEGV、SIGTRAP です。シグナル固有のコードは、具体的なシグナルによって異なります。

Abort message: 'some_file.c:123: some_function: assertion "false" failed'

すべてのクラッシュに abort メッセージ行があるとは限りませんが、中止には abort メッセージ行があります。これは、この pid / tid の fatal な logcat 出力の最後の行から自動的に収集されます。意図的な中止の場合、たいていはプログラムが強制終了した理由が表示されます。

r0 00000000 r1 00000678 r2 00000006 r3 f70b6dc8
r4 f70b6dd0 r5 f70b6d80 r6 00000002 r7 0000010c
r8 ffffffed r9 00000000 sl 00000000 fp ff96ae1c
ip 00000006 sp ff96ad18 lr f700ced5 pc f700dc98 cpsr 400b0010

レジスタダンプは、シグナルが受信されたときの CPU レジスタの内容を示します(このセクションは ABI によって大きく異なります)。どの程度役に立つかは具体的なクラッシュ次第です。

backtrace:
    #00 pc 00042c98 /system/lib/libc.so (tgkill+12)
    #01 pc 00041ed1 /system/lib/libc.so (pthread_kill+32)
    #02 pc 0001bb87 /system/lib/libc.so (raise+10)
    #03 pc 00018cad /system/lib/libc.so (__libc_android_abort+34)
    #04 pc 000168e8 /system/lib/libc.so (abort+4)
    #05 pc 0001a78f /system/lib/libc.so (__libc_fatal+16)
    #06 pc 00018d35 /system/lib/libc.so (__assert2+20)
    #07 pc 00000f21 /system/xbin/crasher
    #08 pc 00016795 /system/lib/libc.so (__libc_init+44)
    #09 pc 00000abc /system/xbin/crasher

バックトレースは、クラッシュ時のコードの位置を示します。最初の列はフレーム番号です(gdb のスタイルと同じく、最も深いフレームは 0 です)。PC 値は絶対アドレスではなく、共有ライブラリの相対位置です。次の列は、マッピングされた領域の名前です(通常は共有ライブラリまたは実行可能ファイルですが、JIT でコンパイルされたコードなどでは、そうでない可能性があります)。最後に、シンボルが使用可能な場合、PC 値が対応するシンボルが、そのシンボルへのオフセット(バイト単位)とともに表示されます。これは、objdump(1) と組み合わせて、対応するアセンブラ命令を検索するために使用できます。

Tombstone を調査する

Tombstone written to: /data/tombstones/tombstone_06

この行により、debuggerd がどこに追加情報を書き込んだかがわかります。debuggerd は最大 10 個の Tombstone を保持し、00~09 の番号を周期的に使用します。必要な場合は、既存の Tombstone を上書きします。

Tombstone は、クラッシュ ダンプと同じ情報に加えて、いくつかの追加情報を示します。たとえば、(クラッシュしたスレッドだけでなく)すべてのスレッド、浮動小数点レジスタ、未加工のスタックダンプ、レジスタ内のアドレス周辺のメモリダンプなどです。中でも最も有用なのは、(/proc/pid/maps に似た)フルメモリ マップです。32 ビット ARM プロセスがクラッシュした例をアノテーション付きで以下に示します。

memory map: (fault address prefixed with --->)
--->ab15f000-ab162fff r-x 0 4000 /system/xbin/crasher (BuildId:
b9527db01b5cf8f5402f899f64b9b121)

ここで注目すべき点が 2 つあります。1 つ目は、この行にプレフィックス「--->」が付いていることです。クラッシュが単なる null ポインタ逆参照でない場合、マップは大変役立ちます。フォールト アドレスが小さい場合は、おそらく null ポインタ逆参照の一種です。そうでない場合は、フォールト アドレス周辺のマップを見ると、何が起きたかを理解する手がかりを得られることがよくあります。マップを見ると、次のような問題が考えられます。

  • メモリブロックが終わった後の読み取り / 書き込み。
  • メモリブロックが始まる前の読み取り / 書き込み。
  • コード以外を実行しようとした。
  • スタックの終わりの実行。
  • コードに書き込もうとした(上記の例を参照)。

注目すべき 2 つ目の点は、Android 6.0 以上では実行可能ファイルと共有ライブラリ ファイルは BuildId を(もしあれば)表示するため、クラッシュしたコードのバージョンが正確にわかることです。Android 6.0 以降のプラットフォーム バイナリにはデフォルトで BuildId が含まれており、NDK r12 以上では -Wl,--build-id も自動的にリンカーに渡します。

ab163000-ab163fff r--      3000      1000  /system/xbin/crasher
ab164000-ab164fff rw-         0      1000
f6c80000-f6d7ffff rw-         0    100000  [anon:libc_malloc]

Android では、ヒープは単一の領域であるとは限りません。ヒープ領域にはラベル [anon:libc_malloc] が付けられます。

f6d82000-f6da1fff r--         0     20000  /dev/__properties__/u:object_r:logd_prop:s0
f6da2000-f6dc1fff r--         0     20000  /dev/__properties__/u:object_r:default_prop:s0
f6dc2000-f6de1fff r--         0     20000  /dev/__properties__/u:object_r:logd_prop:s0
f6de2000-f6de5fff r-x         0      4000  /system/lib/libnetd_client.so (BuildId: 08020aa06ed48cf9f6971861abf06c9d)
f6de6000-f6de6fff r--      3000      1000  /system/lib/libnetd_client.so
f6de7000-f6de7fff rw-      4000      1000  /system/lib/libnetd_client.so
f6dec000-f6e74fff r-x         0     89000  /system/lib/libc++.so (BuildId: 8f1f2be4b37d7067d366543fafececa2) (load base 0x2000)
f6e75000-f6e75fff ---         0      1000
f6e76000-f6e79fff r--     89000      4000  /system/lib/libc++.so
f6e7a000-f6e7afff rw-     8d000      1000  /system/lib/libc++.so
f6e7b000-f6e7bfff rw-         0      1000  [anon:.bss]
f6e7c000-f6efdfff r-x         0     82000  /system/lib/libc.so (BuildId: d189b369d1aafe11feb7014d411bb9c3)
f6efe000-f6f01fff r--     81000      4000  /system/lib/libc.so
f6f02000-f6f03fff rw-     85000      2000  /system/lib/libc.so
f6f04000-f6f04fff rw-         0      1000  [anon:.bss]
f6f05000-f6f05fff r--         0      1000  [anon:.bss]
f6f06000-f6f0bfff rw-         0      6000  [anon:.bss]
f6f0c000-f6f21fff r-x         0     16000  /system/lib/libcutils.so (BuildId: d6d68a419dadd645ca852cd339f89741)
f6f22000-f6f22fff r--     15000      1000  /system/lib/libcutils.so
f6f23000-f6f23fff rw-     16000      1000  /system/lib/libcutils.so
f6f24000-f6f31fff r-x         0      e000  /system/lib/liblog.so (BuildId: e4d30918d1b1028a1ba23d2ab72536fc)
f6f32000-f6f32fff r--      d000      1000  /system/lib/liblog.so
f6f33000-f6f33fff rw-      e000      1000  /system/lib/liblog.so

一般的に、共有ライブラリには 3 つの隣接するエントリがあります。1 つは読み取り可能で実行可能(コード)、1 つは読み取り専用(読み取り専用データ)、もう 1 つは読み取り / 書き込み用(変更可能データ)です。最初の列はマッピングのアドレス範囲を示し、2 番目の列はアクセス許可(一般的な Unix の ls(1) スタイルで表現)を示します。3 番目の列はファイルへのオフセット(16 進数で表現)、4 番目の列は領域のサイズ(16 進数で表現)、5 番目の列はファイル(または他の領域名)を示します。

f6f34000-f6f53fff r-x         0     20000  /system/lib/libm.so (BuildId: 76ba45dcd9247e60227200976a02c69b)
f6f54000-f6f54fff ---         0      1000
f6f55000-f6f55fff r--     20000      1000  /system/lib/libm.so
f6f56000-f6f56fff rw-     21000      1000  /system/lib/libm.so
f6f58000-f6f58fff rw-         0      1000
f6f59000-f6f78fff r--         0     20000  /dev/__properties__/u:object_r:default_prop:s0
f6f79000-f6f98fff r--         0     20000  /dev/__properties__/properties_serial
f6f99000-f6f99fff rw-         0      1000  [anon:linker_alloc_vector]
f6f9a000-f6f9afff r--         0      1000  [anon:atexit handlers]
f6f9b000-f6fbafff r--         0     20000  /dev/__properties__/properties_serial
f6fbb000-f6fbbfff rw-         0      1000  [anon:linker_alloc_vector]
f6fbc000-f6fbcfff rw-         0      1000  [anon:linker_alloc_small_objects]
f6fbd000-f6fbdfff rw-         0      1000  [anon:linker_alloc_vector]
f6fbe000-f6fbffff rw-         0      2000  [anon:linker_alloc]
f6fc0000-f6fc0fff r--         0      1000  [anon:linker_alloc]
f6fc1000-f6fc1fff rw-         0      1000  [anon:linker_alloc_lob]
f6fc2000-f6fc2fff r--         0      1000  [anon:linker_alloc]
f6fc3000-f6fc3fff rw-         0      1000  [anon:linker_alloc_vector]
f6fc4000-f6fc4fff rw-         0      1000  [anon:linker_alloc_small_objects]
f6fc5000-f6fc5fff rw-         0      1000  [anon:linker_alloc_vector]
f6fc6000-f6fc6fff rw-         0      1000  [anon:linker_alloc_small_objects]
f6fc7000-f6fc7fff rw-         0      1000  [anon:arc4random _rsx structure]
f6fc8000-f6fc8fff rw-         0      1000  [anon:arc4random _rs structure]
f6fc9000-f6fc9fff r--         0      1000  [anon:atexit handlers]
f6fca000-f6fcafff ---         0      1000  [anon:thread signal stack guard page]

Android 5.0 以降では、C ライブラリはほとんどの匿名マッピング領域に名前を付けます。そのため、不明な領域が少なくなります。

f6fcb000-f6fccfff rw- 0 2000 [stack:5081]

[stack:tid] という名前の領域は、指定されたスレッドのスタックです。

f6fcd000-f702afff r-x         0     5e000  /system/bin/linker (BuildId: 84f1316198deee0591c8ac7f158f28b7)
f702b000-f702cfff r--     5d000      2000  /system/bin/linker
f702d000-f702dfff rw-     5f000      1000  /system/bin/linker
f702e000-f702ffff rw-         0      2000
f7030000-f7030fff r--         0      1000
f7031000-f7032fff rw-         0      2000
ffcd7000-ffcf7fff rw-         0     21000
ffff0000-ffff0fff r-x         0      1000  [vectors]

[vector][vdso] のどちらが表示されるかは、アーキテクチャ次第です。ARM では [vector] が使用され、他のすべてのアーキテクチャでは [vdso] が使用されます。