XCode4 と Instruments で実機動作中の NSZombie を探す方法

XCode4 で開発していると、予期しないインスタンスが解放されてしまって、そいつを 参照しようとして BAD_ACCESS が出てしまう事が有る。 しかし、いったい何を参照しようとしているのか、なかなか見つけ難い場合がある。

Cocoaではデベロッパ向けに「ゾンビオブジェクト」というアロケーションされていないメモリへのアクセスを見つける為に解放されたオブジェクトをNSZombieオブジェクトとしてリプレースされる機能があるので、BAD_ACCESSが発生した 際にそいつを見つけやすくすることができます。

NSZombie が有効な場合のXCode4 での debug output

GNU gdb 6.3.50-20050815 (Apple version gdb-1708) (Fri Sep 16 06:56:50 UTC 2011)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "--host=i386-apple-darwin --target=arm-apple-darwin".tty /dev/ttys001
sharedlibrary apply-load-rules all
target remote-mobile /tmp/.XcodeGDBRemote-276-71
Switching to remote-macosx protocol
mem 0x1000 0x3fffffff cache
mem 0x40000000 0xffffffff none
mem 0x00000000 0x0fff none
[Switching to process 7171 thread 0x1c03]
[Switching to process 7171 thread 0x1c03]
2011-11-08 22:12:34.523 driverMusic[9536:707] *** -[MPConcreteMediaPlaylist retain]: message sent to deallocated instance 0x3b05d0

最後の1行で、 -[MPConcreteMediaPlaylist retain] が message sent to deallocated instance 0x3b05d0 への参照を失敗している(0x3b05d0 のメモリはすでに解放されNSZombieになってしまっている。)ことがわかります。

しかし、デフォルトの XCode4 の設定では、NSZombie が有効化されていないので、 ハングアップだけして、debug コンソールではメッセージしか表示されませんので 以下の手順で有効化する必要があります。

XCode4 上で NSZombie の追跡を有効化する方法

以下の手順で NSZombie の追跡を有効化してくれます。

  1. Product > Edit Scheme を選択 ( もしくは、 alt/option キー押しながら Run ボタンクリック)
  2. Run {アプリケーション名} の Arguments タブの Enviroment Variables 欄の + ボタンを押す
  3. Name に NSZombieEnabled 、 Value に YES を設定し、チェックを有効にする
  4. OK もしくは Done か Run を押す
  5. 実機なり、シミュレーションなりでビルド&デバッグする
_images/XCode4NSZombieEnable.png

ところで、 Diagnotics にも実は Enable Zombie Objects オブションがあるのだが、実はこれをEnableすれば、いちいち NSZombieEnabled要らないのではないかという気もする。

ところが、上記のアドレスがわかっても、いったい何を参照しているのか見当つかない 場合があります。この場合、以下の2つの方法で追う事ができます。

A Instruments の Zombies Trace Template を使って追っかける

B main.m にオプションを追加して、XCode4のOrganaizer と Instruments で追っかける

ちなみに、上記2方法とも案外日本語の情報が少ないです。

Aの方法が基本的には簡単ですが、Simulator でしか調査できません。(実機側にはそのプロファイルが無い) そこで、どうしても実機でdebugをせざるを得ない場合(例えばiPadミュージックライブラリなどを絡めるアプリは正規のSimulatorではサポートしていないので、実機でやるしかない)はBの方法を取らざるを得ません。

以下にBの手順をメモしておきます。

B 実機で NSZombie をデバッグする

  1. XCode4にて main.m のコードに以下の2行を追加
 extern void _CFEnableZombies(void);
 int main(int argc, char *argv[]{
      _CFEnableZombies();
}

追加後の main.m は基本的に以下の様な感じになるはずです。

#import <UIKit/UIKit.h>

extern void _CFEnableZombies(void); // for NSZombie Debug

int main(int argc, char *argv[])
{
    _CFEnableZombies(); // for NSZombie Debug
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}
  1. 実機を接続し、 XCode4 でRun(ビルド)して、Stop(停止)する。
  2. XCode4 の Organizer を起動し、Devicesタブの実機の Console を開き、Clear しておく。(あとで見やすくする為なので、やらなくても問題ない)
  1. Instruments を起動し、Trace Template で Allocations を選択してProfile(多分ほかのモードでもOKだが、とりあえこれで出来た) を選択。
  2. Instruments の Choose Tartget で実機のデバッグ対象アプリケーションを選択。
  3. Recordボタンを押してアプリケーションを起動し、デバッグしたい操作を行う。(わざとBAD_ACCESSを発生させる)
  4. アプリケーションがハウングアップしたら、XCode4 の Organaizer の Devicesタブの実機の Console でのメーッセージを確認すると、以下の様な メッセージが出る。
_images/OrganizerConsoleLog.png
Nov  8 22:47:10 unknown {アプリ名}[9608] <Error>: *** -[MPConcreteMediaPlaylist retain]: message sent to deallocated instance 0x1a3290
  1. 0x1a3290 が参照しようとした解放済みメモリのアドレスなので、Instruments にまた戻って、そのアドレスを探す。( Statics から Objects List に変更したほうが探しやすい )
_images/Instruments_1.png
  1. アドレスが見つかったら、アドレスの側にある右向きの矢印を押す事で、そのメモリが何の為に、何時alloc されて、何時Zombieになったか(解放されたか)が分かります。あとはこの情報を元にコーディングを直していく事になります。
_images/Instruments_2.png

余談ですが、今回の事例は MPMediaPlayer が間接的に呼び出す MPConcreteMediaPlaylist オブジェクトが犯人でしたが、このオブジェクトのallocはコードで明示的に書いていないので、正体を捕まえるのに結構苦労しました。