ENGINEERING NOTE

クラウドMacでLLDB接続とシンボル読み込みを診断する

クラウドMacでLLDB接続とシンボル読み込みを診断する

Xcodeには「接続中」と表示されているのに、クラウドMacのCPU使用率はほぼアイドル状態のまま、ブレークポイントは青からグレーに変わり、変数ウインドウにも何も表示されないことがあります。この状態で停止や再起動を繰り返したり、DerivedDataを削除したりしても、多くの場合は調査に必要な証拠が失われるだけです。より効果的なのは、問題を3つの層に分けることです。対象プロセスがデバッグ可能か、LLDBと対象の間にセッションが確立されているか、現在の成果物に対応するシンボルが見つかるかを個別に確認します。

まず停止している層を特定する

最初に、発生時刻、プロジェクトのコミット、Scheme、Configuration、対象デバイス、起動方法を記録してから、Xcodeの状態を確認します。「接続が遅い」「アプリの起動が遅い」「シンボル解決が遅い」を同じ問題として扱わないでください。

現象 優先して確認する項目 よくある結論
アプリが起動せず、LLDBが待機し続ける 対象プロセス、起動引数 プロセスが終了したか、別の成果物が起動している
プロセスPIDは表示されるが、画面が応答しない プロセス状態、デバッグサービス 対象が一時停止またはブロックされているか、デバッグのハンドシェイクが完了していない
ブレークポイントが白抜きまたはグレーになる モジュールとシンボル モジュールが未読み込み、パスが変更された、またはUUIDが一致していない
停止はできるがローカル変数が見えない 最適化レベル、デバッグ情報 Release最適化によって変数が統合または削除されている

別のターミナルでプロセスのスナップショットを保存します。

mkdir -p "$HOME/lldb-case"
date -u > "$HOME/lldb-case/time.txt"
ps -axo pid,ppid,state,%cpu,etime,command \
  > "$HOME/lldb-case/processes.txt"
xcrun simctl list devices \
  > "$HOME/lldb-case/simulators.txt"

stateUが長時間表示される場合、プロセスが割り込み不能な待機状態にある可能性があります。Tが継続している場合は、プロセスが一時停止しています。1回のスナップショットだけでは傾向を判断できないため、10秒後にもう一度採取すると有用です。

クリーンアップの前に証拠を残してください。再起動で作業を再開できても、根本原因が解消されたとは限りません。

プロセスとデバッグセッションを検証する

正しい成果物に接続していることを確認する

同名のアプリが、複数のシミュレータ、テストディレクトリ、古いビルドディレクトリに同時に存在することがあります。まずLLDBでプロセスと読み込み済みイメージを確認します。

(lldb) process status
(lldb) target list
(lldb) image list -o -f

target listは今回のビルドで生成された実行ファイルを指している必要があります。また、image listに表示される対象モジュールのパスは、想定したBuild Productsディレクトリ内でなければなりません。別のDerivedDataから読み込まれている場合でも、すぐにすべてを削除しないでください。まず古いパスを記録し、Schemeが誤ったビルド成果物を再利用していないか確認します。

LLDBのコマンドライン自体が応答する場合は、次のコマンドを実行します。

(lldb) thread list
(lldb) thread backtrace all

すべてのスレッドでバックトレースを取得できるなら、通常はデバッグ経路が確立されています。問題はアプリ内部の待機またはシンボル解決にある可能性が高くなります。コマンドが応答し続けない場合は、システム側から対象プロセスをサンプリングします。

sample <PID> 10 1 -file "$HOME/lldb-case/app-sample.txt"

sampleを連続して複数回実行しないでください。10秒間のサンプルがあれば、メインスレッドがロック、ファイルI/O、ネットワーク呼び出し、システムフレームワークのどこで待機しているかを判断できます。

UUIDでdSYMを照合する

ファイル名が同じでも、シンボルが一致するとは限りません。リンクするたびに異なるUUIDが生成される可能性があり、LLDBが使用するのはMach-Oに対応するdSYMだけです。

dwarfdump --uuid "/path/to/MyApp.app/MyApp"
dwarfdump --uuid "/path/to/MyApp.app.dSYM"

両方で同じアーキテクチャのUUIDが一致している必要があります。アプリにarm64が含まれる場合は、少なくともarm64の行を照合してください。別のArchive、別のコミット、または再リンク後のdSYMで代用してはいけません。

モジュールが読み込まれているか確認する

LLDBで対象モジュールを検索します。

(lldb) image lookup -n AppDelegate
(lldb) image lookup -r -n 'YourModule\..*'
(lldb) breakpoint list

image listにモジュールが表示されているのにimage lookupでシンボルが見つからない場合は、デバッグ情報形式を優先して確認します。開発用ビルドでは通常、DWARFまたはDWARF with dSYMを生成する必要があります。最適化レベルの高い構成では、関数がインライン化され、ローカル変数が表示されなくなることがあります。この場合は、チーム共用のRelease構成を一時的に変更するのではなく、診断専用のConfigurationを用意してください。

