MacOSプログラミング/Xcode Debugging Tips

Xcode環境でデバッグを行う際に役に立ちそうな情報をまとめました。
Xcodeはgdbのフロントエンドとして動作するビジュアルデバッガを提供していますが、VisualStudioなどを使い慣れていると、ぱっと見足りない機能があるように見えるというか、「あれ、コレってどうやるの?」みたいな事が、いくつかあります。

このページでは、そんな経験を何度かした私が関連ドキュメントの一部を調べて、これはと思った機能を紹介します。そんなわけで、Xcodeのデバッガの使い方がそもそも分からないというような初心者には適さない内容ではありますが、何となく使っているだけでは分からない、あるいは見落としやすい内容をメインに書いています。

なお、Guard Malloc(libgmalloc)についてはそれ自体の情報量が多いので、別エントリにまとめます。

参考情報:
ここに載っている情報は、以下の情報を参考に意訳したものです。正確な内容を目指してはいますが恐らく間違いもあります。
おかしいな、と思ったら一次情報を参照してください。
- Xcode 3 Unleashed Chapter 22. "More About Debugging"
- Xcode Debugging Guide

ブレークポイント
- メニュー「実行>表示>ブレークポイント」 で、ブレークポイントのリストが表示されます。ここから、ブレーク条件や、ブレークポイントアクションなどを入力することが出来ます。
- ブレークポイントリストの「条件」の部分に式を書くと、式がtrueの時以外はブレークしないように出来ます。
- シンボル名を直接入力してブレークすることも可能です。フレームワークのグローバル関数などで直接ブレークしたい場合は、シンボルに対して張ると便利なことがあります。"objc_exception_throw" とか、"malloc"とかが該当するかと思います。

ブレークポイントアクション
- ブレークしたい理由が値のチェックである場合、停止させる代わりに値が見たり、なんらかのチェックをするために、ブレークポイントアクションを使うと便利な場合があります。ブレークポイントアクションは、ブレークポイントに到達した時に、ログ出力やgdbのコマンド、AppleScriptのコマンドなどを実行させられます。

- ブレークポイントリストの右端にある「続行」っぽいアイコンをチェックすると、アクションだけ実行して実行を止めずに続行させることが出来ます。

また、Xcode 3.2では「内蔵ブレークポイント」という機能があり、ブレークポイントアクションのプリセットを選択行に対して設定することが出来ます。ブレークポイントアクションがイマイチピンと来ない人は、内蔵ブレークポイントを使ってみるといいのではないでしょうか。

embeded_bp.jpg

ブレークを仕掛けているのに、止まらない場合
- デバッガの実行時、ブレークポイントの表示が(-)となってるものは、ユーザー的には有効にしたいのだが、デバッガとしてはシンボル位置が確定されていないので設定できていない、というモノを指します。このような状況が発生することは、gdbがシンボルの遅延ロード機能を持っていることが、一つの原因です。
- 特定のフレームワークについてこれが発生していて、それを何とかしたい場合、「実行>表示>共有ライブラリ」共有ライブラリのリストを開き、開始レベルを「すべて(All)」に設定することで回避できます。
- また、すべてのシンボルを事前に読みたい(遅延ロードを使用したくない)という場合は、「プレファレンス>デバッグ」で「CFMを使わないでシンボルを読み込む」のチェックを外すことで、そのようにすることが可能です。ただし、これを行うとライブラリ数によってはデバッグまでに起動に時間がかかるようになります。




変数の監視
- 変数が変化したときにブレークするブレークポイントをセットするためには、変数ウインドウで変数を選択して、右クリックで「変数を監視」を選択します。すると、変数の横にルーペのアイコンが設定され、変数の変更によってブレークされるようになります。
- Intel CPUでは、4つのウォッチポイント用レジスタがあるため、変数のウォッチは4つまでは速度に影響を与えずに設定することが出来ます。しかし、4つを超えると、目に見えて分かる速度で実行速度が低下するため、注意が必要です。

デバッグ版フレームワークの利用
Appleの提供するフレームワークにはほぼ必ずデバッグ版、プロファイル版があります。しかし、デバッグ版の構成でも、通常自動的にApple側のフレームワークはデバッグ版が使われることはないようです。フレームワークをデバッグ版にすることで、フレームワーク側から取得できる情報量が増えるようです。
- デバッグ版のフレームワークは、大体名前に_debugのサフィックスがついています。gcc(というか、おそらくリンクローダ)ではリンク時にDYLD_IMAGE_SUFFIX 環境変数をフレームワークにつけるサフィックスとして加味して処理するようです。
- Xcodeでこの設定を行うには、実行可能ファイルの一般設定で、「フレームワークを読み込むときに使用するサフィックス」の設定を変更します。ここに、「なし」「デバッグ」「プロファイル」などがありますので、必要なバリエーションを読むことが出来ます。

