Unity Android でよく見かけるクラッシュログを読んでみよう

LINEで送る
Pocket

みなさま、初めまして!

クリエイティブチームではめずらしく PHP のエンジニアをやっております yokada です。

今日は Unity で書きだした Android ゲームがクラッシュした時によく見かけるクラッシュログをテーマに Android OS が実際に実行している ARM アセンブラのコードを眺めてみましょう。

Android ゲームを logcat などでデバッグ中、次のようなクラッシュログが表示されることがあります。

java.lang.Error: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 67101f9c
Build fingerprint: 'samsung/d2om/d2dcm:4.1.2/JZO54K/SC06DOMBMK2:user/release-keys'
Revision: '10'
pid: 5880, tid: 5912, name: UnityMain  >>> **.**.****.****.**** <<<
    r0 0000009c  r1 00000000  r2 00000000  r3 6ce12d60
    r4 00000027  r5 00036f88  r6 00000080  r7 00000004
    r8 699d2640  r9 00000000  sl 5e9bd888  fp 5e9bd9d8
    ip 00000001  sp 5e9bd808  lr 67101f00  pc 400574e8  cpsr 424d4f44

	at libc.pthread_key_delete(pthread_key_delete:196)
	at libunity.006e20f4(Native Method)
	at libunity.006ece0c(Native Method)
	at libunity.006ed8ec(Native Method)
	at libunity.006e57b8(Native Method)
	at libunity.0065f448(Native Method)
	at libunity.0065f90c(Native Method)
	at libunity.0065319c(Native Method)
	at libunity.001615f8(Native Method)
	at libunity.00160940(Native Method)
	at libunity.0011e128(Native Method)
	at libunity.0029f908(Native Method)
	at libunity.0029df2c(Native Method)
	at libunity.002a276c(Native Method)
	at libunity.002a1b3c(Native Method)
	at libunity.0029f364(Native Method)
	at libunity.002a1070(Native Method)
	at libunity.002a01f0(Native Method)
	at libunity.003d24e4(Native Method)
	at libunity.003de1b0(Native Method)
	at libdvm.dvmPlatformInvoke(dvmPlatformInvoke:112)
	at libdvm.dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)(dvmCallJNIMethod:360)
	at dalvik-jit-code-cache (deleted).00000214(Native Method)

普段 PHP しか触っていない自分はもはや何が何やらわかりません。一見すると Java の Exception ログのようにも見えますのでコールスタックを表現している、また、その仮説が正しいとして一番上の pthread_key_delete にてクラッシュが発生してるということが想像できますので、これを確かめたいと思います。

  • ※ libc や libunity というのは Linux の *.so を指します

libdvm.so と libc.so に関するコールスタックは関数名と仮引数が見えていますが libunity.so に関するコールスタックは関数名も仮引数も見えません。これを逆アセンブルして先の仮説が正しいかどうか見ていきたいと思います。

まず *.so のコードを確認するために gobjdump をインストールします。

$ brew install binutils

次に libunity.so を取得します。 libunity.so はゲームの APK ファイルに含まれていますので、これを展開します。

$ unzip the-game.apk

libunity.so は展開したディレクトリの lib/armeabi-v7a 以下にあります。 libunity.so が見つかったら gobjdump コマンドを使って中身を見てみます。

$ gobjdump --target=elf32-littlearm -d -C ./lib/armeabi-v7a/libunity.so

gobjdump コマンドを実行すると次のような出力を得ることができます(抜粋)。