ブレークポイントにpendingと表示されても、必ずしもエラーではありません。動的フレームワークがまだ読み込まれていない場合、対応するイメージがプロセスに入るまでブレークポイントは待機します。同じブレークポイントを何度も削除するのではなく、確実に読み込まれるエントリポイントにブレークポイントを設定し、その後に読み込まれるモジュールを確認してください。

システムログからハンドシェイク失敗を探す

Xcodeの画面には「接続に失敗しました」としか表示されないことがありますが、システムログには、プロセス終了、権限拒否、接続中断など、より具体的な情報が残ります。再現前に、対象を限定したログ収集を開始します。

log stream --style compact --info \
  --predicate 'process == "debugserver" OR process == "lldb-rpc-server"' \
  > "$HOME/lldb-case/debug-session.log"

一度再現したら、Control-Cで停止します。ログにはユーザー名、プロジェクトのパス、デバイス識別子が含まれる場合があります。サポートチケットへの添付やチーム内での共有前に匿名化してください。システム全体を対象にした不要な--debug収集を長時間続けると、大量のノイズが発生し、絞り込みの負担も増えるため避けます。

ログに、接続した瞬間に対象プロセスが終了したことが記録されている場合は、まずLLDBを使わずにアプリを単独で起動し、安定して動作し続けるか確認します。テストプロセスだけが失敗する場合は、メインアプリのPIDだけを見るのではなく、テストホスト、テストBundle、テスト対象アプリをそれぞれ確認してください。

再現可能な復旧手順を確立する

復旧作業は影響の小さい手順から始めます。

  1. 現在のデバッグセッションを終了しますが、対象アプリとログは残します。
  2. Scheme、Configuration、実行先、実行ファイルのパスを確認します。
  3. Mach-OとdSYMのUUIDを比較します。
  4. プロジェクト全体をクリーンアップせず、現在のTargetだけを再ビルドします。
  5. 古い成果物が再利用されていると確認できた場合に限り、該当プロジェクトのDerivedDataディレクトリを削除します。
  6. それでも再現する場合は、スレッドのバックトレース、プロセスサンプル、システムログ、最小再現手順を保存します。

ほかのタスクのデータを誤って削除しないよう、まず次のコマンドでディレクトリと更新日時を確認できます。

find "$HOME/Library/Developer/Xcode/DerivedData" \
  -maxdepth 1 -mindepth 1 -type d -print

クラウドMacでは、ビルド、テスト、GUIデバッグの各タスクが同時に動作している場合があります。クリーンアップの前に、ほかのパイプラインが同じディレクトリを使用していないことを確認してください。より安全なのは、タスクごとに独立した-derivedDataPathを使用し、デバッグ時の状態と自動ビルドのキャッシュを分離する方法です。

調査結果を受け入れ基準にする

修正後は、少なくとも4項目を検証します。コールドスタートから接続できること、起動済みプロセスに接続できること、ソースコードのブレークポイントで停止できること、例外による一時停止時に重要な変数を確認できることです。その後、リモートセッションから一度退出して再接続し、もう一度テストします。これにより、結果が現在のGUIセッションや一時的な環境変数に依存していないことを確認できます。

チームでは、UUIDの照合、成果物のパス、Configurationをビルドチェックリストに含められます。ただし、個人のディレクトリをスクリプトへ固定的に書き込んではいけません。安定したデバッグ手順とは、単に「クリーンアップして再試行する」ことではありません。どのメンバーでも同じ証拠から障害がプロセス、セッション、シンボルのどの層にあるかを判断し、問題が生じた層だけを修正できることが重要です。

よくある質問

LLDBは接続できるのにブレークポイントで停止しない場合、最初に何を確認しますか?

image listで対象モジュールが読み込まれているか確認し、実行ファイルとdSYMのUUIDを照合します。モジュール未読込では保留状態になり、UUID不一致では正しいソース行を解決できません。

デバッグが停止したら、すぐにDerivedDataを削除してよいですか?

先にプロセス一覧、LLDB出力、システムログ、成果物UUIDを保存します。古いビルドデータが原因だと確認できてから、対象プロジェクトのディレクトリだけを削除してください。

リモートデバッグではデバッグ用ポートをインターネットへ公開する必要がありますか?

通常は不要です。クラウドMac上でXcodeとLLDBを動かし、管理されたリモートデスクトップまたはSSHから操作します。転送が必要な場合も待受先を限定します。

VMDebug クラウドMac

1回の注文で専用利用できる物理Mac miniが必要ですか?

M4とM4 Proの2構成、4つのレンタル期間、5つの選択可能なノードを確認し、ワークフローに合ったデバイスを選びましょう。

構成を選んで注文する