アプリケーションを自分のタイミングで起動する
- Xcodeではデフォルトではデバッガを起動するとアプリケーションも同時に処理を開始しますが、一部のケースではこれはやりづらい場合があります。デバッガを起動した際にアプリケーションを自動で実行したくない場合、プロジェクトから実行ファイルを選択して「デバッガの起動後に実行可能ファイルを起動する」のチェックを外すと、「デバッグ」を実行した際、デバッガのみが起動するようになります。
その後、デバッガの「再起動」ボタンを押すと、アプリケーションが起動します。
このように処理を分けることで、たとえばアプリケーションが起動する前にgdbを使ってアレコレ設定を行ったり、ということが出来るようになります。


デバッガの表示と実行行が合わない場合
- XcodeはMac, Windows, Unixのどの行末コードでも適切に対応しますが、ファイル内の行末コードが統一されていない場合、Xcodeのデバッガの行ハイライトが実際の行とずれることがあります。
この問題が発生する場合、「表示>テキスト>行末コード」を使用して、現在のテキストの行末コードを確認しながら、テキストを修正してみてください。

スタックトレースと実行状態が合わない場合
- gccでは末尾再起などスタックトレースに関わる最適化が可能なので、最適化をしているとスタックトレースと実情が合わなくなる場合があります。
- スタックトレースの表示がおかしいと思ったり、コードの実行順が理解できないと思ったら、最適化オプションを確認して、「なし(-O0)」に設定してください。コンパイラが最適化した後のコードをソースレベルデバッグするのは、並大抵のことではないし、不要に困惑させられることの方が多いです。

オブジェクトの内容を詳細表示する
- Xcodeの変数ウインドウは変数を綺麗に表示してくれますが、表示内容が不足な場合があります。たとえば、NSArrayなどでは、オブジェクトが何個あるか、と言ったことはわかるが、実際に何を保持しているのか、ということは、変数ビューでは分からない、分かりづらい場合があります。
このような場合、変数を右クリックして「説明をコンソールに出力する」を実行すると、Cocoaオブジェクトならば、そのオブジェクトのdebugDescriptionメソッドを呼んでオブジェクトの説明を出力できます。
また、それ以外のオブジェクトは、CFShow()関数にその値を渡した結果が出力されます。

gdbのコマンドを使ってデバッグする
gdbでは数多くの便利な機能が提供されていますが、全部を網羅するのは大変です。
ここでは、簡単に理解できて、すぐに役に立つコマンドを少しだけ紹介します。

- オブジェクト内容の詳細表示
 gdbでCocoaオブジェクトを表示するには、poコマンドを使います。
 例: po item
 (itemという変数があると仮定)

- 特定コード・メソッドの実行
 gdbでは、callコマンドで特定のコードを実行することが出来ます。
 例: call (void) CFShow(item)
    call (void) printf("hello world?n")

 ちなみに、(void) は、gdb側が関数の返値を何型で受けるか、の指定です。ここに(int) や(MyObject*)などを指定することも出来ます。有効な型を設定すると、返値は$1, $2 などの変数に格納されます。
 そのようにして格納された変数は、"call (void) CFShow($1)" や "call (void) [$1 myMethod]"のように利用することが出来ます。

- 特定アドレスをフォーマットつきで出力する
 gdbでは、xコマンドで任意の値をフォーマットして表示出来ます。
 例: x /x 0x04322d1

- 式を実行する
 gdbでは、print(p) コマンドで任意の式を出力出来ます。
 例: print sizeof(MyObject)


データフォーマッタを使う
- Xcodeの変数ウインドウにはサマリーというカラムがあり、オブジェクトの内容を分かりやすく表示してくれます。このサマリーは自由にカスタマイズが可能になっています。
編集するには、サマリーをダブルクリックするか、右クリックで「サマリーのフォーマットを編集」を選択します。
- フォーマッタの形は、たとえば以下のような感じになっています。
 例:(x = {[$VAR x]} y = {[$VAR y]})
 出力例: (x = 12.3 y = 4.56)
