iOSのクラッシュログをSymbolicate（復元）して解析する

# はじめに
現在、XcodeのOrganizerや、Crashlyticsなどのクラッシュ検知ツールを用いることで自身のアプリのクラッシュログ（スタックトレース）を簡単に解析することが可能となっています。

ただし、生のクラッシュログはスタックトレースがアドレスで表記されているため、パッと見ただけでは全く解析ができません。（語弊があるかもですがｗ）
アプリのdSYMファイルを用いて**シンボル化（Symbolicate）**することで、はじめてクラッシュ検知ツールで見ることができるような、解析可能なスタックトレースへと復元することができるようになります。

今回はそのような**手動でSymbolicateするやり方**をまとめます。
これが意外と罠にハマりやすかったので手順を追って記します。
iOSエンジニアなら知っていて損はない内容かなと思いました。

# こんなシチュエーションで役に立ちます:+1:
* クラッシュした端末はあるけど、手元にないからMacに接続できない
* 証明書未登録でMac上でビルドができない端末でクラッシュしたログを解析したい
* リリースしたアプリのクラッシュログをお問い合わせフォームなどで送ってもらった

# 用意するもの
* Crashログ(.crash or .ips)
    * ログさえあれば端末は手元に不要で、Macに接続できなくても良いです
* Mac
    * Xcode
    * Crashさせたアプリの.dSYMファイル```（hoge.app.dSYM）```
        * これがないと復元できません

# やり方
## 端末からクラッシュログを抜き出す
とっても簡単です。下記にアクセスします。（OSによって多少文言が異なります）

* **iOS10.3以上**
    * 設定.App -> プライバシー -> 解析 -> 解析データ
* **iOS 10.3未満**
    * 設定.App -> プライバシー -> 診断と使用状況 -> 診断データと使用状況データ

開くと、アプリ名-日付.ips(.crash)ファイルがずらっと並んでいるので、該当の日時のログをタップします。
下の画像を見ても分かる通り、肝心のスタックトレースがアドレス表記になっているため、CoreFoundationやUIKitっていうのはわかってもこのままじゃ何もわかりません:joy:  
<img src="https://qiita-image-store.s3.amazonaws.com/0/128422/d681dbd7-5344-673f-22d9-763d7038a601.png" width=300> <img src="https://qiita-image-store.s3.amazonaws.com/0/128422/f22f0e9d-c6bc-72d2-4ac5-1ab049c549f8.png" width=300>

そして、このログを丸ごとコピーしてMacで閲覧できる状態にします。これで準備完了です。
と、これをやってて思ったのですが、ログを長押ししても全選択のメニューが出てきません(自分だけですかね？):scream:
なので自分で操作して始端からドラッグして選択範囲を一番下まで広げておかないとダメでした。
片方の手で青い丸ぽちをドラッグしつつ、片方の手で下までスクロールしてやると比較的楽です（謎Tips:sparkles:)

## クラッシュログをSymbolicate（復元）する
Xcodeのsymbolicatecrashを実行することでSymbolicateできます。
ただし、こちらもXcodeのバージョンによって格納先が異なります。

```bash:Path
# Xcode 7.3以上
/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources

# Xcode 7
/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources
```

毎回この長ったらしいのを打つのがめんどくさければ、PATHを張っておくと良いと思います。

コマンドを実行する前に.crash, .dSYMファイルを同一フォルダに移動しておきましょう。（symbolicatecrashの実行時に、関連するファイルを参照しやすくするためです）
今回はDesktop上で行うとします。

さて、準備が整ったのでいよいよSymbolicateします。
ターミナルから下記のコマンドを実行します。（.dSYMが.crashファイルと同じディレクトリにあれば、自動でSpotlight検索が走るようなので、第二引数はなくても実行可能かもしれません）

```bash:Terminal
上記Path/symbolicatecrash -v ~/Desktop/hoge.crash ~/Desktop/hoge.app.dSYM
```

実行！
```Error: “DEVELOPER_DIR” is not defined...```

あ、はい。
どうやら、こいつを定義してあげないといけないようです。ターミナルでexportします。

```bash:Terminal
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
```
気を取り直して、再度Symbolicate！！
ぶわぁっと結果が標準出力に吐かれて、数秒待つと完了です:tada:

ファイルに出力したい場合は、先ほどのコマンドの最後に```> hoge_symbolicated.crash```などを指定して実行してあげるとよいです。

## はまったポイント
### :warning: 自身のアプリのスタックトレースは復元されたけど、UIKitなどのframework部分は復元されない
「よし！やっとSymbolicateできたぞー。どれどれ…」
あれ、なんか一部しか復元できてない＞＜というか、自分のアプリ内のスタックトレースだけ復元されている…なんで！？
UIKitとかWebCoreとかのスタックトレースはアドレスのままで、見れないじゃん・・・

