Unityで作ったアプリから、AndroidのWebViewをnewするだけのコードが特定機種で落ちる不具合を発見してから、原因を特定するための調査をした際の備忘録です。
問題
問題の再現方法
Unity 4.6.6で、以下のコードをGalaxy系の4.3(S4, Jなど)で走らせる。
AndroidJavaObject activity = null;
using (var cls = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
activity = cls.GetStatic<AndroidJavaObject>("currentActivity");
}
ativity.Call("runOnUiThread", new AndroidJavaRunnable(() => {
using (var webView = new AndroidJavaObject("android.webkit.WebView", activity)) {}
activity.Dispose();
}));
そうすると、以下のようなダンプをLogcatに吐いて落ちる。
I/DEBUG ( 202): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG ( 202): Build fingerprint: 'samsung/SC-04E/SC-04E:4.3/JSS15J/SC04EOMUBNB1:user/release-keys'
I/DEBUG ( 202): Revision: '15'
I/DEBUG ( 202): pid: 8741, tid: 8895, name: WebViewCoreThre >>> com.hoge <<<
I/DEBUG ( 202): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr bbadbeef
I/DEBUG ( 202): r0 0000000b r1 00000000 r2 00000001 r3 bbadbeef
I/DEBUG ( 202): r4 00000000 r5 741aa3e4 r6 78a7c468 r7 78a7c398
I/DEBUG ( 202): r8 78bf6558 r9 20100001 sl 78bc9d18 fp 1da00005
I/DEBUG ( 202): ip 00000001 sp 7c41fb60 lr 73c9847d pc 73c9847e cpsr 60000030
I/DEBUG ( 202): d0 0000000000000000 d1 0000000000000000
I/DEBUG ( 202): d2 0000000000000000 d3 0000000000000000
I/DEBUG ( 202): d4 000000003f800000 d5 0000000000000000
I/DEBUG ( 202): d6 bf80000000000000 d7 000000003f800000
I/DEBUG ( 202): d8 0000000000000000 d9 0000000000000000
I/DEBUG ( 202): d10 0000000000000000 d11 0000000000000000
I/DEBUG ( 202): d12 0000000000000000 d13 0000000000000000
I/DEBUG ( 202): d14 0000000000000000 d15 0000000000000000
I/DEBUG ( 202): d16 3ff0000000000000 d17 322f796470730631
I/DEBUG ( 202): d18 0064007200610068 d19 0065007200610077
I/DEBUG ( 202): d20 007300690064002e d21 00790061006c0070
I/DEBUG ( 202): d22 006900440049002e d23 0061006c00700073
I/DEBUG ( 202): d24 3fc74721cad6b0ed d25 3fc2f112df3e5244
I/DEBUG ( 202): d26 ffffffffffffffff d27 ffffffffffffffff
I/DEBUG ( 202): d28 0000000000000000 d29 0000000000000000
I/DEBUG ( 202): d30 0000000000000000 d31 0000000000000000
I/DEBUG ( 202): scr 60000010
I/DEBUG ( 202):
I/DEBUG ( 202): backtrace:
I/DEBUG ( 202): #00 pc 000ed47e /system/lib/libwebcore.so
I/DEBUG ( 202): #01 pc 000ed4cb /system/lib/libwebcore.so
I/DEBUG ( 202): #02 pc 000ede7b /system/lib/libwebcore.so
I/DEBUG ( 202): #03 pc 000ef447 /system/lib/libwebcore.so
I/DEBUG ( 202): #04 pc 000ef479 /system/lib/libwebcore.so
I/DEBUG ( 202): #05 pc 001670a9 /system/lib/libwebcore.so
I/DEBUG ( 202): #06 pc 0016352f /system/lib/libwebcore.so
I/DEBUG ( 202): #07 pc 0028298b /system/lib/libwebcore.so
I/DEBUG ( 202): #08 pc 00020c0c /system/lib/libdvm.so (dvmPlatformInvoke+112)
I/DEBUG ( 202): #09 pc 00051773 /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+398)
I/DEBUG ( 202): #10 pc 0002a0a0 /system/lib/libdvm.so
I/DEBUG ( 202): #11 pc 0002ea6c /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
I/DEBUG ( 202): #12 pc 00063905 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+292)
I/DEBUG ( 202): #13 pc 0006392f /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20)
I/DEBUG ( 202): #14 pc 00058683 /system/lib/libdvm.so
I/DEBUG ( 202): #15 pc 0000cfa0 /system/lib/libc.so (__thread_entry+72)
I/DEBUG ( 202): #16 pc 0000d11c /system/lib/libc.so (pthread_create+208)
I/DEBUG ( 202):
I/DEBUG ( 202): stack:
I/DEBUG ( 202): 7c41fb20 00000017
I/DEBUG ( 202): 7c41fb24 00000040
I/DEBUG ( 202): 7c41fb28 798630e0
I/DEBUG ( 202): 7c41fb2c 00000000
I/DEBUG ( 202): 7c41fb30 741aa3e4
I/DEBUG ( 202): 7c41fb34 7c41fc14 [stack:8895]
I/DEBUG ( 202): 7c41fb38 78a7c468
I/DEBUG ( 202): 7c41fb3c 73c98389 /system/lib/libwebcore.so
I/DEBUG ( 202): 7c41fb40 78a7c468
I/DEBUG ( 202): 7c41fb44 73c98389 /system/lib/libwebcore.so
I/DEBUG ( 202): 7c41fb48 0000000b
I/DEBUG ( 202): 7c41fb4c 400a153c /system/lib/libc.so (pthread_key_create+208)
I/DEBUG ( 202): 7c41fb50 00000000
I/DEBUG ( 202): 7c41fb54 741aa3e4
I/DEBUG ( 202): 7c41fb58 df0027ad
I/DEBUG ( 202): 7c41fb5c 00000000
I/DEBUG ( 202): #00 7c41fb60 7c41fba4 [stack:8895]
I/DEBUG ( 202): 7c41fb64 00000000
I/DEBUG ( 202): 7c41fb68 7c41fc14 [stack:8895]
I/DEBUG ( 202): 7c41fb6c 73c984cf /system/lib/libwebcore.so
I/DEBUG ( 202): #01 7c41fb70 7c41fba4 [stack:8895]
I/DEBUG ( 202): 7c41fb74 73c98e7f /system/lib/libwebcore.so
I/DEBUG ( 202): #02 7c41fb78 78a7c398
I/DEBUG ( 202): 7c41fb7c 74096410 /system/lib/libwebcore.so
I/DEBUG ( 202): 7c41fb80 7430e0de /system/lib/libchromium_net.so
I/DEBUG ( 202): 7c41fb84 32588cc7
I/DEBUG ( 202): 7c41fb88 78b40e9c
I/DEBUG ( 202): 7c41fb8c 00000000
I/DEBUG ( 202): 7c41fb90 73f206e5 /system/lib/libwebcore.so
I/DEBUG ( 202): 7c41fb94 78a7dca0
I/DEBUG ( 202): 7c41fb98 741a835c /system/lib/libwebcore.so
I/DEBUG ( 202): 7c41fb9c 73c9a44b /system/lib/libwebcore.so
原因の推測
まず、logcatから落ちている箇所がlibwebcore.soの中で発生しているということが分かるので、WebViewをnewしている辺りから派生していると考えられる。
そこでWebViewの処理を調べてみると、どうやらnewした時にスレッドを数本立ち上げて初期化しているようです。
とりあえずJavaではなく、ネイティブコードの部分で落ちていそうなので、機種からlibwebcore.soを引っ張りだしてきて、objdumpで逆アセンブルしてみました。
ed458: 4b18 ldr r3, [pc, #96] ; (ed4bc <JNI_OnLoad+0xef8>)
ed45a: 4a19 ldr r2, [pc, #100] ; (ed4c0 <JNI_OnLoad+0xefc>)
ed45c: 447b add r3, pc
ed45e: b570 push {r4, r5, r6, lr}
ed460: 589d ldr r5, [r3, r2]
ed462: 682c ldr r4, [r5, #0]
ed464: b974 cbnz r4, ed484 <JNI_OnLoad+0xec0>
ed466: 2004 movs r0, #4
ed468: f7fb ebc2 blx e8bf0 <operator new(unsigned int)@plt>
ed46c: 4915 ldr r1, [pc, #84] ; (ed4c4 <JNI_OnLoad+0xf00>)
ed46e: 4479 add r1, pc
ed470: 4606 mov r6, r0
ed472: f7fb ec84 blx e8d7c <pthread_key_create@plt>
ed476: b120 cbz r0, ed482 <JNI_OnLoad+0xebe>
ed478: f20c ff00 bl 2fa27c <ucnv_open_emoji+0x7bfac>
ed47c: 4b0e ldr r3, [pc, #56] ; (ed4b8 <JNI_OnLoad+0xef4>)
ed47e: 601c str r4, [r3, #0]
ed480: 47a0 blx r4
ed482: 602e str r6, [r5, #0]
ed484: 682d ldr r5, [r5, #0]
ed486: 6828 ldr r0, [r5, #0]
ed488: f7fb ec7e blx e8d88 <pthread_getspecific@plt>
ed48c: b108 cbz r0, ed492 <JNI_OnLoad+0xece>
ed48e: 6804 ldr r4, [r0, #0]
ed490: b984 cbnz r4, ed4b4 <JNI_OnLoad+0xef0>
ed492: 2008 movs r0, #8
000ed47eの落ちている部分は、これはr3のポインタに0を代入しようとしている模様です。
r3のポインタは0xbbadbeefを指しているので、意図的にクラッシュさせているようです。
その少し上を見てみると、pthread_key_createしてその結果をr0に入れ、0比較しているコードがあります。
r0を見てみると0xbです。
pthread_key_createが返すエラーの中で、0xbはEAGAINであり、これはThread Local Storage(TLS; スレッド固有データ)のキーが使い果たされているときに出るエラーです。
まとめ
以上より、以下の流れで落ちていると考えられます。
- new WebView(context)
- WebViewCoreThreadのスレッドが立ち上がる
- スレッドの初期化が始まる
- System.load("libwebcore.so")
- libwebcore.soのJNI_OnLoadが呼ばれる
- ptherad_key_createできずクラッシュ
なぜ特定機種だけ落ちるのか?
ここで、なぜ特定機種だけで落ちるのかを調査しました。
Samsung改造説
まず、共通しているのがSamsung製端末だったので、Samsungがlibwebcore.so独自改造をしている線を疑いました。
そこでSamsungのサイトから該当端末の公開されているコードを拾ってきて読んでみました。
すると確かにThreadIdentifierDataPthreads.cpp`の落ちていると思われる以下の箇所は、Samsungの独自改造部分でした。
#if ENABLE(SAMSUNG_WEBKIT_PERFORMANCE_PATCH)
// SAMSUNG CHANGE : Webkit Performance Patch Merge + r92154
void ThreadIdentifierData::initializeOnce()
{
if (pthread_key_create(&m_key, destruct))
CRASH();
}
// SAMSUNG CHANGE : Webkit Performance Patch Merge -
#endif
ですが、そもそも本家のコードも違うタイミングでpthread_key_createしているので、どうやらSamsungの改造が直接の原因ではなさそうです。
TLSを使いすぎてる説
そもそもpthread_key_createでEAGAINエラーが帰ってきているので、TLSを使いきっているのが根本的な原因です。
そこで以下の様なコードをUnityアプリ起動直後に走らせ、pthread_key_createの余力をいろいろな端末で調べてみました。
[System.Runtime.InteropServices.DllImport("c")]
private static extern int pthread_key_create(IntPtr key, IntPtr destructor);
public void Init()
{
var key = System.Runtime.InteropServices.Marshal.AllocHGlobal(sizeof(int));
int count = 0;
while (true)
{
var ret = pthread_key_create(key, IntPtr.Zero);
if (ret != 0) break;
count++;
}
Debug.Log(string.Format("pthread_key_create capacity={0}", count));
System.Runtime.InteropServices.Marshal.FreeHGlobal(key);
}
結果
【追記】Unity 4.6.6で、development build + script debuggingを有効にした結果です。
リリースビルドにするとTLSは少し回復します(下の追記参照)
端末名 | OSバージョン | TLS余力 |
---|---|---|
Xperia AX | 4.0.4 | 16 |
Galaxy S3 | 4.0.4 | 17 |
Xperia Z | 4.1.2 | 16 |
Xperia Z1 | 4.2.2 | 13 |
Galaxy S4 | 4.2.2 | 9 |
Galaxy S4 | 4.3 | 3 |
Galaxy J | 4.3 | 3 |
Galaxy S4 | 4.4.2 | 75 |
Galaxy Z3 | 4.4.2 | 78 |
Nexus7 (2013) | 5.1.2 | 89 |
考察
4.3で余力3とか、そりゃあ落ちますよ・・・
というか4.4から急に凄い増えている・・・
これは本家にキャパ増やす修正入ってますね。(*1)
結論としては、UnityやらMonoやらできっとある程度TLSを使っているしまっている中で、WebViewを立ち上げ限界を超えたTLSを使おうとしてクラッシュというのが事の顛末のようです。
誰が悪いわけでもないので、対策としてはWebViewを使わないようにするという方法しかなく、現実的にはGalaxyの4.3系でのクラッシュを黙認せざるを得ないと思います。(他の4.3系の端末では未確認です)
追記(2015/06/30)
Unityのビルドオプションでリリースビルドにしたところ、TLSが6回復しました。
またSamusungの4.3でWebView立ち上げても落ちないTLSの閾値を探ってみたところ、5以上なら大丈夫そうです。
なのでリリースビルドでビルドすれば動くようになるはずです。
また回復量と閾値がどの端末でも同じなら、リリースビルドにさえしておけば大体の端末で落ちなくなるはずですが、もちろん4.3以下の端末でTLSが厳しいことに変わりはないので、使っているUnityのバージョンやライブラリ次第では落ちるかもしれません。
参考
*1: bionicにおけるPTHREAD_KEYS_MAXの修正コミット で、4.4からTLSのキャパが64から128になった模様。