- $VAR は、そのカラムが対象としているオブジェクトそのものです。
- {} でくくられた部分はコードです。この部分は実行されます。アプリケーションコードに含まれるプログラムを、ユーザーの裁量で設定、実行することが出来ます。
- 構造体(クラス含む)のメンバへのアクセスをする場合は、"%path.To.Value%"のように、%%でくくります。
- スーパークラスのメンバへのアクセスは"%SomeSuperclassName.x%"のようにスーパークラス名を含みます。
- サマリーがカラムの他の値の参照をするには:hogeと後ろにつけます。
 には以下の4つが指定可能です。
 - n: 変数名
 - v: 値
 - t: 型
 - s: サマリー
 例:{[$VAR x]}:s

- データフォーマッタはデータタイプに対して設定されるもので、インスタンスに対して設定されるわけではないので、その点は注意が必要です。つまり、int型のhogeに対して設定を行うと、他のすべてのint型に同じフォーマッタが使用されます。
- 自分で設定したフォーマッタの情報は、以下のファイルに保存されます。
  ~/Library/Application Support/Developer/Xcode/CustomDataViews/CustomDataViews.plist

- 繰り返しとなりますが、フォーマッタにはアプリケーションコードのメッセージ送信を利用することが出来ます。これは、ユーザーにより大きな自由度を与えますが、メッセージ送信によってオブジェクトのステートを変更するようなメソッドをうっかり呼んでしまうと、大きなトラブルの種となります。・・・理由は書かなくても分かりますよね?
- フォーマッタからアプリケーションコードを実行する場合、デバッガは未初期化変数に対してもメソッドを実行するので、この点に気をつける必要があります。妙な返値が返ると、データフォーマッタが例外を吐いて、データが表示出来なくなることがあるようです。


解放済みオブジェクトへの参照を発見する
- Cocoa/Objective-Cでプログラミングをしていると、すでに解放済みのオブジェクトに触ってしまう、ということがあります。それでプログラムがハングしてくれれば、バグであることが分かるのですが、オブジェクトへ送るメッセージによっては、ハングしてくれないこともあります。このようなケースでは、バグを発見するのが困難になるため、よりややこしいバグの温床になる可能性があります。

このような問題を回避するために、Cocoaでは「ゾンビオブジェクト(NSZombie)」を提供しています。ゾンビオブジェクトの機能を有効にすると、一度作成されたオブジェクトはリリース後にはNSZombieオブジェクトにリプレースされます。NSZombieオブジェクトは、どんなメッセージを受け取ってもNSExceptionをスローし、コンソールには「自分は生前なんだったのか」を出力します。
つまり、ゾンビ機能をONにすることで、おかしなオブジェクトがアプリケーションに混ざることを発見しやすくなるわけです。

NSZombieの機能を有効にするには、実行可能ファイルの環境変数に
- NSZombieEnabled=YES
と設定します。

あるいは、Foundation/NSDebug.h に、NSZombieEnabled グローバル変数が定義されているので、これをmain.m のコード内でYESに設定します。

なお、OSX 10.4 以前では、CoreFoundationの互換型(CFTypesという) (NSString等)をゾンビ化するのには、色々とトリックが必要だったそうです。10.5以降では、CFTypesもNSZombie にすることが出来るようになったということです。


解放済みメモリへの参照を発見する
- malloc等で確保したメモリのを解放後に参照してしまう、などのバグを発見したい場合、Guard Malloc を利用します。Guard Mallocは、それ自体への説明がそこそこ量が必要なので、別エントリとして再度解説します。

その他、細かいTIPS
- デバッグ情報のフォーマットは、XcodeではSTABSとDWARFが利用可能ですが、STABSはいわば古いフォーマットで、DWARFの方がトレース出来る情報量が多いので、必ずDWARFを使うべきです。一部の古いMac OS向けのプロジェクトでは、DWARFが利用できないことがあるようです。
- Key-Value Observationを介して管理している無名オブジェクトの中身を知りたい場合は、"po[myObject observationInfo]" で詳細表示が出来るそうです。


さらに踏み込んだ内容を知りたい人に:
ここで紹介した情報は、冒頭の2つのソースの内容を意訳したものですので、そちらの1次情報を是非参照してください。
また、(Xcode 3 Unleashedからの転載ですが) Appleはこれに近しい内容に関するTechNoteをいくつか公開しています。こちらも参考情報としてリストしておきます。

- Mac OS X Debugging Magic
- Getting Started with GDB
- GDB Internals

Comment

Only the administrator may view.

Only the administrator may read this comment.

Comment is pending approval.

Comment is pending administrator's approval.

Comment is pending approval.

Comment is pending administrator's approval.

Comment is pending approval.

Comment is pending administrator's approval.

管理者だけに表示を許可する

Trackback

Trackback URL:

http://deathcube.blog36.fc2.com/tb.php/19-b18ca107