Unity Cloud Diagnostics でクラッシュレポートを見る(iOS)
目次
前置き
とあるSDKを使ったiOSアプリを開発しようと思い、とりあえずサンプルシーンをビルドして実機で動かそうと思ったらシーンが始まると同時にアプリがクラッシュするといった事態に遭遇しました。しかも、Xcodeからアプリを起動すると問題ないのに、実機からアプリを起動する時に限ってクラッシュするんです。Unityの公式ドキュメントにiOSのトラブルシューティングのページがあったので見てみたら、ドンピシャなものがありました。
ゲームが Xcode から起動したときには正しく実行されるが、デバイス上で手動で起動すると最初のレベルのロード中にクラッシュする
これにはいくつかの理由が考えられます。より多く詳細を確認するために、デバイスのログをチェックする必要があります。Mac にデバイスを接続し、Xcode を起動してメニューから Window > Devices and Simulators を選択します。ウィンドウの左ツールバーで使っているデバイスを選択し、Show the device console ボタンをクリックして慎重に最新のメッセージを確認します。さらにクラッシュレポートを検証する必要があるかもしれません。クラッシュレポートを取得するには、以下を参照してください。
Troubleshooting on iOS devices - Unity マニュアル
ということで、クラッシュレポートを検証することになりました。調べてみたところ、クラッシュレポートを見るにはいくつか方法があるみたいですが、今回はUnityのCloud Diagnosticsを使ってやってみることに。しかし、一度も使ったことがなかったのでどのようにして使うのかをいろいろ実験してみました。今回はその実験結果もとい使い方を記しておこうと思います。
しかし、今回は最終的にiPhoneのクラッシュレポートを自分で取得して自分でクラッシュレポートを解析することになったんですが、それについては後述します。
解説に誤りがあった場合はご報告お願いします!
環境
Unity Cloud Diagnosticsとは?
簡単にまとめると、ユーザーが遭遇したクラッシュや、例外などを自動で集めてくれたり、ユーザー執筆のバグレポートを集めてくれたりするサービスです。プランによって受けられるサービスに違いがあったりします。詳しくは公式ページをご覧ください。
クラッシュレポートにはクラッシュしたスレッド、例外の種類、スタックトレースなどが載っているのですが、スタックトレースについては、どのメソッドでクラッシュしたのかについてはアドレスでしか記載されておらず、とても読めるものではありません。そのため、通常はシンボル化と呼ばれるアドレスをメソッド名に変換する処理を行う必要があります。ところがどっこい、Cloud Diagnostics を使うとすでにシンボル化されたクラッシュレポートを読むことができるのです。これは便利! ただし、メモリ不足によるクラッシュは Cloud Diagnostics にレポートされないのでその点は注意が必要です。
Unity Cloud Diagnosticsを使ってみる
とりあえずどんな感じに動いてくれるのか実験してみました。2019.4.9f1(LTS)で新規プロジェクトを作ってその中で実験してみます。丁寧な解説付きのチュートリアルページがあったのでそれをやってみます。このチュートリアルの3番と4番をやればUnity Editorで起こした例外をUnity Services Dashboardで確認できるようになります。
Cloud Diagnostics - Unity Learn
しかし、私が確認したいのは例外ではなくクラッシュレポートについてです。早速クラッシュを起こすようなコードを書いてクラッシュを起こしてみましょう。
UnityEngine.Diagnostics
名前空間にあるUtils
クラスのForceCrash
メソッドを使うことで意図的にクラッシュを起こすことができます。クラッシュの種類を引数に指定してForceCrash
を実行します。AccessViolation
は無効なメモリへのアクセスです。
using UnityEngine; using UnityEngine.Diagnostics; using UnityEngine.UI; public class Crasher : MonoBehaviour { public Button _button; void Start () { _button.onClick.AddListener (Crash); } void Crash () { Debug.Log ("クラッシュさせるぞー"); Utils.ForceCrash (ForcedCrashCategory.AccessViolation); } }
Unity Editor上で実行すると当然ながらUnity Editorもクラッシュしますので注意してください。また、Unity Editorでのクラッシュは Cloud Diagnostics にレポートされません。
ということで、ビルドして実機で実行してみます。プラットフォームをiOSにスイッチし、Build Settings は特に弄らず、 Player Settings は Other Settings > Identification の Bundle Identifier を適切に変更してビルドします。ビルドが成功したらいよいよiPhoneに転送してクラッシュさせましょう! ただし、クラッシュレポートが送信されるにはアプリをXcodeから起動するのではなく、実機から起動する必要があります。実機から起動した場合、クラッシュしたらアプリは終了されます。そしたら Unity Dashboard でクラッシュレポートが来ているか確認してみましょう。
※ 私の場合だけかもしれませんが、実機に転送後初めての実機からのアプリ起動によるクラッシュはなぜかレポートされませんでした。そのため、もしクラッシュを起こしたのに Dashboardでクラッシュを確認できない場合は何度かクラッシュをさせてみるといいかもしれません。
おお、来てます!!
ページ下部にある Problems の項目でクラッシュの詳細を見てみましょう。しかし、 Unknown Function とか書かれてるのが気になるなぁ...
スタックトレースを見てみると...
んん? なんかシンボル化されてないのがあるっぽくね?
シンボル化されていない部分はアドレスの右脇に <system symbols missing>
とありますね。しかし、 UnityFramework の部分だけはちゃんとシンボル化されているようです。これは一体どういうことでしょうか。
Cloud Diagnostics のドキュメントページのシンボルに関するページを見てみると、原因っぽい内容を見つけました。
Crash and exception reporting missing symbols | Unity Cloud Diagnostics
シンボルにはシステムシンボルとアプリケーションシンボルの二種類のシンボルがあり、システムシンボルはOSのサプライヤーから提供され、アプリケーションシンボルはUnityのプロジェクトがビルドされたときに提供されると書かれています。そして、システムシンボルが欠落している原因の多くは、UnityがそのバージョンのOS用のシンボルを持っていないことに起因するとも書かれています。
今回使用している iOS 13.7 は この実験をやった日のわずか5日前にリリースされました。そのため、まだUnityがiOS13.7向けのシステムシンボルを持っていなかったのでしょうか?
ちなみに、ビルド時に生成されるアプリケーションシンボルが正しくサーバーにアップロードされたかどうかは以下のファイルを見るとわかります。
~/Library/Logs/Unity/symbol_upload.log
確認してみると...
time="2020-09-06T16:44:06+09:00" level=info msg="b5701ffd9ba8312889708277314e2b92 successfully uploaded to cloud storage" time="2020-09-06T16:44:06+09:00" level=info msg="All 1 uploads completed with out error." time="2020-09-06T16:44:19+09:00" level=info msg="9a25edc0f70938afb2606b9f026189ac successfully uploaded to cloud storage" time="2020-09-06T16:44:19+09:00" level=info msg="All 1 uploads completed with out error."
このログを見る限りアプリケーションシンボルとして使うファイルは2種類あるみたいで、そのどちらも正しくアップロードされているようです。
iOSのバージョンを変えればうまくシンボル化されるかもしれませんが、わざわざダウングレードするのも面倒なのでここで断念...。
まとめ
Unity の Cloud Diagnostics を使うことで簡単にクラッシュレポートを見ることができました。しかし、デバイスのOSのバージョンによってはシンボル化できない部分も出てきてしまうようです。
おまけ: 自力でiOSのクラッシュレポートをシンボル化する
結局、私の実行環境では Cloud Diagnostics を使って完全なシンボル化がされたクラッシュレポートを見ることができませんでした。さて、どうにかして完全なシンボル化がされたクラッシュレポートを手にしたいです。ということで、自力でシンボル化をしてみたいと思います。ちなみに、Cloud Diagnocticcs をONにしないと UnityFramework 関連の部分がシンボル化されなかった(私が試した中では)ので、 自力でシンボル化する場合も Cloud Diagnostics はONにしておくのがいいと思います。
1. クラッシュレポートをデバイスから取得する
実機に転送したアプリがクラッシュするとデバイスにクラッシュレポートが登録されます。
設定アプリ > プライバシー > 解析 > 解析データ
目的のクラッシュレポートを選択してAirDropなどでMacに転送します。
2. シンボルファイルを取得する
シンボル化に必要なファイルは以下に配置されます。
~/Library/Developer/Xcode/DerivedData/<build id>/Build/Products/<build type>/
Cloud Diagnostics を有効にしてビルドすると、<build type>
(下の画像の場合 ReleaseForRunning-iphoneos)フォルダ以下は以下のような構成になります。
ミソなのは、このフォルダの中にUnityFramework.framework.dSYM
が含まれていることです。Cloud Diagnocticcs をONにすることでこのファイルが<build type>
以下に生成されます。これがないと UnityFramework に関する部分がシンボル化されませんでした。他に方法があるのかもしれませんが未検証です。
この中のappname.app.dSYM
をコピーしてどこか違う場所に移動させます。クラッシュレポートと一緒に新しく作業用ディレクトリを作ってまとめておきます。
3. シンボル化する
シンボル化には以下のプログラムを使います。しかし、とても長いのでエイリアスを作っておきます。
alias symbolize="/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash"
また、パスを通しておきます。
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
上の二つのコマンドをコマンドラインに打ち込んだら、いよいよシンボル化を行なっていきます。コマンドは作業ディレクトリ内でやると楽です。
$ symbolize -v hoge.ips appname.app.dSYM > symbolicated.ips
シンボル化プログラムは標準出力に出力されるので、出力をファイルにリダイレクトします。
注意
シンボル化する際は以下のディレクトリ内にクラッシュしたOSのバージョンがあるかを確認しましょう。無い場合はUIKit等のframeworkのスタックトレースをうまく復元できないらしいです。ということは、おそらくこれがシステムシンボルの有無を表しているのでしょう。私の方では確認していませんが、参考記事によるとiOS DeviceSupportは端末をMacに接続し、XCode内でデバイスを選択すると、そのOSのディレクトリが作成されるとのことです。
ls ~/Library/Developer/Xcode/iOS\ DeviceSupport/
iOSのクラッシュログをSymbolicate(復元)して解析する - Qiita
4. シンボル化完了!
上のスクリーンショットをみると、Cloud Diagnostics でシンボル化されなかった部分がシンボル化されているのが分かります。今後何度もシンボル化をすることになりそうなら上のパスとエイリアスを .zshrc あたりにでも書いておくといいですね。
参考
Unity crash and exception reporting | Unity Cloud Diagnostics