こんにちは、Mirrativ iOS エンジニアのちぎらです。クラッシュが発生して、その原因が分からないととてもかなしい気持ちになります。このブログでも以前から触れているように、Mirrativ のクライアントアプリではエモモなどの表示に Unity を使用しています。今回は、Unity の Framework とその内部で発生したクラッシュ解析の取り組みについて紹介をしたいと思います。
隠されたクラッシュ情報
Mirrativ iOS アプリではクラッシュ情報の解析と集計に Firebase Crashlytics を使用しています。Bitcode を有効にしている場合、App Store Connect からダウンロードした dSYM ファイルを Firebase Crashlytics にアップロードすることによってクラッシュ情報の詳細が見えるようになります。しかし、dSYM ファイルをアップロードした後も特定のライブラリに関するクラッシュ情報の詳細が _hidden
となって隠されてしまう場合があります1。Mirrativ の iOS アプリの場合、UnityFramework がそれに該当していました。
このクラッシュの原因がフレームワーク側の実装にありそうか、そもそも解消が難しい問題なのか、詳細が分からないままでは議論の余地もありません。 _hidden
となってしまう現象は Bitcode を無効にすれば解消されるようですが、これを理由にして Bitcode を無効にしたくもありません。
以下では、 _hidden
解消までの道のりを順を追ってみていきます。
_hidden
はどこからきているのか
Firebase Crashlytics には dSYM ファイルをちゃんとアップロードしているはずですが2、どの時点で _hidden
という状態になってしまっているのでしょうか。まずは実際に _hidden
という表記が見つかるところまで掘り下げていきます。
Unity Framework に対応する dSYM ファイルを探す
_hidden
はどこからきているのかを調べるためには、まず App Store Connect からダウンロードした dSYM ファイルの内どれが UnityFramework に対応したものかを知る必要があります。
各 ***.dSYM
ファイルは実はディレクトリになっていて、ディレクトリの奥地には dSYM ファイルの実体が、それぞれ対応するモジュールの名前で配置されています。
# ***.dSYM ファイルは実はディレクトリ $ file ./21bd27d3-4dd8-3d3f-b877-c83696a83557.dSYM ./21bd27d3-4dd8-3d3f-b877-c83696a83557.dSYM: directory # 各 ***.dSYM ファイルの奥地には、対応するモジュール名で名付けられたファイルが配置されている $ ls ./21bd27d3-4dd8-3d3f-b877-c83696a83557.dSYM/Contents/Resources/DWARF mirrativ $ ls ./2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM/Contents/Resources/DWARF UnityFramework # 奥地にあるファイルが dSYM ファイルの実体 $ cd ./21bd27d3-4dd8-3d3f-b877-c83696a83557.dSYM/Contents/Resources/DWARF/ $ file ./mirrativ mirrativ: Mach-O 64-bit dSYM companion file arm64
つまり、どの ***.dSYM
ファイルの奥地に UnityFramework
という名前のファイルが格納されているのか?というのを調べればよいのですが、人間には非常にやりづらい作業です。以下のようなスクリプト find_dsym.sh
を準備し、各 ***.dSYM
ファイルと奥地に配置されているファイルの名前の対応を分かりやすく表示できるようにしました。
files=`ls "$1"` for uuid in ${files[@]}; do module=`ls "$1/${uuid}/Contents/Resources/DWARF/"` echo "${uuid:0:8}: ${module}" done
このスクリプトを実行すると、以下のような結果が得られます。これでどの dSYM ファイルが UnityFramework に対応したものなのかが一目でわかるようになりました。
$ ./find_dsym.sh {path_to_appDsyms_directory} 01526605: Shared 21bd27d3: mirrativ 2ba4bd27: UnityFramework 2d4dd1c9: widget 2ebf8484: ...
dSYM ファイルの中身が _hidden
になっていることを確認する
dSYM ファイルは dwarfdump
というコマンドで中身を確認することができます。上で見つけた UnityFramework に対応する dSYM ファイルの中身を覗いてみましょう。
$ dwarfdump ./2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM | less
dwarfdump
は大量のテキストを出力してターミナルを文字で埋め尽くしてしまうので、 結果を less
に流して確認しています。上のコマンドを実行すると、以下のような出力が確認できます。
2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM/Contents/Resources/DWARF/UnityFramework: file format Mach-O arm64 .debug_info contents: 0x00000000: Compile Unit: length = 0x00000322, format = DWARF32, version = 0x0004, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000326) 0x0000000b: DW_TAG_compile_unit DW_AT_producer ("__hidden#178_") DW_AT_language (DW_LANG_ObjC_plus_plus) DW_AT_name ("__hidden#179_") DW_AT_stmt_list (0x00000000) DW_AT_comp_dir ("__hidden#180_") DW_AT_APPLE_optimized (true) DW_AT_APPLE_major_runtime_vers (0x02) DW_AT_low_pc (0x0000000000005970) DW_AT_high_pc (0x00000000000074bc) 0x0000002b: DW_TAG_subprogram DW_AT_low_pc (0x0000000000005970) DW_AT_high_pc (0x0000000000005a00) DW_AT_call_all_calls (true) DW_AT_name ("__hidden#0_") 0x0000003c: DW_TAG_subprogram DW_AT_low_pc (0x0000000000005a00) ...
_hidden
という文字列が確認できました。つまり、App Store Connect からダウンロードした dSYM ファイルの内容が既に _hidden
となっていて、従って Firebase Crashlytics 上の表示も _hidden
となっていた訳です。
クラッシュ情報を復元する
Firebase Crashlytics 上の _hidden
がどこからくるのかが分かりました。今度はクラッシュ情報を復元します。
bcsymbolmap ファイルを使って dSYM ファイルの内容を復元する
UnityFramework を生成する際、副産物として bcsymbolmap というファイルが生成されます。bcsymbolmap ファイルにもユニークな uuid が名前として付けられており、例えば上の UnityFramework には E003569B-D03E-3032-9B07-F39A6DE94BAC.bcsymbolmap
というファイルが付随して生成されています。この bcsymbolmap ファイルは UnityFramework のビルドと同時に生成されるので、生成された Framework と紐付けて保存しておく必要があります。
bcsymbolmap ファイルはテキスト形式で中身を見れるようになっています。中身を覗いてみましょう。
$ less ./BCSymbolMaps/E003569B-D03E-3032-9B07-F39A6DE94BAC.bcsymbolmap BCSymbolMap Version: 2.0 +[UnityURLRequest storeRequest:taskID:] +[UnityURLRequest requestForTask:] +[UnityURLRequest removeRequest:] ...
メソッド名らしきものが並んでいます。dsymutil
というコマンドに _hidden
となっている dSYM ファイルとそれに対応する bcsymbolmap ファイルの情報を渡すことによって、dSYM ファイルの内容を復元することができます。3
# --symbol-map オプションには bcsymbolmap ファイルが含まれるディレクトリを指定する $ dsymutil --symbol-map ./BCSymbolMaps ./2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM
上記を実行した後、もう一度 dwarfdump
で dSYM ファイルの中身を確認してみます。( Framework ビルド時のファイルの絶対パスなどが含まれているので一部伏字 ***
にしています )
$ dwarfdump ./2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM | less 2ba4bd27-261b-373d-91a1-d7c6b64d9c14.dSYM/Contents/Resources/DWARF/UnityFramework: file format Mach-O arm64 .debug_info contents: 0x00000000: Compile Unit: length = 0x00000322, format = DWARF32, version = 0x0004, abbr_offset = 0x0000, addr_size = 0x08 (next unit at 0x00000326) 0x0000000b: DW_TAG_compile_unit DW_AT_producer ("Apple clang version 11.0.3 (clang-1103.0.32.62)") DW_AT_language (DW_LANG_ObjC_plus_plus) DW_AT_name ("/***/Classes/Unity/UnityWebRequest.mm") DW_AT_stmt_list (0x00000000) DW_AT_comp_dir ("***") DW_AT_APPLE_optimized (true) DW_AT_APPLE_major_runtime_vers (0x02) DW_AT_low_pc (0x0000000000005970) DW_AT_high_pc (0x00000000000074bc) 0x0000002b: DW_TAG_subprogram DW_AT_low_pc (0x0000000000005970) DW_AT_high_pc (0x0000000000005a00) DW_AT_call_all_calls (true) DW_AT_name ("+[UnityURLRequest storeRequest:taskID:]") 0x0000003c: DW_TAG_subprogram DW_AT_low_pc (0x0000000000005a00) DW_AT_high_pc (0x0000000000005b54) DW_AT_call_all_calls (true) DW_AT_name ("+[UnityURLRequest requestForTask:]") 0x0000004d: DW_TAG_subprogram DW_AT_low_pc (0x0000000000005b54) ...
無事、dSYM ファイルに含まれていた _hidden
が人間に読める文字列になりました。
Firebase Crashlytics に復元した dSYM ファイルをアップロードする
あとは情報を復元した dSYM ファイルを Firebase Crashlytics にアップロードするだけです。4 すでに Firebase Crashlytics 上で集計されたクラッシュはそのままですが、新たに発生したクラッシュには復元されたクラッシュ情報が付与されます。
これで無事クラッシュ情報が Firebase Crashlytics 上で確認できるようになり、問題の把握に一歩だけ近づきました!
クラッシュ情報の中に _hidden
という文字列を見かけた際には、この記事を思い出していただけると幸いです🙂
We are hiring!
Mirrativ では一緒にアプリを作ってくれる iOS エンジニアを募集中です!気軽にご連絡ください!
-
公式では
obfuscated symbols
やhidden symbols
などと呼ばれています。 Building Your App to Include Debugging Information↩ -
Firebase Crashlytics のバージョン 8.0.0 から hidden symbols を含む dSYM ファイルをアップロードする際に警告が出るようになりました。Firebase iOS Release Notes↩
-
Adding Identifiable Symbol Names to a Crash Report - Restore Hidden Symbols↩
-
Firebase に確認したところ、同名で内容の異なる dSYM ファイルをアップロードした場合、古い dSYM ファイルを新しいもので上書きしてくれるようです。つまり、とりあえず全ての dSYM ファイルをアップロードしておいて、後で必要に応じて情報を復元した dSYM ファイルだけアップロードする、という運用でいけます。↩