iOSアプリのクラッシュログから解析する

## 概要

リリースビルドのiOSアプリのクラッシュログから、クラッシュ理由を探るべく方法を調査したのでそのメモです。

なお、今回の記事を書くにあたって以下の記事を参考にさせていただきました。

- [iOSのクラッシュログをSymbolicate（復元）して解析する](https://qiita.com/ruwatana/items/cc470eb229d267d693b0)
- [[Xcode]クラッシュレポートの解析手順備忘録](https://qiita.com/yu_okb/items/82b41f08882d578c2f25)
- [symbolicateを使わずにクラッシュログを(部分的に)解析する](https://qiita.com/arabian9ts/items/ad9f9cf26222c96bed20)


## 準備するもの

解析するにあたっていくつか準備するものがあります。

- クラッシュログ（`.crash`や`.ips`ファイル）
- Xcode
- dSYMファイル


## 大まかなフロー

大まかなフローをまず先に示します。

1. iPhone/iPadなど端末からクラッシュログを取り出す（`.crash` or `.ips`ファイル）
2. 対象アプリの`.app`ファイルと`.dSYM`ファイルを取り出す
3. 取り出したログをsymbolicateする（実行ファイルの`symbolicatecrash`）

クラッシュログを解析するためには、抽出したログからだけでは困難です。
というのも、クラッシュログは

```
Thread 32 name:  com.apple.NSURLConnectionLoader
Thread 32:
0   libsystem_kernel.dylib        	0x00000001855ecbc4 0x1855ec000 + 3012
1   libsystem_kernel.dylib        	0x00000001855eca3c 0x1855ec000 + 2620
2   CoreFoundation                	0x0000000185a9bce4 0x1859b2000 + 957668
3   CoreFoundation                	0x0000000185a998b0 0x1859b2000 + 948400
4   CoreFoundation                	0x00000001859ba2d8 0x1859b2000 + 33496
5   CFNetwork                     	0x0000000186123b40 0x186075000 + 715584
6   Foundation                    	0x00000001864e3860 0x1863d6000 + 1103968
7   libsystem_pthread.dylib       	0x000000018572032c 0x18571e000 + 9004
8   libsystem_pthread.dylib       	0x00000001857201f8 0x18571e000 + 8696
9   libsystem_pthread.dylib       	0x000000018571ec38 0x18571e000 + 3128
```

こんな感じで、いったいどのクラスのどのメソッドでクラッシュしたのか、というのがまったく分かりません。（フレームワークくらいなら書いてありますが・・・）
そこで、解析しやすいように、実行した対象のメモリ番地がなにを指しているのかをsymbolicate（シンボル化）する必要があります。

実際にシンボル化すると以下のような形に復元されます。

```
Thread 32 name:  com.apple.NSURLConnectionLoader
Thread 32:
0   libsystem_kernel.dylib        	0x00000001855ecbc4 mach_msg_trap + 8
1   libsystem_kernel.dylib        	0x00000001855eca3c mach_msg + 72
2   CoreFoundation                	0x0000000185a9bce4 __CFRunLoopServiceMachPort + 196
3   CoreFoundation                	0x0000000185a998b0 __CFRunLoopRun + 1424
4   CoreFoundation                	0x00000001859ba2d8 CFRunLoopRunSpecific + 436
5   CFNetwork                     	0x0000000186123b40 +[NSURLConnection+ 715584 (Loader) _resourceLoadLoop:] + 404
6   Foundation                    	0x00000001864e3860 __NSThread__start__ + 996
7   libsystem_pthread.dylib       	0x000000018572032c _pthread_body + 308
8   libsystem_pthread.dylib       	0x00000001857201f8 _pthread_body + 0
9   libsystem_pthread.dylib       	0x000000018571ec38 thread_start + 4
```

見比べてみるとだいぶ見やすくなっているのが分かるかと思います。
自分がコードを書いた部分でのクラッシュであればメソッド名などからあたりを付けることができると思います。


## iOSからクラッシュログを取り出す

さあ、では実際に解析作業に入りましょう。
まずは対象アプリのクラッシュログを取り出します。
クラッシュログは以下の場所に保存されています。
該当アプリのログを探して取り出しましょう。

「**設定アプリ > プライバシー > 解析 > 解析データ**」

![解析データ](https://qiita-image-store.s3.amazonaws.com/0/5616/f6375aa7-bdcd-0829-4a7a-adb9e4dfe49a.png)

こんな感じで、ログを出力したアプリ名が記載されているのでそれを探します。
該当ファイルがあったらそれを出力してMacに送ります。（自分の手元に端末がある場合はAirDropでも受け取れるようです）


## .appと.dSYMを取り出す

次に対象アプリの`.app`と`.dSYM`を取り出します。

対象ファイルは、Xcodeの「**Xcode > Window > Organizer**」でウィンドウを開くとビルドしたアーカイブ一覧が表示されます。
![Orgnizer](https://qiita-image-store.s3.amazonaws.com/0/5616/1f3c8336-7109-705e-cd64-bfb097cf9c45.png)

「**対象ビルドを選択、右クリック > Show in Finder**」とすると、対象のファイル（`.xcarchive`）がFinderに表示されます。
そして、「**対象ファイル、右クリック > パッケージの内容を表示**」を選択すると以下のように`.app`ファイルと`.dSYM`ファイルを見つけることができます。
![appとdSYM](https://qiita-image-store.s3.amazonaws.com/0/5616/eb866c18-8710-2532-016d-7f57a9709e43.png)


## シンボル化する

前述までに取り出したファイルを使って実際にシンボル化を行います。
シンボル化を行うにはXcodeに付属している`symbolicatecrash`を利用します。
実行ファイルはXcodeのアプリ内にあります。以下のパスです。（バージョンによっては異なる位置にあるかもしれません）

```console:symbolicatecrashの場所
$ /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash
```

コマンドは以下のように指定します。

*※ 適当なフォルダ（下の例では`works_folder`）などを作って、そこに必要なファイル（クラッシュログなど）をすべて集めておくと作業しやすいでしょう。*

```console:シンボル化
$ /path/to/xcode-thing/symbolicatecrash -v ./works_folder/hoge.ips ./works_folder/hoge.app.dSYM
```

また、シンボル化した情報は標準出力に出力されるので、ファイルなどで保存したい場合は以下のようにすることでファイルに保存することができます。

```console:ファイルへの保存
$ /path/to/xcode-thing/symbolicatecrash -v ./works_folder/hoge.ips ./works_folder/hoge.app.dSYM > anyName.txt
```

ただし、初期状態では以下のエラーが出ます。

```console
Error: “DEVELOPER_DIR” is not defined...
```

これは、`symbolicatecrash`で使用されている`DEVELOPER_DIR`が定義されていないために起こるエラーです。
なので、`.bashrc`などに以下の設定を追加しておきます。

```config
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
```

これで再度実行すると無事にシンボル化されたものが出力されると思います。


## おまけ

### Xcode -> Run時のdSYM生成

Instrumentsを利用してのメモリリークチェックなどで、Xcodeから直にビルド→インストールで実機確認するケースがあると思います。
その際にもdSYMを使ってシンボル化したいケースがあります。

しかし、通常はdSYMが生成されません。
そこで、Build Settingsの`DEBUG_INFORMATION_FORMAT`を`dwarf-with-dsym`にしてビルドを行うと、`Products`フォルダの中にdSYMが生成されるのでシンボル化に使うことができるようになります。

具体的には以下のパスに生成されます。

```console:dSYMの場所
/Users/[USER_NAME]/Library/Developer/Xcode/DerivedData/[PROJECT_NAME]-[RANDOM_STRING]/Build/Products/Debug-iphoneos/[APP_NAME].app.dSYM
```