比較的新しいデバイスを使って開発をしていて、何も問題なく動いていたアプリが、古いデバイスでテストしてみたらいきなりクラッシュして起動しないというバグにハマってしまった。
クラッシュはネイティブコードの中なのだけど、.soファイルがロードされて、JNI_OnLoadが呼ばれる前にクラッシュする。デバッガがアタッチされる前にクラッシュするものだからちょっと困る。
クラッシュ時のログの中身はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
03-31 16:57:00.015 7070-7070/? I/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 03-31 16:57:00.015 7070-7070/? I/DEBUG: Build fingerprint: 'samsung/SC-02C/SC-02C:2.3.6/GINGERBREAD/OMLE1:user/release-keys' 03-31 16:57:00.015 7070-7070/? I/DEBUG: pid: 23705, tid: 23705 >>> com.nyahoon.chocolatesweeper <<< 03-31 16:57:00.015 7070-7070/? I/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad 03-31 16:57:00.015 7070-7070/? I/DEBUG: r0 00000027 r1 deadbaad r2 a0000000 r3 00000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: r4 00000001 r5 00000000 r6 00000004 r7 001561ac 03-31 16:57:00.015 7070-7070/? I/DEBUG: r8 00000000 r9 0000d088 10 beca1454 fp 00000001 03-31 16:57:00.015 7070-7070/? I/DEBUG: ip afd466a8 sp beca12f8 lr afd196f1 pc afd161c0 cpsr 60000030 03-31 16:57:00.015 7070-7070/? I/DEBUG: d0 435a7ea04271872b d1 3ff000004359b6db 03-31 16:57:00.015 7070-7070/? I/DEBUG: d2 4d8521ef424ab7ea d3 42c8000000540ff0 03-31 16:57:00.015 7070-7070/? I/DEBUG: d4 40de3000002a9a38 d5 0000000049000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d6 4515a0000006f180 d7 3fc000003f4ccccd 03-31 16:57:00.015 7070-7070/? I/DEBUG: d8 0000000000000000 d9 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d10 0000000000000000 d11 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d12 0000000000000000 d13 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d14 0000000000000000 d15 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d16 8021c8dc40567068 d17 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d18 42eccefa43de3400 d19 3fbc71c71c71c71c 03-31 16:57:00.015 7070-7070/? I/DEBUG: d20 4008000000000000 d21 3fd99a27ad32ddf5 03-31 16:57:00.015 7070-7070/? I/DEBUG: d22 3fd24998d6307188 d23 3fcc7288e957b53b 03-31 16:57:00.015 7070-7070/? I/DEBUG: d24 3fc74721cad6b0ed d25 3fc39a09d078c69f 03-31 16:57:00.015 7070-7070/? I/DEBUG: d26 0000000000000000 d27 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d28 0000000000000000 d29 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: d30 0000000000000000 d31 0000000000000000 03-31 16:57:00.015 7070-7070/? I/DEBUG: scr 60000010 03-31 16:57:00.070 7070-7070/? I/DEBUG: #00 pc 000161c0 /system/lib/libc.so 03-31 16:57:00.070 7070-7070/? I/DEBUG: #01 pc 0092f17c /data/data/com.nyahoon.chocolatesweeper/lib/libcs.so 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd161a0 2c006824 e028d1fb b13368db c064f8df 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd161b0 44fc2401 4000f8cc 49124798 25002027 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd161c0 f7f57008 2106ea16 eb8af7f6 460aa901 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd161d0 f04f2006 95015380 95029303 eef0f7f5 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd161e0 462aa905 f7f52002 f7f5eefc 2106ea02 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd196d0 4a0e4b0d e92d447b 589c41f0 26004680 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd196e0 686768a5 f9b5e006 b113300c 47c04628 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd196f0 35544306 37fff117 6824d5f5 d1ef2c00 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd19700 e8bd4630 bf0081f0 00027e6c ffffff88 03-31 16:57:00.070 7070-7070/? I/DEBUG: afd19710 b086b570 f602fb01 9004460c a804a901 |
JNI_OnLoadが呼ばれる前にクラッシュするものだから、始めはグローバルコンストラクタを疑ってグローバル変数であやしいコンストラクタを呼んでる箇所を取り除いてみたけどそれでもダメ。
クラッシュした時のプログラムカウンタの値からどの関数でクラッシュしたのかをつきとめるしかない。
ちなみに、Google検索でいろいろ調べていたらわかったんだけど、0xdeadbaad というアドレスで SIGSEGV が起きているのは abort() でクラッシュしたということらしい(Android固有の実装?)。つまり、何者かが assert を仕込んでいてそれにひっかかったということみたい。
だからコールスタックの一番上の
1 |
#00 pc 000161c0 /system/lib/libc.so |
はきっと abort() なのでしょう。シンボル情報がないのでわからないけど。
で、問題なのは次の
1 |
#01 pc 0092f17c /data/data/com.nyahoon.chocolatesweeper/lib/libcs.so |
これをndk-stackを使って調べてみると・・・
1 |
Stack frame #01 pc 0092f17c /data/data/com.nyahoon.chocolatesweeper/lib/libcs.so: Routine __check_for_sync8_kernelhelper at /Volumes/Android/buildbot/src/android/gcc/toolchain/build/../gcc/gcc-4.9/libgcc/config/arm/linux-atomic-64bit.c:64 |
こんなん出ました!
なんとAtomic更新。どうも8バイト変数にAtomic更新が使えるかどうかチェックしていて、使えないデバイスだから親切にクラッシュしてくれたみたい! 誤動作して意味不明なバグになるよりいいもんね!!!
でも、コンパイル時に教えて欲しいよね〜。
これなら古いデバイスで、しかもJNI_OnLoadの前にクラッシュするというのも納得がいく。
__check_for_sync8_kernelhelper でググってみるとここにたどりつく。
https://code.google.com/p/android/issues/detail?id=58476
これによると古いコンパイラならクラッシュしないみたいだけど、それってAtomic更新が正しく処理されないってことなので、古いコンパイラにしても意味がない。
もうちょっと調べるとこんなのも見つかった。
https://gcc.gnu.org/wiki/Atomic
これによると、GCC4.8以降でビルドすれば、Linux3.1以降なら8バイトのアトミック更新ができるらしい。Androidでも古いOSだと(もしくは非64ビットデバイスだと)クラッシュするのかもしれない。手持ちのデバイスでは少なくともAndroid 4.1.1でもまだクラッシュしている。
まあ何にせよ、古いデバイス、古いOSをサポートするには64ビットのAtomic更新は使ってはいけないということでした。