…
   56e38:       e08a1001        add     r1, sl, r1
   56e3c:       e58d1000        str     r1, [sp]
   56e40:       e58d0004        str     r0, [sp, #4]
   56e44:       e3a0000c        mov     r0, #12
   56e48:       e3a01010        mov     r1, #16
   56e4c:       e3a05000        mov     r5, #0
   56e50:       eb017b4d        bl      b5b8c <__gnu_uldivmod_helper@plt+0x5fb14>
   56e54:       e3500000        cmp     r0, #0
   56e58:       0a000005        beq     56e74 <__gnu_uldivmod_helper@plt+0xdfc>
   56e5c:       e51f197c        ldr     r1, [pc, #-2428]        ; 564e8 <__gnu_uldivmod_helper@plt+0x470>
   56e60:       e5805000        str     r5, [r0]
   56e64:       e08f1001        add     r1, pc, r1
   56e68:       e5805004        str     r5, [r0, #4]
   56e6c:       e0861001        add     r1, r6, r1
   56e70:       e5801008        str     r1, [r0, #8]
   56e74:       e1a01004        mov     r1, r4
   56e78:       eb22f845        bl      914f94 <std::vector >::push_back(unsigned short const&)+0x109d74>
   56e7c:       e51f0998        ldr     r0, [pc, #-2456]        ; 564ec <__gnu_uldivmod_helper@plt+0x474>
   56e80:       e3e03000        mvn     r3, #0
   56e84:       e51f699c        ldr     r6, [pc, #-2460]        ; 564f0 <__gnu_uldivmod_helper@plt+0x478>
   56e88:       e51f199c        ldr     r1, [pc, #-2460]        ; 564f4 <__gnu_uldivmod_helper@plt+0x47c>
   56e8c:       e08f0000        add     r0, pc, r0
   56e90:       e0812000        add     r2, r1, r0
   56e94:       e1a01006        mov     r1, r6
   56e98:       e7a12000        str     r2, [r1, r0]!
…

うまく libunity.so の中身を表示させることができました。どうやらデマングルもうまくいっているようです。

では次に pthread_key_delete のひとつ下の libunity.006e20f4 という記述をヒントに 006e20f4 に該当するコードを見つけてみたいと思います。 gobjdump の出力の中から 6e20f4 に該当する行を検索します( 006e20f4 では見つからなかったため、左のゼロパディングを無視しました)。すると、次のようなコードが見つかります。

  6e20f4:       ebe5ca54        bl      54a4c 
<pthread_key_delete@plt>

なんと pthread_key_delete という記述が見つかりました。予想通り pthread_key_delete を呼び出している可能性が高そうです。どうやら鍵になるのは bl というインストラクション(ニーモニック)のようですので、これを ARM のドキュメントで確認してみましょう。すると次のような記述が見つかります。

B, BL, BX, BLX, and BXJ
Branch, Branch with Link, Branch and exchange instruction set, Branch with Link and exchange instruction set, Branch and change to Jazelle state.

さらに…

Operation
The BL and BLX instructions copy the address of the next instruction into lr (r14, the link register).

…とあります。このことから次のことがわかります。

  • ● bl は分岐のための命令である。つまりプログラムカウンターの値を指定したアドレスに変更することができる。
  • ● bl は次の命令のアドレスを r14 というレジスタにコピーする。つまり r14 の値をプログラムカウンターに戻すことで、プログラムの実行を bl した時の次の命令から再開することができる。

つまり 6e20f4 に位置するコードは pthread_key_delete を関数呼び出ししている命令であり、先の予想は概ね正しそうだということがわかります(しかしながら pthread_key_delete の実態は libunity.so にはなく libc.so にあるようでした)。

同様にしてクラッシュログの 6e20f4 以下のアドレスに該当する命令がなんであるかも見ていくことで、クラッシュログに載っていたアドレスが bl かそれに準ずるコード(プログラムカウンターを書き換えているなど)であることを確認することができます。

いかがでしたでしょうか。普段見慣れないクラッシュログもある程度は見える形に持っていけるということがわかりました。興味のある方は関数呼び出しの際、引数をどうやって渡すのか? また、戻り値をどうやって受け取るのか? など調べてみると面白いかもしれません。アセンブラを眺めるのが初めての方は if 分の実装なども面白いと思っていただけるかと思います。

ただ、一番安心できるのはこのログに遭遇しないことなのかもしれませんね。