比較的新しいデバイスを使って開発をしていて、何も問題なく動いていたアプリが、古いデバイスでテストしてみたらいきなりクラッシュして起動しないというバグにハマってしまった。
クラッシュはネイティブコードの中なのだけど、.soファイルがロードされて、JNI_OnLoadが呼ばれる前にクラッシュする。デバッガがアタッチされる前にクラッシュするものだからちょっと困る。
クラッシュ時のログの中身はこんな感じ。

JNI_OnLoadが呼ばれる前にクラッシュするものだから、始めはグローバルコンストラクタを疑ってグローバル変数であやしいコンストラクタを呼んでる箇所を取り除いてみたけどそれでもダメ。
クラッシュした時のプログラムカウンタの値からどの関数でクラッシュしたのかをつきとめるしかない。

ちなみに、Google検索でいろいろ調べていたらわかったんだけど、0xdeadbaad というアドレスで SIGSEGV が起きているのは abort() でクラッシュしたということらしい(Android固有の実装?)。つまり、何者かが assert を仕込んでいてそれにひっかかったということみたい。

だからコールスタックの一番上の

はきっと abort() なのでしょう。シンボル情報がないのでわからないけど。

で、問題なのは次の

これをndk-stackを使って調べてみると・・・

こんなん出ました!
なんと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更新は使ってはいけないということでした。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Anti Spam Code *