以下のセクションでは、ネイティブ コードでのクラッシュ(以下「ネイティブ クラッシュ」)の一般的なタイプを示し、サンプル クラッシュ ダンプの分析と 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
を実行すると、パス上の crasher
と crasher64
の両方が取得されます(後者では 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]
が使用されます。