Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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になった模様。

asuuma
qualiarts-inc
QualiArtsは皆様に長く愛されるエンターテイメントを提供するため、スマートフォンゲームの企画、開発、運営を行っております。
https://qualiarts.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした