【Unity】Androidの特定機種でWebViewをnewすると落ちる

  • 54
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

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; スレッド固有データ)のキーが使い果たされているときに出るエラーです。

まとめ

以上より、以下の流れで落ちていると考えられます。

  1. new WebView(context)
  2. WebViewCoreThreadのスレッドが立ち上がる
  3. スレッドの初期化が始まる
  4. System.load("libwebcore.so")
  5. libwebcore.soのJNI_OnLoadが呼ばれる
  6. 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になった模様。