Address Sanitizerで独自Frameworkのメモリエラーを検出する

# はじめに

Xcode7より、AddressSanitizer(aka ASan)という、強力なメモリエラー検知ツールが追加されたことは、ご存知の方も多いかもしれません[0]

お仕事で、ASanで、独自Framework内のメモリエラーを調査する機会があったので、その際に、学んだ、使い方やTipsについて書き留めます。

もし何か不備等ありましたら、気軽にコメントください :)

# Address Sanitizerとは？

AddressSanitizer(aka ASan)は、メモリエラー検出ツールです。

**ASanは、不正なメモリ操作を検出するコードをコンパイル時に挿入します**。

ここがポイントですね。

ASanのコードは、メモリエラーを検知するとアプリをクラッシュさせるとともに、詳細なログをコンソールに出力します。

このあたりは、WWDC15のSession413[0]が詳しいです。

そのため、Valgrindなどとは違い、よりオーバーヘッドが少なく、2x程度に抑えられるそうです。

ASanは、Clang/LLVMの機能です。なので、iOSアプリに限らず、C/C++言語であれば、利用できます[1]。また、Android NDKでも利用できるそうです[2]。

検出項目としては以下が上げられています。

* Use-after-free
* Heap buffer overflow
* Stack buffer overflow
* Global variable overflow
* Overflows in C++ containers
* Use after return

例えば、以下のようなStack/Heap-buffer-overflowを検出してくれます。

```c
  char buff[10];
  buff[10] = 'a';
```
```c
  char *ptr  = (char*) malloc(10);
  ptr[10] = 'c';
```


# How To Use

## Run an app with Xcode

Xcodeからアプリを実行し、アプリコードのメモリエラーを検出するだけであれば、Schemeに設定するだけ。

**Edit Scheme > Run > Diagnostics > Address Sanitizer**

にチェック✔を入れます。

## Build an IPA with xcodebuild

### App only

xcodebuildでipaを作成したい場合は、Build Settingsに、`-fsanitize=address`を追加します。

- OTHER_CFLAGS
- OTHER_CXXFLAGS
    - `$(OTHER_CFLAGS)`となっていれば必要ありません

