Unity iOSのクラッシュレポートについて調べてみた(活用方法や注意点など)


Unityには現状iOSプラットフォームのみ利用可能な「[UnityEngine.CrashReport](https://docs.unity3d.com/ScriptReference/CrashReport.html)」と言うAPIが備わってます。

こちらの機能は、恐らくはiOS標準で備わっている？[クラッシュレポート機能](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs)を利用して実装されているものと思われ[^1]、それを踏まえつつ実際に有効活用する上で必要となってくるであろうレポートの読み方や注意点について調べみたのでメモ序に纏めて行ければと思います。

[^1]: Unity側の内部実装まで追いきれていないので、あくまでも推測の域を出ない

もし間違いなどあればコメントにて教えて頂けると幸いです！ :bow: 
(間違いなどあったら随時修正予定...)

**記事中で利用するUnity/Xcodeのバージョンについて**

- Unityバージョン: `2019.4.01f`
- Xcodeバージョン: `11.3.1`

-------------------------------------------------------------------------



# TL;DR

本題に入っていく前に、幾つかの要点だけ先に纏めておきます。

## `UnityEngine.CrashReport`について

- **`UnityEngine.CrashReport`ではメモリ不足によるクラッシュは取得できない**
    - iOSでのメモリ不足によるクラッシュは`JetsamEvent`として記録されるため、これが影響していると推測(アプリ本体のクラッシュレポートとは違うところに記録される)
    - この事もあってか、`Cloud Diagnostics`に備わっているクラッシュレポートも同様に記録されない ([ドキュメントにも記載有り](https://unitytech.github.io/clouddiagnostics/crashesandexceptions/UnityCloudDiagnosticsCrashesExceptions.html))
- **レポート内容からは「発生端末, OSバージョン, 日時, etc..」や「例外情報」などは分かる一方、スタックトレースはシンボル化されていないので情報を復元する必要がありそう**
    - 参考: [iOSアプリのクラッシュログから解析する](https://qiita.com/edo_m18/items/dbf720b1964f7cd5e9b9)
    - `Cloud Diagnostics`のクラッシュレポートはシンボル化がプロセスとして組み込まれているっぽいので、復元された結果が表示される

## iOSのクラッシュレポートについて

- **大凡の情報は以下のドキュメントに纏まっている**
    - [Diagnosing Issues Using Crash Reports and Device Logs](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs)
    - [Understanding and Analyzing Application Crash Reports](https://developer.apple.com/library/archive/technotes/tn2151/_index.html)
- **クラッシュレポートは端末に保持されており、`[プライバシー -> 解析および改善 -> 解析データ]`から参照可能**
    - 以下のフォーマットで記録されている
        - アプリ: `<AppBinaryName>-<DateTime>.ips`
        - JetsamEvent: `JetsamEvent-<DateTime>.ips`
    - AirDrop経由でMacに転送することでシンボル化と言った復元・解析が可能
    - JetsamEventも同様に記録されているので、こちらを活用することでメモリクラッシュの原因特定に活かせる
- **「クラッシュした際の例外情報は何を示しているのか？」については以下のドキュメントに纏まっている**
    - [Understanding the Exception Types in a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/understanding_the_exception_types_in_a_crash_report)
    - Xcodeでデバッグ実行中にクラッシュした際の原因調査にも役立つかと思われる










# `UnityEngine.CrashReport`の利用方法

使い方については「[Scripting API - CrashReport](https://docs.unity3d.com/ScriptReference/CrashReport.html)」に載ってますが、簡単に纏めます。

- Unity iOSのPlayer Settingsで以下の設定を適用
    - `Other Settings -> Script Call Optimization` を**「Fast but no Exceptions」**に設定
    - `Debugging and crash reporting -> Enable CrashReport API`に**チェックを入れる**
        - ※こちらの設定は`Cloud Diagnostics`にある`Crashes and exceptions`にチェックを入れていると強制的に有効になる模様

設定が適用された状態でビルドを行い、生成された.xcodeprojを開くと`Classes/CrashReporter.h`にある**`ENABLE_CUSTOM_CRASH_REPORTER`に1が設定されているかと思われます。**
この状態であれば設定は完了です。

```objc:Classes/CrashReporter.h
// Enable custom crash reporter to capture crashes. Crash logs will be available to scripts via
// CrashReport API.
#define ENABLE_CUSTOM_CRASH_REPORTER 1
```










# クラッシュさせてレポートを確認

実際にアプリをクラッシュさせてレポートの方を確認していきます。
一先ずは検証用に以下の2パターンのクラッシュを用意しました。

- **[Utils.ForceCrash](https://docs.unity3d.com/ScriptReference/Diagnostics.Utils.ForceCrash.html)を叩いて強制的にクラッシュ**
    - 今回は[ForcedCrashCategory. AccessViolation](https://docs.unity3d.com/ScriptReference/Diagnostics.ForcedCrashCategory.AccessViolation.html)を渡して無効なメモリアクセス想定でクラッシュさせる
- **メモリ不足を引き起こしてクラッシュ**
    - 実装としてはクソデカテクスチャを大量に生成して不足させている

こちらをアプリから呼び出し、クラッシュさせた後に`UnityEngine.CrashReport`のAPIを呼び出して内容を確認していきます。

参考までに今回使用したサンプルコードを載せておきます。

```csharp
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Diagnostics;

namespace Samples
{
    sealed class CrashTest : MonoBehaviour
    {
        [SerializeField] Button _fatalErrorButton = default;
        [SerializeField] Button _outOfMemoryButton = default;

        [SerializeField] Button _showLatestReportButton = default;
        [SerializeField] Button _showAllReportsButton = default;
        [SerializeField] Button _removeAllReportsButton = default;


        void Start()
        {
            _fatalErrorButton.onClick.AddListener(() => { FatalError(); });
            _outOfMemoryButton.onClick.AddListener(() => { OutOfMemory(); });

            _showLatestReportButton.onClick.AddListener(() => { ShowLatestReportButton(); });
            _showAllReportsButton.onClick.AddListener(() => { ShowAllReportsButton(); });
            _removeAllReportsButton.onClick.AddListener(() => { RemoveAllReportsButton(); });
        }

        /// <summary>
        /// 強制クラッシュテスト
        /// </summary>
        void FatalError()
        {
            // 無効なメモリアクセス想定でクラッシュ
            Utils.ForceCrash(ForcedCrashCategory.AccessViolation);
        }

        /// <summary>
        /// メモリ不足テスト
        /// </summary>
        void OutOfMemory()
        {
            // クソデカテクスチャを生成しまくってメモリ不足を起こす
            for (int i = 0; i < 100000; ++i)
            {
                var texture = new Texture2D(4096, 4096, TextureFormat.RGBA32, true);
            }
        }

        /// <summary>
        /// 最新のクラッシュレポートを表示
        /// </summary>
        void ShowLatestReportButton()
        {
            var report = UnityEngine.CrashReport.lastReport;
            if (report == null)
            {
                Debug.Log("not lastReport");
                return;
            }

            // 参照しやすいようにテキストにも書き込み
            File.WriteAllText(Application.persistentDataPath + "/latest_report.txt", report.text);
            Debug.Log($"[time]:{report.time}\n[text]:{report.text}");
        }

        /// <summary>
        /// 取得可能な全てのレポートを表示
        /// </summary>
        void ShowAllReportsButton()
        {
            var reports = CrashReport.reports;
            if (reports == null || reports.Length <= 0)
            {
                Debug.Log("not reports");
                return;
            }

            Debug.Log($">>> [reports.length]:{reports.Length}");
            var count = 1;
            foreach (var report in reports)
            {
                File.WriteAllText(Application.persistentDataPath + "/all_reports_" + count + ".txt", report.text);
                count++;
                Debug.Log($"[time]:{report.time}\n[text]:{report.text}");
                Debug.Log("-----------------------------------------------------");
            }
        }

        /// <summary>
        /// 取得可能なレポートリストから全てのレポートを削除
        /// </summary>
        void RemoveAllReportsButton()
        {
            CrashReport.RemoveAll();
            Debug.Log("remove all.");
        }
    }
}
```



## レポート内容を確認

**始めに`Utils.ForceCrash`を叩くパターンでアプリをクラッシュさせ、その次にメモリ不足パターンで再度アプリをクラッシュさせます。**
2回クラッシュさせたらアプリを再起動し、[UnityEngine.CrashReport.reports](https://docs.unity3d.com/ScriptReference/CrashReport-reports.html)を実行して結果を出力します。
(上記サンプルコードの`CrashTest.ShowAllReportsButton()`を実行)

この時に期待する挙動としては「`Utils.ForceCrash`」と「メモリ不足」による2件分のクラッシュレポートが表示されることですが、**実際に取得出来ているレポートは`Utils.ForceCrash`でクラッシュさせた時の1件だけであり、メモリ不足によるクラッシュレポートは取得できてません。**



### 何故メモリ不足によるレポートを取得できていないのか？

iOSはアプリ側でメモリが圧迫し続けるとシステム(OS)の方から強制終了される仕組みが備わってます。
**この際に行われるイベントを`Jetsam Event`[^2]と呼び、アプリ本体側のクラッシュレポートとは違うカテゴリーの`JetsamEvent`と言うJSON形式のレポートに情報が書き込まれます。**
参考: [Identifying High-Memory Use with Jetsam Event Reports](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_high-memory_use_with_jetsam_event_reports)

[^2]: `Jetsam`・・・ メモリ不足時にプロセスを停止させるための機構の名称(という認識)

話を戻して`UnityEngine.CrashReport.reports`でメモリ不足によるクラッシュレポートが読み込まれなかった理由についてですが、**恐らくは`UnityEngine.CrashReport`が内部的に参照しているレポートはアプリ本体側のクラッシュレポートのみであり、上記の`JetsamEvent`によるレポートはカテゴリー違いによって参照されなかった為に読み込まれなかったものと推測できます。**

ここらの話を理解するにあたっては、iOSが持つクラッシュレポート機能そのものについての予備知識が必要になってくるので、後半にて順を追って整理していきます。



### `Utils.ForceCrash`のレポート内容について

先に今回取得に成功した`Utils.ForceCrash`のクラッシュレポートについて簡単に触れておきます。
レポートを全て載せると結構な長さとなるので、一先ずは説明に必要な箇所を引用します。[^3]

[^3]: レポート中に度々「`CrashReportTest`」と出てくるが、こちらの名称は今回実行したサンプルアプリの名称

<details><summary>`Utils.ForceCrash`でのクラッシュレポート(クリックで展開)</summary><div>

```
Incident Identifier: 6E86B3BE-3333-45F6-BAF1-D15B58C07112
CrashReporter Key:   TODO
Hardware Model:      iPhone12,5
Process:         CrashReportTest [2623]
Path:            /private/var/containers/Bundle/Application/*****/CrashReportTest.app/CrashReportTest
Identifier:      ********
Version:         0
Code Type:       ARM-64
Parent Process:  ??? [1]

Date/Time:       2020-06-14 11:53:24 +0000
OS Version:      iPhone OS 13.3.1 (17D50)
Report Version:  104

Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x117e64000
Crashed Thread:  0

Thread 0 Crashed:
0   libsystem_kernel.dylib              0x00000001b678eefc 0x1b6769000 + 155388
1   ???                                 0xf62aeb81b65d84dc 0x0 + 0
2   ???                                 0x2340ab810516ac7c 0x0 + 0
3   UnityFramework                      0x00000001056ecccc 0x104738000 + 16469196
4   UnityFramework                      0x00000001056f4384 0x104738000 + 16499588
5   UnityFramework                      0x00000001056ae748 0x104738000 + 16213832
6   UnityFramework                      0x00000001055dc810 0x104738000 + 15353872
7   UnityFramework                      0x000000010572b240 0x104738000 + 16724544
8   UnityFramework                      0x000000010572aa04 0x104738000 + 16722436
9   UnityFramework                      0x000000010572a78c 0x104738000 + 16721804
10  UnityFramework                      0x000000010478a588 0x104738000 + 337288
11  UnityFramework                      0x00000001055bb480 0x104738000 + 15217792
12  UnityFramework                      0x00000001050d4f70 0x104738000 + 10080112
13  UnityFramework                      0x00000001050e2fc4 0x104738000 + 10137540
14  UnityFramework                      0x00000001050f033c 0x104738000 + 10191676
15  UnityFramework                      0x0000000104cc3608 0x104738000 + 5813768
16  UnityFramework                      0x0000000104cc34d8 0x104738000 + 5813464
17  UnityFramework                      0x0000000104ea07b8 0x104738000 + 7767992
18  UnityFramework                      0x0000000104e9752c 0x104738000 + 7730476
19  UnityFramework                      0x0000000104e97588 0x104738000 + 7730568
20  UnityFramework                      0x0000000104e97838 0x104738000 + 7731256
21  UnityFramework                      0x00000001053b9898 0x104738000 + 13113496
22  UnityFramework                      0x0000000104751984 0x104738000 + 104836
23  UnityFramework                      0x0000000104751860 0x104738000 + 104544
24  QuartzCore                          0x00000001bd429f24 0x1bd418000 + 73508
25  ???                                 0xcb69e301bd4f8608 0x0 + 0
26  ???                                 0x72166981b68f5dac 0x0 + 0
27  ???                                 0x99715701b69207c4 0x0 + 0
28  ???                                 0xf6195101b691fe90 0x0 + 0
29  ???                                 0xc0245f01b691aac8 0x0 + 0
30  ???                                 0x4040bd01b6919f40 0x0 + 0
31  ???                                 0x69526c01c0baa534 0x0 + 0
32  ???                                 0x7a61c901baaa5580 0x0 + 0
33  ???                                 0xeb6e50810475031c 0x0 + 0
34  CrashReportTest                     0x000000010426be1c 0x104264000 + 32284
35  libdyld.dylib                       0x00000001b6798e18 0x1b6798000 + 3608

Thread 1:
0   libsystem_kernel.dylib              0x00000001b678fab4 0x1b6769000 + 158388
1   ???                                 0x814efc81b66b59e8 0x0 + 0

Thread 2:
0   libsystem_kernel.dylib              0x00000001b678fab4 0x1b6769000 + 158388
1   ???                                 0x7c476701b66b59e8 0x0 + 0

Thread 3
// ※ここから上記の様な感じのスタックトレースが[Thread 39]分まで載っている。長いので省略。


Thread 0 crashed with ARM-64 Thread State:
    pc: 0x00000001b678eefc     fp: 0x000000016bb98e90     sp: 0x000000016bb98e70     x0: 0x0000000000000000 
    x1: 0x0000000000000000     x2: 0x00000000000120a8     x3: 0x000000013b004e3c     x4: 0x000000016bb989f0 
    x5: 0x000000016bb98e40     x6: 0x000000000000000a     x7: 0x000000000000004e     x8: 0x00000000000005b9 
    x9: 0x979d972d475ea69c    x10: 0x00000001ffe10990    x11: 0x0000000000000002    x12: 0x00000000fffffffd 
   x13: 0x0000010000000000    x14: 0x0000000000000000    x15: 0x0000000000000000    x16: 0x0000000000000148 
   x17: 0x0000000000000001    x18: 0x0000000000000000    x19: 0x000000000000000b    x20: 0x0000000000000407 
   x21: 0x00000001046c5920    x22: 0x00000001055d3590    x23: 0x0000000000000001    x24: 0x000000016bb98ef8 
   x25: 0x0000000000000000    x26: 0x00000001057251c8    x27: 0x0000000000000001    x28: 0x000000016bb98fb8 
    lr: 0x00000001b66ae8b8   cpsr: 0x0000000040000000 

// プロセスにロードされた各バイナリイメージのビルドUUID。こちらも全部乗せると長いので省略。
Binary Images:
       0x104264000 -        0x10426bfff +CrashReportTest arm64  <d18f18a360ba30be848a2d978859bca9> /private/var/containers/Bundle/Application/75C3EDB6-2DCE-4839-B56A-D7CD34B855FA/CrashReportTest.app/CrashReportTest
       0x104738000 -        0x105e5bfff  UnityFramework arm64  <24b073e8659a305cb641b1601462faf6> /private/var/containers/Bundle/Application/75C3EDB6-2DCE-4839-B56A-D7CD34B855FA/CrashReportTest.app/Frameworks/UnityFramework.framework/UnityFramework
       0x107ad8000 -        0x107ae3fff  libobjc-trampolines.dylib arm64-unknown  <a782ecde318c3379b33e7d204926c9c9> /usr/lib/libobjc-trampolines.dylib
       0x1b657c000 -        0x1b6592fff  libsystem_trace.dylib arm64-unknown  <1177e8a367aa3c8cb5605bcc40419d54> /usr/lib/system/libsystem_trace.dylib
 ...
```
</div></details>


#### 得られる情報とシンボル化について

**レポートを見ると、「どの例外が投げられたのか？」と言った情報は分かる一方、スレッド別のスタックトレースにはアドレスと思われる情報しか載っておらず、「どのクラス・メソッドで実行されたのか？」と言った情報が追えません。**
**これについては[シンボル化](https://qiita.com/mao_/private/5e4a6f916c87f4eaf5e2#%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)と呼ばれる作業を行い、情報を復元する必要があります。(詳細については後述)**

但しシンボル化自体はXcodeが必要となる作業という認識のため、もしランタイム上から取得して何かを行うとしたら「発生日時・端末/OS情報」や「例外情報」と言った読める範囲の情報をアプリ上のDebuggerに表示したり、Slackにメッセージを投げたりすると言ったことは出来るかもしれません。

```:例外情報
Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x117e64000
Crashed Thread:  0
```

```:スタックトレース
Thread 0 Crashed:
0   libsystem_kernel.dylib              0x00000001b678eefc 0x1b6769000 + 155388
1   ???                                 0xf62aeb81b65d84dc 0x0 + 0
2   ???                                 0x2340ab810516ac7c 0x0 + 0
3   UnityFramework                      0x00000001056ecccc 0x104738000 + 16469196
4   UnityFramework                      0x00000001056f4384 0x104738000 + 16499588
5   UnityFramework                      0x00000001056ae748 0x104738000 + 16213832
6   UnityFramework                      0x00000001055dc810 0x104738000 + 15353872
7   UnityFramework                      0x000000010572b240 0x104738000 + 16724544
8   UnityFramework                      0x000000010572aa04 0x104738000 + 16722436
9   UnityFramework                      0x000000010572a78c 0x104738000 + 16721804
10  UnityFramework                      0x000000010478a588 0x104738000 + 337288
11  UnityFramework                      0x00000001055bb480 0x104738000 + 15217792
12  UnityFramework                      0x00000001050d4f70 0x104738000 + 10080112
13  UnityFramework                      0x00000001050e2fc4 0x104738000 + 10137540
14  UnityFramework                      0x00000001050f033c 0x104738000 + 10191676
...
```



## 補足: `Cloud Diagnostics`のクラッシュレポートについて

[Cloud Diagnostics](https://unity3d.com/jp/unity/features/cloud-diagnostics)と言うサービスを利用すると、簡単にクラッシュレポートを集計し、ダッシュボード上から確認することが出来るようになります。(保持件数や保持期間などで違いがあるが、Unity Personalでも利用可能)

この記事中では導入に関しての解説は省くので、詳細については以下のドキュメントを御覧ください。

- [Unity crash and exception reporting](https://unitytech.github.io/clouddiagnostics/crashesandexceptions/UnityCloudDiagnosticsCrashesExceptions.html)



### シンボル化されたスタックトレースを確認可能

ポイントとしては、`UnityEngine.CrashReport`では対応されていない**スタックトレースのシンボル化もプロセスに含まれているみたいなので、以下の様に自動で復元されたスタックトレースを確認することが出来るみたいです。**

※ただ、デバッグシンボル(`.dSYM`)のアップロードプロセス等でミスが起きた際にシンボル化に失敗する時もあるらしく？手動でシンボルをアップロードする仕組みも用意されているので、覚えてくと問題が起きた際に幸せになれるかも。 
参考: [Crashes and Exceptions -> Symbols](https://unitytech.github.io/clouddiagnostics/crashesandexceptions/UnityCloudDiagnosticsSymbols.html)

<img width="1613" alt="スクリーンショット 2020-06-14 23.32.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/4e5fa41b-e767-52e5-1767-13ac5a48e31a.png">



### メモリ不足によるクラッシュ(`JetsamEvent`)は集計されないので注意

メモリ不足によるクラッシュ(`JetsamEvent`)は集計されないみたいなので、その点は注意する必要がありそうです。　
(恐らくは`UnityEngine.CrashReport`と同様に`JetsamEvent`自体が別なクラッシュレポートとして保持されるので取得されない説がある)

[ドキュメント](https://unitytech.github.io/clouddiagnostics/crashesandexceptions/UnityCloudDiagnosticsCrashesExceptions.html)にも以下のように記載されてます。

> Note that iOS crash reporting cannot capture crashes caused by the application running out of memory and then being closed by the operating system.



### メタデータについて

ダッシュボードからは「最後に確認した発生時刻」「Occurrences」「Stack Trace」「端末情報」と言った様々な情報を確認することが出来ますが、場合によっては参照したいのに表示されていないデータなどがあるかもしれません。

手元で確認した感じだとiOSのクラッシュレポートにある例外情報がどこにも載っていないように見受けられました。
(※プランによって表示内容を変更したり参照できる項目が増えたりするので、Plus/Proならひょっとしたら参照できるかもしれないが...(未調査))

こういったデータについては、ダッシュボード上からJSON形式でレポートを取得することで参照できるかもしれません。
例としてiOSの例外情報は以下のような形でJSONに載っていました。
※JSON自体はフォーマットされていないので、見やすいように手動でフォーマット掛けてます。

<img width="282" alt="スクリーンショット 2020-06-15 2.58.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/6fea5181-eee2-2abb-b8ed-fca3c96b0ddb.png">










# iOSのクラッシュレポートについて

ここからは機能を活用していく上での予備知識として、iOSのクラッシュレポートについて簡単に纏めていきます。

とは言えども、解説する内容の大体は以下の公式ドキュメントに纏まっています。
記事中では幾つかの要点を絞って解説していきますが、公式ドキュメントの方も一読してみることをオススメします。

- [Diagnosing Issues Using Crash Reports and Device Logs](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs)
- [Understanding and Analyzing Application Crash Reports](https://developer.apple.com/library/archive/technotes/tn2151/_index.html)





## クラッシュレポートの取得

クラッシュレポートはiOS端末上に保持されており、端末上からAirDrop経由などでMacに転送することが出来ます。
（AppSotreやTestFlight経由で集計することも可能な模様）

参考: [Acquiring Crash Reports and Diagnostic Logs](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/acquiring_crash_reports_and_diagnostic_logs)

### iOS端末上のクラッシュレポートの参照と取得

クラッシュレポートはiOSの`設定`にある以下のメニューから参照/シェアなどが可能です。

> 日: [プライバシー -> 解析および改善 -> 解析データ]
> 英: [Privacy -> Analytics & Improvements -> Analytics Data]

メニューを開くと`<AppBinaryName>-<DateTime>`のフォーマットでクラッシュレポートがアルファベット順に並んでいるかと思います。
今回サンプルで用意した`CrashReportTest`は以下のように並んでおり、この`.ips`ファイルがクラッシュレポートとなります。

![IMG_15DD358A17FA-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/ad973572-9dac-809e-4275-0339557dd67b.jpeg)

ちなみにメモリの圧迫によるクラッシュ(`JetsamEvent`)は以下のように`JetsamEvent-<DateTime>`のフォーマットで記録されます。JetsamEventについては[こちらの章](https://qiita.com/mao_/private/5e4a6f916c87f4eaf5e2#jetsamevent%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で解説していきます。

![IMG_40EEEF1D6168-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/d3430f1f-a884-77d1-b0af-a98912235290.jpeg)

### 内容の確認

リストから確認したいレポートをタップすることで開けます。

試しに`CrashreportTest-<最新の日付>.ips`を開くと以下のようになっており、内容自体は`UnityEngine.CrashReport`から読み込める内容と大凡一致していることが確認出来ます。[^4]

[^4]: 但し`CrashreportTest-<最新の日付>.ips`の方は一部メタデータっぽいのも入っているっぽいので、完全一致では無さそう...?

[後述のシンボル化](https://qiita.com/mao_/private/5e4a6f916c87f4eaf5e2#%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を行うにあたってはこちらが必要となるので、解析するのであれば右上のシェアボタンからAirDrop等でMacに転送しておくと良いかもしれません。

![IMG_F48C4F8FFBF4-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80207/eac7bd50-5a42-cf5d-3a4d-fad6e70d5d40.jpeg)





## JetsamEventについて

参考: [Identifying High-Memory Use with Jetsam Event Reports](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_high-memory_use_with_jetsam_event_reports)

iOSはアプリ側でメモリが圧迫し続けるとシステム(OS)の方から強制終了される仕組みが備わっており、この際に行われるイベントを`JetsamEvent`と呼ぶみたいです。

この時に書き込まれるレポートとしてはアプリ本体側のレポート(`<AppBinaryName>-<DateTime>`のフォーマットの方)ではなく、`JetsamEvent-<DateTime>`と言うフォーマットのレポートに詳細が記録されます。

JetsamEventのレポート自体はJSON形式且つシンボル化が不要な形で記録されており、**`reason`と言うKeyに記録されているValueの内容から原因を特定することが出来ます。**

### 「メモリ不足」特定の例

`reason`の一例を挙げると、Valueに**`per-process-limit`と言う文字列が入っている場合には、ドキュメントには「システムがすべてのアプリに課した常駐メモリの制限を超えました。この制限を超えると、プロセスは終了の対象となります」とあり、大凡メモリ不足でクラッシュしたと言うことの特定に繋がります。**

実際に`CrashReportTest`にてメモリ不足でクラッシュさせた後に、最新のJetsamEventのレポートを開いて`per-process-limit`で検索をかけると以下のようなブロックが引っかかります。

`name`(プロセス名)には想定通り、クラッシュした`CrashReportTest`の文字列が入っています。

```json
  {
    "uuid" : "d18f18a3-60ba-30be-848a-2d978859bca9",
    "states" : [
      "frontmost"
    ],
    "killDelta" : 8259,
    "genCount" : 0,
    "age" : 106579063,
    "purgeable" : 0,
    "fds" : 50,
    "coalition" : 1800,
    "rpages" : 134272,
    "reason" : "per-process-limit",
    "pid" : 2922,
    "cpuTime" : 1.271835,
    "name" : "CrashReportTest",
    "lifetimeMax" : 134272
  },
```

これ以外にも得られる情報はあるので、詳細はドキュメントを御覧ください。


### 余談: Jetsamの名前の由来?について

調べていたらKeijiro Takahashiさんの過去ツイを見つけました。なるほど。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">&quot;Jetsam&quot;は船舶の難破を防ぐために投棄された積荷／備品の事。iOSでメモリ不足の時にプロセスを殺す仕組みも&quot;jetsam&quot;なんですが、ここからきてるのね。 <a href="http://t.co/TaD8rv2oYx">http://t.co/TaD8rv2oYx</a></p>&mdash; Keijiro Takahashi (@_kzr) <a href="https://twitter.com/_kzr/status/321776051527225345?ref_src=twsrc%5Etfw">April 10, 2013</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>




## シンボル化について

参考: [Adding Identifiable Symbol Names to a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/adding_identifiable_symbol_names_to_a_crash_report)

`UnityEngine.CrashReport`から読み込んだレポートや、端末に記録されているクラッシュレポートのスタックトレースはそのままの形だと特定が困難です。
こちらを元の「クラス名・メソッド名」に戻すにはシンボル化を行う必要があります。

シンボル化については以下の記事がわかりやすく纏まっており、この記事中で特筆して書き足す要素は無かったので、内容については以下の記事を御覧ください。

- [iOSアプリのクラッシュログから解析する](https://qiita.com/edo_m18/items/dbf720b1964f7cd5e9b9)






## クラッシュレポートの読み方について

全部丁寧に纏め上げると長くなりそう(それだけで1記事書けそう)なので、ここではドキュメントの紹介レベルで解説を済ませます。
(ただ、内容自体は大切なところなので、どこかで纏め直したい。。)

### クラッシュレポートの構造

クラッシュレポートの構造（どういったセクションが存在して何が記載されているのか？）については以下のドキュメントを御覧下さい。
例えば「ヘッダ情報とそのフィールドについて」や「例外情報とそのフィールドについて」などが記載されます。

手元にあるクラッシュレポートと合わせて確認していくと理解しやすいかもしれません。

- [Examining the Fields in a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/examining_the_fields_in_a_crash_report)



### 一般的なクラッシュ原因の特定

表題の件に関しては、ほぼ名の通りですが以下のドキュメントを御覧ください。
内容としてはレポート中にある`例外情報`と`トレース情報`を元に、**「こう言うパターンの情報が載っていたら、この原因が疑える」**と言った情報が載ってます。

- [Identifying the Cause of Common Crashes](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes)


### レポートの分析

以下のドキュメントは「クラッシュレポートの読み方」と言うよりは「それを活用した分析手法(問題に対する考え方)」が纏まってます。

- [Analyzing a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/analyzing_a_crash_report)


## 例外情報の理解

クラッシュレポートの例外情報セクションには以下のような例外の情報が記載されてます。

```:例外情報
Exception Type:  SIGSEGV
Exception Codes: SEGV_ACCERR at 0x117e64000
Crashed Thread:  0
```

以下のドキュメントでは**これらが主に何を意味するのか？について記載されています。**
**こちらの情報はクラッシュレポートの理解以外にも、Xcodeでデバッグ実行中にクラッシュした際の原因調査にも役立つと思われるので、一読しておくことを個人的にはオススメします。**

[Understanding the Exception Types in a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/understanding_the_exception_types_in_a_crash_report)










# 参考/関連リンク



## Unity

- [Scripting API - CrashReport](https://docs.unity3d.com/ScriptReference/CrashReport.html)
- [Scripting API - Utils.ForceCrash](https://docs.unity3d.com/ScriptReference/Diagnostics.Utils.ForceCrash.html)
- [Cloud Diagnostics](https://unity3d.com/jp/unity/features/cloud-diagnostics)
    - [Unity crash and exception reporting(documents)](https://unitytech.github.io/clouddiagnostics/crashesandexceptions/UnityCloudDiagnosticsCrashesExceptions.html)


## iOS

- [Understanding and Analyzing Application Crash Reports](https://developer.apple.com/library/archive/technotes/tn2151/_index.html)
- [Diagnosing Issues Using Crash Reports and Device Logs](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs)
    - **Essentials**
        - [Acquiring Crash Reports and Diagnostic Logs](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/acquiring_crash_reports_and_diagnostic_logs)
    - **Crash Reports**
        - [Adding Identifiable Symbol Names to a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/adding_identifiable_symbol_names_to_a_crash_report)
        - [Identifying the Cause of Common Crashes](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_the_cause_of_common_crashes)
        - [Examining the Fields in a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/examining_the_fields_in_a_crash_report)
        - [Analyzing a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/analyzing_a_crash_report)
        - [Understanding the Exception Types in a Crash Report](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/understanding_the_exception_types_in_a_crash_report)
    - **Device Logs**
        - [Identifying High-Memory Use with Jetsam Event Reports](https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/identifying_high-memory_use_with_jetsam_event_reports)
- [iOSアプリのクラッシュログから解析する](https://qiita.com/edo_m18/items/dbf720b1964f7cd5e9b9)
- [iOS における malloc の挙動について](https://github.com/keijiro/ios-resident-memory-test)