```:復元したはずのクラッシュログなのに・・・
Thread 6 Crashed:
0   WebCore                       	0x000000018d2c5190 0x18d280000 + 283024
1   WebCore                       	0x000000018d2c5190 0x18d280000 + 283024
2   WebCore                       	0x000000018d2c56f4 0x18d280000 + 284404
3   UIKit                         	0x000000018ecfe4bc 0x18e836000 + 5014716
4   UIKit                         	0x000000018ecfedb8 0x18e836000 + 5017016
5   UIKit                         	0x000000018ee9b96c 0x18e836000 + 6707564
6   UIKit                         	0x000000018ee9bae4 0x18e836000 + 6707940
7   UIKit                         	0x000000018ee9b6ac 0x18e836000 + 6706860
8   UIKit                         	0x000000018eb2b308 0x18e836000 + 3101448
9   UIKit                         	0x000000018ee9b96c 0x18e836000 + 6707564
```

ということで、自分はこちらにハマりまして、かなり時間を取られてしまいました・・・
ヒントは、symbolicatecrash実行時の出力のWarningにありました。
こんなWarningが吐かれていました。
```## Warning: Can't find any unstripped binary that matches version of hogehoge```
また、その行付近にこんなのも。
```[くぁｗせｄｒｆｔｇｙふじこ] NO MATCH (device support)```

ということで、どうやらdevice supportの中身と一致しないよって言っているようです。
iOS DeviceSupportディレクトリをのぞいて見ます。
```ls ~/Library/Developer/Xcode/iOS\ DeviceSupport/```

おおお。

```
10.0 (14A346)   10.0.1 (14A403) 10.1.1 (14B150) 10.3.2 (14F89)  8.1.3 (12B466)  9.0 (13A344)    9.2.1 (13D15)   9.3.2 (13F69)
10.0 (14A5261v) 10.1 (14B72)    10.2.1 (14D27)  7.1 (11D167)    8.4 (12H143)    9.0.2 (13A452)  9.3 (13E233)    9.3.5 (13G36)
10.0 (14A5346a) 10.1.1 (14B100) 8.0 (12A365)    9.0 (13A343)    9.1 (13B143)    9.3.1 (13E238)
```

ここでやっと理解しました。

**iOS DeviceSupportディレクトリに存在しないOSのクラッシュログはUIKitなどのframeworkのスタックトレースを復元できないようです。**

今回はiOS10.3.1でのクラッシュを復元しようとしてましたが、iOS DeviceSupportの中に10.3.1がありませんでした。

:memo: *追記 2018.04.09*
iOS DeviceSupportは端末をMacに接続し、XCodeにてデバイスを選択すると、そのOSのディレクトリが作成されるようです。
~~iOS DeviceSupportは一度でも端末をMacに接続したことがあると、そのOSのディレクトリが作成されるようだったので、~~

たまたま手元にあった10.3.1端末を探し出し、接続して10.3.1が追加された状態で再度symbolicateすると、
frameworkを含めた全てのスタックトレースを無事復元することができ、クラッシュの原因を知ることができました:tada:
（ちなみに、クラッシュの原因はWebThreadでUIいじっちゃってるというしょうもないものでした:dizzy_face:）

```:完全に復元されたクラッシュログ
Thread 6 Crashed:
0   WebCore                       	0x000000018d2c5190 _WebTryThreadLock(bool) + 192
1   WebCore                       	0x000000018d2c5190 _WebTryThreadLock(bool) + 192
2   WebCore                       	0x000000018d2c56f4 WebThreadLock + 108
3   UIKit                         	0x000000018ecfe4bc -[UIWebView _webViewCommonInitWithWebView:scalesPageToFit:] + 128
4   UIKit                         	0x000000018ecfedb8 -[UIWebView initWithCoder:] + 112
5   UIKit                         	0x000000018ee9b96c UINibDecoderDecodeObjectForValue + 680
6   UIKit                         	0x000000018ee9bae4 UINibDecoderDecodeObjectForValue + 1056
7   UIKit                         	0x000000018ee9b6ac -[UINibDecoder decodeObjectForKey:] + 104
8   UIKit                         	0x000000018eb2b308 -[UIView initWithCoder:] + 652
9   UIKit                         	0x000000018ee9b96c UINibDecoderDecodeObjectForValue + 680
```

# まとめ
つらつらと書きましたが、今回のポイントをまとめておきます。

* 端末の設定.AppからCrashログを抜き出した場合は、Symbolicate(復元)する必要がある
* Symbolicateには、Crashさせたアプリの.dSYMファイルが必要となる
* 完全な復元には、Crashさせた端末と同じOSのiOS DeviceSupportファイルが、復元実行するMac内に保存されている必要がある
    * 該当OSのiOS DeviceSupportファイルがない場合は、UIKitなどのframeworkのスタックトレースが復元されない

以上です。最後まで読んでいただきありがとうございました:bow:

# 参考リンク
* https://stackoverflow.com/questions/10772748/cant-symbolicate-crash-log-why