⚠️ Xcode 9では、`xcodebuild archive`時に以下が必要なようです。
(`xcodebuild build`では、`-enableAddressSanitizer`を`YES`にすればよい）

1. "Link Binary With Libraries"に、`libclang_rt.asan_ios_dynamic.dylib`を追加
2. `ENABLE_BITCODE`を"NO"にする
3. 後述のTips 2にあるように、Appバンドルに`libclang_rt.asan_ios_dynamic.dylib`をコピーする

### App + Frameworks

さらに、アプリにリンクさせる独自Frameworkを調査するときは、別の設定が必要です。
※今回私が検証したFrameworkは、Static libraryから生成したFrameworkバンドルです。

**Framework project:** Build Settingsに、`-fsanitize=address`を追加します。

- OTHER_CFLAGS
- OTHER_CXXFLAGS
    - `$(OTHER_CFLAGS)`となっていれば必要ありません

**App project:** 加えて`OTHER_LDFLAGS`にも、`-fsanitize=address`を追加します。

- OTHER_CFLAGS
- OTHER_CXXFLAGS
- OTHER_LDFLAGS 

これで、Address Sanitizerが有効化されて、ソースコードがコンパイルされます[3]。

また、`-fno-omit-frame-pointer`を追加すると、Stacktraceがよりわかりやすくなると[GithubのWiki](https://github.com/google/sanitizers/wiki/AddressSanitizer)には書いてありますが、Xcode 8では、付けなくてもよさそうです[4]。

設定は以上です。簡単ですね。

しかし、実際利用する際は、**以下の3点に注意が必要です。**

### Tips 1: Xcodeのバージョンを一致させる

**Frameworkとアプリは、同じXcodeバージョンで、ビルドしなければなりません。**

でないと、`Undefined symbols for architecture`というエラーが発生します。

これは、Clang/LLVMによって、コンパイル時に挿入されるASanの関数は、Xcodeのバージョンによって、シンボル名が異なる(Clang/LLVMのバージョンが異なる)ためです。

### Tips 2: IPAには、Run Scriptで、ASanのdylibをバンドルする

Run with Xcodeと違い、xcodebuildでは、ASanのdylibが、AppBundleにコピーされません。よって、単にIPAを生成するだけだとrpathエラーでクラッシュします。

```
dyld: Library not loaded: @rpath...
```

これを防ぐためには、Xcode.appから、`libclang_rt.asan_ios_dynamic.dylib`を取り出し、Projectに追加後、Run ScriptのCopy Filesで、コピーさせる必要があります。

<img width="791" alt="Screen Shot 2016-10-14 at 5.29.20 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/30023/00d0b669-b82d-5ee8-4716-00631bf1ca15.png">

Xcode 8では以下の場所にあります。

```
Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/8.0.0/lib/darwin/libclang_rt.asan_ios_dynamic.dylib
```

※これよりもrsync?などを使ったスマートな方法があったら教えてください！

### Tips 3: コンソールログを取得する

ipaファイルをインストールして、アプリを起動した場合、コンソールログを必ず記録しましょう。

メモリエラーが発生するとASanにより、アプリは`EXC_CORPSE_NOTIFY`でクラッシュし、クラッシュログが残りますが、なんと、その内容に、ASanログは記録されません。

コンソールログに出力されます。

そのため、テスト時は、クラッシュ後、必ず、Xcodeでコンソールログの取得を行ってください。そうしないと、ASanの真骨頂である、有用な情報が得られません。

# 最後に

ASanは、簡単な設定で、メモリエラー検出し、エラーの状況を詳細にレポートしてくれる、バグ修正には非常有用なツールです。

正直、無料のツール[5]で、ここまで詳細な調査が出来ることに驚きました。

`EXC_CORPSE_NOTIFY`クラッシュに遭遇したら、一度試してみるをオススメします。

なお、今回、CocoaPodsを利用しなかったので、試していません。おそらく、以下のように、設定をすればいいのではないかなと思いますがどうでしょうか。

```ruby
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['OTHER_LDFLAGS'] += '-fsanitize=address'
      config.build_settings['OTHER_CFLAGS'] += '-fsanitize=address'
    end
  end
end
```

# References


[0] Advanced Debugging and the Address Sanitizer
https://developer.apple.com/videos/play/wwdc2015/413/

[1] 以下のサイトが参考になります。

Clang: Address Sanitizer
http://clang.llvm.org/docs/AddressSanitizer.html

GitHub: google/sanitizers
https://github.com/google/sanitizers
https://github.com/google/sanitizers/wiki/AddressSanitizer

実際のコードはここかな？
https://github.com/llvm-mirror/llvm/blob/master/lib/Transforms/Instrumentation/AddressSanitizer.cpp

[2] https://source.android.com/devices/tech/debug/asan.html

[3] 逆に言えば、たとえ、Schemeで、Address Sanitizerが有効化し、アプリをビルドしても、Framework/Static Libraryなどの既にコンパイル済みのバイナリには適用できない。

[4] 上記に示した簡単なメモリエラーを起こしたところ、差分は確認できませんでした。オプションを設定せずとも十分に記録は残っていたので、設定しなくてもよいかと思います。

確認したclangのバージョンはこちら。
Apple LLVM version 8.0.0 (clang-800.0.38)
Target: x86_64-apple-darwin15.6.0

なお、`-fno-omit-frame-pointer`以外にも、[ここ](http://clang.llvm.org/docs/AddressSanitizer.html)を読むと、`-O1``-fno-optimize-sibling-calls`もあるとより詳しく表示されると書いてあります。


[5] 有料では、Coverityなどがありますね。
http://www.coverity.com/html_ja/products/code-advisor/index.html