Watchpoint(ウォッチポイント)— 指定された変数やメモリ領域の値が変更されたときにプログラムの実行を一時停止するデバッグメカニズムです。ブレークポイントがコード行に紐づいているのに対し、ウォッチポイントはプログラムのどの部分で変更が発生してもデータの変化を追跡します。Apple Developer Documentationによると、ウォッチポイントはデータ競合のデバッグ、予期しないプロパティ変更、オブジェクトのライフサイクル追跡に不可欠です。
重要なポイント
Watchpoint — 指定されたメモリアドレスの値が変更されたときにプログラムの実行を一時停止するデバッガメカニズムです。ブレークポイントが特定のコード行に到達したときに反応するのに対し、ウォッチポイントは特定のメモリセルへの書き込みに反応します — コードのどの部分が書き込みを行ったかに関係ありません。これにより、特にマルチスレッドアプリケーションにおいて、予期しない変数の変更を見つけるのに不可欠です。
動作原理はプロセッサのハードウェアサポートに基づいています。最新のARMチップ(Apple Silicon、Qualcomm Snapdragon)は4~8個のハードウェアウォッチポイントレジスタを提供します。プロセッサがウォッチポイントに一致するアドレスへの書き込み命令を実行すると、ハードウェア割り込みが発生し、デバッガがそれをキャッチします。ハードウェアレジスタが枯渇すると、LLDBはソフトウェアモードに切り替わります — 各書き込み命令をステップ単位でチェックするため、実行速度が数十倍低下します。
ARM Architecture Reference Manualによると、ハードウェアウォッチポイントはData Watchpoint and Trace(DWT)モジュールレベルで動作し、実行可能コードの変更を必要としません。応答時間はナノ秒単位で、オーディオやビデオフレームバッファなどのデータ処理ループにおける高頻度の変数変更も追跡できます。
Watchpoint は、どの変数が変更されているかは分かっているが、どこから変更されているかが分からない場合に不可欠です。典型的なシナリオ:UIViewのframeプロパティが明らかな理由なく変更される、バックグラウンドスレッドのカウンタが予測不能にリセットされる、isLoadingフラグがネットワークリクエスト完了前に切り替わる。これらのそれぞれの場合において、書き込みが発生する可能性のあるすべての場所にブレークポイントを設定するのは非現実的です。ウォッチポイントは単一のコマンドで問題を解決します。
XcodeとLLDBは3種類のウォッチポイントをサポートしています:watchpoint set variable — ローカル変数の監視用;watchpoint set expression — アドレスを返す式の監視用;watchpoint set — 生のメモリアドレスの監視用。それぞれに適用領域があります。
| Watchpointの種類 | LLDBコマンド | 用途 |
|---|---|---|
| 変数 | watchpoint set variable -w write self.count | ローカル変数とグローバル変数、構造体のプロパティ |
| 式 | watchpoint set expression -w write -- &self->mutex.lock | ポインタによる構造体フィールド、インデックスによる配列要素 |
| アドレス | watchpoint set -w write 0x600000c4b80 | 以前のLLDB出力からの特定のメモリアドレス |
Xcodeでは、デバッグエリアを介してウォッチポイントを設定できます:ブレークポイントで停止し、Variables Viewパネルで目的の変数を見つけ、右クリックしてWatch Variableを選択します。Xcodeは自動的に正しい変数名とコンテキストでwatchpoint set variableコマンドを実行します。その後、デバッガは値が変更されるたびに停止します — コンソールに切り替えずに迅速なデバッグが可能です。ただし、この方法は変数が現在のフレームのスコープ内にある場合にのみ機能します。
func processItems(_ items: [String]) {
var index = 0
// GUIでindexにウォッチポイントを設定:
// ここで停止、右クリック → Watch Variable
for item in items {
index += 1
print("アイテム \(index): \(item)")
}
}
LLDB はコンソールからウォッチポイントを管理するための完全なコマンドセットを提供します。GUIよりも制御性が高く、監視領域のサイズ設定、トリガー条件の指定、計算された式から取得したアドレスへのウォッチポイント作成、トリガー時の自動アクション実行が可能です。コマンドインターフェースは、監視パラメータを素早く変更する必要がある複雑なシナリオのデバッグに特に役立ちます。
watchpoint set variableコマンドはスコープを考慮して変数名を受け取ります:Objective-Cのselfプロパティにはself->_property、Swiftにはself.propertyを使用します。-w writeパラメータは書き込み追跡を設定し、-w read — 読み取り追跡(一部のアーキテクチャでのみ利用可能)、-s size — 領域サイズ(バイト単位)を設定します。ウォッチポイント設定後、watchpoint listコマンドでリストを表示できます。
(lldb) watchpoint set variable -w write -s 8 self.balance
Watchpoint 1: addr = 0x600000c4b80 size = 8 state = enabled type = w
watchpoint spec: 'self.balance'
(lldb) watchpoint set expression -w write -- self->items._storage
Watchpoint 2: addr = 0x600003a4c00 size = 8
(lldb) watchpoint list
1: location = 0x600000c4b80, type = write, variable = '_balance'
2: location = 0x600003a4c00, type = write, expression = 'self->items._storage'
ブレークポイントと同様に、ウォッチポイントは条件付きトリガーをサポートしています。-cパラメータはSwiftまたはObjective-Cで条件を設定します。たとえば、newValue > 1000という条件のウォッチポイントは、書き込まれた値が1000を超えた場合にのみ停止します。これは、毎秒数千の変更を生成するループやセンサーのデバッグ時に重要です — そうでなければデバッガはすべての変更で停止し、作業が不可能になります。
(lldb) watchpoint set variable -w write self.temperature -c "(int)$newValue > 100"
Watchpoint 3: addr = 0x600000e4a20, condition = '(int)$newValue > 100'
(lldb) watchpoint modify 3 -C "po self.temperature" -G true
// 値を自動的にログに記録して実行を継続
Objective-C および Swift オブジェクトのプロパティに対するウォッチポイントの特有の特徴 — ウォッチポイントはプロパティ名ではなく、オブジェクトのメモリ内のivar(インスタンス変数)フィールドのアドレスに設定されます。つまり、新しいオブジェクトが割り当てられるたびに(たとえば、ViewControllerを再作成する場合)、メモリアドレスが変更されるためウォッチポイントは無効になります。再起動後もプロパティを継続的に監視するには、オブジェクトの初期化時にウォッチポイントを再設定する必要があります。
配列 や 辞書 の要素を監視するには、特定の要素のアドレスを計算する必要があります。たとえば、配列の3番目の要素を監視するには watchpoint set expression -- &array[2] を使用します。配列が内部バッファを再割り当てすると(容量を超えて要素を追加した場合)、ウォッチポイントは無効になります — LLDBは Watchpoint 1 has an invalid address エラーを報告します。このような場合、コレクションのサイズ変更後にウォッチポイントを再設定する必要があります。
(lldb) expr var $arr = [10, 20, 30, 40, 50]
(lldb) watchpoint set expression -w write -- &$arr[2]
Watchpoint 4: addr = 0x1000a4b20, size = 8
(lldb) expr $arr[2] = 99
Watchpoint 4 hit: old value: 30, new value: 99
Objective-Cでは、objc_object構造体のretainCountフィールドにウォッチポイントを設定することで、オブジェクトのretain countを追跡できます。LLDBは watchpoint set expression -w write -- (int*)[object retainCount] を介してこれを行います。ただし、ARC(自動参照カウント)を使用するSwiftオブジェクトの場合、retain countに直接アクセスすることはできません — 代わりにInstrumentsまたはMemory Graph Debuggerを使用してリーク解析を行ってください。Appleによると、retain countのウォッチポイントはARC最適化が無効になったDebugビルドでのみ機能します。
Watchpoints には考慮すべきいくつかの重要な制限があります。主な制限 — ハードウェアウォッチポイントの数はARMアーキテクチャで4~8個、x86で最大4個のレジスタに制限されています。すべてのハードウェアレジスタが占有されると、LLDBはソフトウェアウォッチポイントモードに切り替わります:追跡範囲内のすべての書き込み命令を変更して例外を生成します。これにより実行速度が10~50倍低下するため、実際には同時に2~3個以上のアクティブなウォッチポイントを使用しないことを推奨します。
2つ目の制限 — メモリ再割り当て時のウォッチポイントの無効化。ガベージコレクションやARCが作動し、オブジェクトがメモリ内で移動すると(ヒープ圧縮を行う言語の場合)、ウォッチポイントアドレスが不正になります。SwiftとObjective-CではARCはオブジェクトを移動しませんが、配列や文字列の再割り当ては同じ効果を引き起こします。LLDBは Watchpoint N address (0x...) doesn’t contain a valid allocation というメッセージでこれについて警告します。
3つ目の制限 — 変数の可視性。ローカル変数のウォッチポイントは、その変数が現在のスタックフレームのスコープ内にある間のみ機能します。関数が終了すると、ウォッチポイントは自動的に削除されます。グローバル変数や長寿命オブジェクトのフィールドを監視する場合、ウォッチポイントは watchpoint delete で明示的に削除されるか、プロセスが終了するまで持続します。
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # すべてのwatchpointsを削除
2 watchpoints deleted.
(lldb) watchpoint disable 1 # 一時的に無効化
(lldb) watchpoint enable 1 # 再度有効化
ARMによると、Apple Silicon(M1~M4)のウォッチポイントは1~8バイトの監視領域サイズをサポートしています。8バイトを超える構造体を監視するには、各フィールドに複数のウォッチポイントを設定する必要があります。これは CGRect(16バイト)や UIEdgeInsets(16バイト)などの複雑なデータ構造をデバッグする際に重要です。
よくある質問
Breakpoint はコードの行に紐づいており、その行に到達すると停止します。Watchpoint はメモリアドレスに紐づいており、プログラムのどこからでもそのアドレスへの書き込みが発生すると停止します。ウォッチポイントは「誰が値を変更しているか」を探し、ブレークポイントは「この行で何が起こっているか」を探します。
ARM(Apple Siliconを含む)では4~8個のハードウェアウォッチポイント。これを超えるとウォッチポイントはソフトウェアモードに切り替わり、実行速度が10~50倍低下します。同時に2~3個以上のアクティブなウォッチポイントを使用しないことを推奨します。
Watchpoints はデバッグセッション間で保持されません。新しい起動のたびにメモリアドレスが変わるため、ウォッチポイントを再度設定する必要があります。例外は固定アドレスを持つグローバル変数のウォッチポイントです。
はい、ただしウォッチポイントはプロパティ自体ではなく、プロパティのivar(バッキングストレージ)に設定されます。Swiftでは watchpoint set variable self.property を使用します — LLDBがプロパティ名から対応するivarを自動的に見つけます。
ハードウェア ウォッチポイントはパフォーマンスに影響しません — 割り込みはプロセッサレベルで発生します。ソフトウェア ウォッチポイント(ハードウェアレジスタが不足した場合)は、LLDBがすべての書き込み命令をチェックするため、実行速度が10~50倍低下します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。