ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
実行、ブレーク、検証:LLDBでの効果的なデバッグ方法
LLDBを使用してコードベースの検証とデバッグを行う方法を解説します。クラッシュログとバックトレースを最大限に活用する方法や、アクションと複雑な停止条件によってブレークポイントを強化する方法を学ぶことができます。デバッグ体験を向上させる、「p」コマンドとSwift 6の最新機能についても確認します。
関連する章
- 0:00 - Introduction
- 0:42 - Agenda
- 1:15 - Debugging as a search problem
- 4:07 - Crashlogs & starting the program
- 7:27 - Breakpoints
- 12:10 - Breakpoint actions
- 15:27 - Help command
- 16:05 - High-firing breakpoints
- 19:24 - The p command
- 25:39 - @DebugDescription macro
- 27:50 - Wrap-up
リソース
関連ビデオ
WWDC23
WWDC21
-
ダウンロード
こんにちは Felipeです Apple Debugging Technologiesチームで エンジニアを担当しています ここでは コードを簡単に探索して バグを素早く見つける デバッグの手法を説明します Xcodeには基本デバッガとして LLDBが付属しており プログラムを任意の時点で一時停止し 変数の状態の検証や 式の評価などを行うことができます 今回はLLDBの主なツールをはじめ 新しい機能や あまり知られていない 高度なテクニックもご紹介します まず デバッガのガイドとなる デバッグモデルを定義します その後 クラッシュログを使った デバッグ方法について説明します ブレークポイントを使用してプログラムの実行を 一時停止するさまざまな方法や 特定のコーディングパターンがデバッガと どのように相互作用するかを見ていきます また プログラムの状態を検査する 主要なツール「p」コマンドを紹介します 最後にSwift 6の新機能を紹介します これにより デバッガでのデータ型の表示を カスタマイズできるようになります プログラムの問題をデバッグする際には 通常 いつの時点でプログラムの動作に 問題が発生したのかが提示されます クラッシュすることもあれば 誤った値が表示されることも プログラムがハングすることもあります プログラムの実行が開始された時点から 不正な動作が見られた 時点までのどこかで 誤ったコードが実行されています 目標はそのコードを見つけることです 通常 バグの調査では 様々な時点でのプログラムの状態を 調べることで見つけることができます 検査を重ねるごとに 問題のあるコードに 段々と近づいていきます プログラムの状態を調査する いくつかの方法には馴染みがあると思います 例えば 一部のコードベースでは log文を使用します この場合 ログファイルのエントリを読めば 特定の時点における プログラムを状態を検証できます ログが十分に詳細であれば こうするだけで コード内のバグを特定できます この場合 どの情報を記録するかを プログラマが事前に 判断する必要がありますが ユーザーとデベロッパの間で アプリの診断情報を効果的に やり取りすることができます よく使われる別のテクニックとして printデバッグもあります これはほとんどの方にとって 最初に学ぶデバッグ手法です printデバッグでは print文をプログラムに挿入します コードを再コンパイルして プログラムを実行し バグを再現します 最後に 出力されたメッセージを検証します 新たな出力が必要な場合は このプロセス全体を繰り返します 最終的に プログラムの状態を 詳細に検証してバグを修正します 注意すべきこととして デバッグが終わったら print文を削除する必要があります print文のすべての内容が本番環境に 入り込んでしまったという 笑い話は誰もが持っているはずです このプロセスは非常に時間がかかり ミスも起こりやすくなります このセッションでは デバッガを使用し より短時間で 検索範囲をナビゲートする方法を紹介します そのため LLDBに搭載されている 主なツールについて説明します バックトレース 変数の検査 ブレークポイント 式の評価です またLLDBを使用して プログラムを実行せずに 問題を検証する方法も紹介します デバッガを使用する際には 一貫して3つのアクションを 繰り返すことになります プログラムの実行 特定のポイントでのブレーク プログラムの状態の検証です プログラムを検証したら プログラム実行の次のポイントに進むか 前のポイントの検証が必要な場合は プログラムを再起動します 効果的なデバッグセッションでは この実行 ブレーク 検証のループを 効率的に繰り返すことが重要です 実践してみましょう ほとんどのデバッグセッションでは まずデバッガで コードをコンパイルし アプリを実行します 多くの場合 必要な操作は Xcodeで開始ボタンを押すか コマンドラインで対象の実行可能ファイルと 引数を指定して LLDBを起動するだけです しかし 問題をデバッグするには まずバグを再現する必要があります LLDBではその点でも役立ちます アプリを起動する必要すらない手法を 使用します Appleプラットフォームでは プログラムがクラッシュするたびに クラッシュ発生時のプログラムの状態 に関する情報が収集され クラッシュログが作成されます LLDBでは クラッシュログを取り込み それをデバッグセッションに 似た形式で表示できるため デベロッパはまずこれで クラッシュの 発生状況の初期調査を行えます これだけでバグの原因を 突き止められることもあります マルチプラットフォーム ビデオ再生アプリである Destination Videoのテストを 担当している同僚から クラッシュログが送られてきました これをLLDBで開いてみましょう クラッシュログを開くには このファイルを 副ボタンでクリックし を選びます プロジェクトのコンテキストで ファイルを開くかどうか尋ねられます を選択します
XcodeがLLDBを使用して デバッグセッションを作成し クラッシュ発生時の プログラムの状態を再現します クラッシュの行が強調表示されており コードでJSONファイルを 開けなかったことがわかります クラッシュの直前に プログラムが開こうとしていた ファイルの名前が記録されているので 同僚に連絡してそのログファイルを 送ってもらうことにしましょう しかし ここに至った経緯は何でしょうか このデバッガには その答えを調べられる バックトレースというツールがあります バックトレースは プログラムがこの状態に 至るまでの関数の呼び出し順序 つまりスタックフレームを表します バックトレースを見ることで 各関数が行っていた処理 呼び出された場所 それぞれの出力先がわかります
Xcodeのデバッグナビゲータで 現在のバックトレースを確認できます クラッシュの発生時 プログラムは何をしていたのでしょうか 現在のフレームは JSONを読み込む関数のものです その前のフレームを見てみると ビデオの メタデータがインポートされています これはプログラムの初期化時に 行われた操作です
バックトレースは クラッシュログを使用する場合も 通常のデバッグセッションでも プログラムの制御フローを 把握できる便利なツールです バックトレースを クラッシュログと組み合わせると クラッシュの状況を直感的に把握できます この例では プログラムによって 記録されている可能性がある 情報の特定に クラッシュログが役立ちました この情報により 別の方法で 問題を調査することもできるでしょう クラッシュログで 正確な行番号情報を得るため クラッシュログを作成したアプリのバージョンと 同じコミットでプロジェクトが チェックアウトされていること そのビルドのdSYMバンドルが 利用可能であることを確認してください dSYMバンドルの詳細は「Symbolication: beyond the basics」をご覧ください Destination VideoはSwiftUIで作られています 詳しく調べてみたいので リストにユーザーがビデオを 追加できる機能を試作しました 目的は あるコードがいつ実行されるかを 理解することです LLDBとブレークポイントがどのように 役立つ様子を探ってみましょう アプリのメイン画面でビデオを選択すると そのコンテンツのDetailViewが表示されます
このビューには 私が作成した ボタンが表示され クリックすると ボタンの表示テキストが変わります
仕組みを理解するため このボタンを作成するコードに ブレークポイントを設定しましょう これが試作したコードです コンストラクタで Buttonクラスを呼び出しています 該当する行番号をクリックして ブレークポイントを設定しましょう
ブレークポイントナビゲータに 新しいブレークポイントが表示されました では アプリケの起動時に 何が起きるか見てみましょう LLDBにより このブレークポイントは 3か所に分かれています
そのため このブレークポイントによる中断が複数の コードパスで発生する可能性があります 再びビデオのDetailViewに移動し この仮説を検証してみましょう
デバッガがプログラムを停止しました プログラムが停止した行が Xcodeで強調表示されています Buttonのコンストラクタを 呼び出だそうとしている行です バックトレースを調べると UI要素を構築するネスト呼び出しの 途中であることがわかります
例えば このフレームでは 要素の縦方向スタックを作成しています 前のフレームの内容を見てみましょう
ScrollViewを作成しています
設定したブレークポイントは1つですが LLDBにより この行に関連する場所が 合計3つ特定されています これらに関する情報情報を得るには 「breakpoint list」コマンドを使用します 70行目に設定した ブレークポイントに加えて このブレークポイントに関連する3つの場所の 行番号と列番号も表示されます リストの最初の場所は 現在 プログラムが停止している位置です 70行目にあるButtonの コンストラクタの呼び出しです このことは行と列の情報からわかりますが ブレークポイントの識別子を 参考にすることもできます
LLDBでは 各ブレークポイントの位置に この1.1のように識別子が割り当てられます これは Xcodeでブレークポイント行を ハイライトする際に使用される識別子と同じです リストの2番目のブレークポイントは 識別子が1.2に設定されており Buttonコンストラクタの第1引数である actionクロージャを参照しています このブレークポイントは ボタンのクリック時にトリガされます 最後のブレークポイントの位置は 識別子が1.3で コンストラクタ呼び出し内の トレイリングクロージャを参照しています 上の行でクロージャの本文が 波かっこで始まっていますが 実際には この位置は 次の行に解決されています ブレークポイントに到達してみましょう この最初のコンストラクタ呼び出しから 引き続き実行します
トレイリングクロージャ内のブレーク ポイント1.3でプログラムが停止しました バックトレースにより このコンストラクタが 前フレームであるとわかります
つまり このクロージャを呼び出したのは コンストラクタ自身でした
最後のブレークポイントに到達するには をクリックします
再び停止しました 今回の停止場所は actionクロージャの中です この例からわかるように 非常に基本的なブレークポイントでも 停止の結果は興味深いものです 3つのコード領域は いずれも同じ行に起因しますが 到達先のコードパスはそれぞれ異なります コンストラクタの呼び出し コンストラクタ自体によって 呼び出されるトレイリングクロージャ そして ボタンのクリック時にのみ 呼び出されるactionクロージャです これはSwiftUIのように クロージャを多用する宣言型コードで よく見られるシナリオです クロージャの呼び出しのタイミングは わからないこともあるため クロージャの本文内に行ブレークポイントを 設定して 適切に停止するようにしましょう アプリの一時停止は デバッグサイクルの重要な要素ですが これをプログラム検証と組み合わせることで デバッグ体験をさらに向上できます 例として UI要素とプログラムがどのように やり取りするかを確認し Buttonコンストラクタを呼び出す 最初のブレークポイントに注目しましょう ボタンの作成時にのみ停止するため 残り2つのブレークポイントは 無効にしておきましょう
これでボタンをクリックすると UIが更新され ブレークポイントが トリガされるはずです
LLDBを使用すると 「p」コマンドで リストのサイズを検証できます これについては 後ほど詳しく説明します
リストには 今追加したばかりの ビデオが1つ含まれています タイトルも調べられます
ブレーク/検証のデバッグサイクルでは 同じコマンドを何度も繰り返すと 手間がかかってしまいます ブレークポイントアクションの概念を使えば ブレークポイントへの到達時に デバッガに コマンドを実行させることができます ブレークポイントに到達したら リストの内容を出力するように このサンプルを変更しましょう ブレークポイントを副ボタンでクリックすると メニューが表示されます アクションを追加して 最後にリストに追加された ビデオの名前を出力させましょう
ブレークポイントへの到達後も 実行を継続させることができます
これで コンストラクタが呼び出される たびに キューの情報が得られます
このようにデバッガを活用すれば コードを再コンパイルすることなく 情報を出力できます ここまでは XcodeのGUIを使用して デバッガを操作してきましたが LLDBにも充実したコマンドライン インターフェイスが用意されています 今度はコマンドラインを使用して 先ほどの例の手順を 繰り返してみましょう デバッガコマンドラインにアクセスするため まず アプリを一時停止します
次に「b」コマンドを使用して ブレークポイントを設定します 「b」コマンドは 「breakpoint set」コマンドの 短縮バージョンです
コマンドラインを使用してブレークポイント アクションを追加することもできますが Xcodeで設定済みのアクションが 上書きされることに注意してください 「break command add」と入力して リストに最後に追加された ビデオの名前を出力してみましょう
先ほどと同様に 出力後も実行が継続されます このコマンドは最新の ブレークポイントに影響しますが ブレークポイント識別子を引数として 指定すれば 別のブレークポイントを 変更することもできます
LLDBでは「help」コマンドで すべてのコマンドの説明を 確認できます 「help」コマンドで 特定のコマンドの オプションを確認することもできます LLDBの機能を詳しく知るには 「apropros」コマンドが便利です LLDBのヘルプテキストについて 指定したキーワードを検索して そのキーワードに当てはまるコマンド またはオプションを返します 例えば バックトレースに 関連するコマンドを検索すると 「frame select」コマンドと そのエイリアスの「f」コマンドが返されます 「thread backtrace」コマンドも 返されています デバッグをする際 何度もトリガされる ブレークポイントを作成しがちですが 実際に重要なのはその一部だけです よくある例は ループ内にブレークポイントを設定し 繰り返しのたびに停止するのではなく 特定のイベントの発生時にだけ 停止するようにしたい場合です トリガの頻度の高いブレークポイントを 適切に処理する手法を3つ見ていきましょう リモートに配置されている場合は そのビデオを読み込んで処理します リモートに配置されている場合は そのビデオを読み込んで処理します ビデオが非常に長い時だけ loadRemoteMedia関数で 停止させると良さそうです そのためには 行ブレークポイントを設定し 設定したブレークポイントの ブレークポイント条件を変更して デバッガでプログラムを 停止するかどうかを決めるルールを定義します コマンドラインの場合 「break modify」コマンドを使用し ブレークポイント識別子と条件を 引数として指定します 条件式にはブレークポイントの場所で 有効なコードを使用できます この例では 現在のビデオの長さが 60秒を超える場合のみ プログラムを停止するよう ブレークポイントを変更しています Xcodeでは ブレークポイントを 副ボタンでクリックし を選択して フィールドに条件を入力します 先ほどの例に戻りましょう loadRemoteMedia関数を実行した時だけ processVideo呼び出しで 停止するのもよさそうです この場合 再度 行ブレークポイントを 設定することも可能ですが 今回はブレークポイントアクションを 追加します 先ほどはブレークポイントアクションを 使って変数を出力しましたが このアクションで新しいブレークポイントを 作成することもできます 「tbreak」コマンドを使うと 一時ブレークポイントを作成し 指定した場所で1回だけ プログラムを停止させることができます この例では loadRemoteMediaに 自動継続ブレークポイントを設定して processVideoに一時ブレークポイントを 作成するアクションを追加します
3番目の手法は 一定回数だけ ブレークポイントを無視してから 以降のトリガで停止するものです 例えば コレクションのうち最初の 10本のビデオを無視させてみましょう 先ほどと同様に ブレークポイントを変更しますが 今回は「--ignore-count」フラグを使います Xcodeではから 同じオプションを使用できます 1行のコードが何百万回も 実行されるような極端なケースでは 前2つの手法ではプログラムの実行速度が 著しく低下する可能性がありました 実行のたびにデバッガを一時停止して 続行するかを判断する必要があるからです このような状況では コードの再コンパイルをお勧めします 例えば 停止条件を計算したうえで この条件が真である場合のみ 実行されるブレークポイントを if文の中に設定すると良いでしょう SIGSTOPシグナルを引数として raise関数を使うと便利です こうすることで アプリ停止させるとともに XcodeやLLDBでアプリ実行する場合は ブレークポイントへの到達時と同様に デバッガに処理を引き継ぐことができます ここまでは デバッグサイクルの 2つの要素に焦点を当ててきました プログラムの実行と 問題の場所での停止です ただ プログラムの状態を検証するための 主要ツール「p」コマンドには 簡単に触れただけでした 前の例では 変数の確認や式の評価の 主な手段として 「p」コマンドを使用しましたが LLDBには他にも多くの コマンドが用意されており それぞれに使い道があります これらのツールをすべて理解するのは とても大変ですが Xcode 15以降では 変数の検証や式の評価が 必要な状況において ほとんどの場合「p」コマンドだけで 対応できるようになりました 「p」コマンドは「dwim-print」コマンドの エイリアスとして改訂されており 1つのコマンドに 複数のツールを組み合わせることで 時間の節約を可能にしています 詳しくは「Debug with structured logging」 をご覧ください それでは始めましょう アプリに新しいビデオを追加するため ビデオのJSONの説明を編集しました すると アプリを起動した途端に クラッシュしてしまいました
こちらが 新しいビデオを追加するために 編集したJSONファイルです アプリを起動し クラッシュの発生時点で デバッガを停止させましょう
コンソールを見てみると DestinationVideoのデベロッパは それぞれの ワークフローでログを使用しています
最後に記録された情報は ビデオのJSONファイルが 読み込まれたことでした
プログラムを開始してから 例外が報告されるまでの間に 問題が発生しています この時点で最初にすべきことは JSON読み込み関数を呼び出す直前に ブレークポイントを設定し この辺りにバグがないか調べることです 実際にやってみましょう
ログメッセージを副ボタンでクリックすると ソースコードの対応する場所に 素早く移動できます
この関数の最後に ブレークポイントを設定しましょう
この直前でプログラムを 停止する必要があるので プログラムを再開しましょう コードは変更していないので Controlキーを押しながらクリックして 再コンパイルを省略し 時間を節約できます
ローカル変数のURLと filenameに注目してください
どちらも問題ないようです 値を確認するには 変数ビューアを参照するか
ソースコード上でそれぞれの変数に カーソルを合わせます
一部の変数にはクイックルックボタンがあり 変数の詳細を確認することができます
私が編集したファイルと似ていますね
JSON読み込み関数には 問題がないようなので この後にバグが潜んでいそうです プログラムの他の部分に視線を移し JSONデコーダを引数とする Videoコンストラクタを調べましょう
複数あるtry文のうち いずれかに問題がありそうですが Videoコンストラクタを呼び出すたびに 停止するのは考えものです
アプリのビデオが多すぎるからです このように繰り返しトリガされる ブレークポイントは 説明した手法でうまく処理しましょう 新しいビデオはアプリ内の ビデオでは12番目なので このブレークポイントにignore-countを 使用してもよいのですが 代わりに新しい方法として Swift Errorブレークポイントを使います このブレークポイントは Swift Errorがスローされるとすぐに LLDBにアプリを停止させます
をクリックしましょう
Errorブレークポイントでプログラムが停止し JSONキー「imageName」のデコードが 中断されています ここが問題の発生箇所です バックトレースを使用して 入力データが存在する 前のフレームに移動しましょう
プログラマなので コードを使って この状況を解決したいところです 「p」を使うと自由度が大きく高まります 簡単なプログラミングで この「data」配列内にある imageNameの数を調べてみましょう
文字列の作成が必要ですね
出力項目が多すぎるので imageNameを検索してみましょう
すっきりしましたが それでも多すぎます countプロパティを見てみましょう
ビデオは13本あるはずですが imageNameキーは12個だけです 何か問題が起きています JSONファイルを見てみましょう
imageNameの綴りが間違っていました 修正しましょう
これでよし
この例でお見せしたように 「p」コマンドを使用することで 変数の検証や複雑な式の評価を 実行することができ バックトレースの任意のフレームで こうした操作を行えます 複雑な式を徐々に構築して コードを再コンパイルすることなく 段階ごとに中間結果を 出力できます 今回のデバッグセッションでは JSONファイルに 不足しているキーを追加することで アプリのログを拡張する方法も紹介しました 今回調べたほとんどの変数は 非常にシンプルなものでした しかし 一部の型はデータが多すぎて デバッグセッションで 検証しづらいことがあります こうした型を調査する場合は 変数ビューアや 「p」コマンドの短い説明が役立つでしょう これらの型は多数のプロパティを持つか コレクション内に 格納されていることがほとんどです 例えば 変数ビューアで WatchLaterItemのコレクションを表示しても 手動で展開しない限り 各エントリの情報は 表示されません LLDBには「p」コマンドの出力と 変数ビューアをカスタマイズできる メカニズムが搭載されています Swift 6では@DebugDescriptionマクロにより ソースコードから直接 このカスタマイズを行えるようになりました このマクロで型に 注釈を加えるだけでなく 型の概要を示すDebugDescription 文字列プロパティも作成する必要があります この作成には文字列補間と ストアドプロパティが必要です WatchLaterItem型で実際にやってみましょう このデータ構造体には関連する3つの データメンバーが含まれています ビデオ 名前 リストへの追加日です まずDebugDescriptionマクロで 型をマーキングします 次に debugDescription文字列の 計算型プロパティを作成します ここでは 「name」と「addedOn」を 型のサマリとして使用します その後 変数ビューアを再び見てみると コレクションの各項目に サマリが表示されるようになります
CustomDebugStringConvertible プロトコルの使用経験者なら この例は馴染み深いものでしょう その場合 型の出力には 「po」コマンドを使っていたと思いますが そうした場合は プロトコルの実装を確認してください 文字列補間と計算済みプロパティだけを 使用しているのであれば プロトコルをこのマクロで置き換えられます こうした型はデバッガとの 連携が強化されているため 「p」コマンド1つだけで デバッグを進めることができます 今回は デバッグサイクルを 複数回繰り返して デバッグを有効活用するための 主な概念について学びました デバッグを検察問題として 扱う方法についても説明しました LLDBは条件付きブレークポイントと 変数検証機能を備え こうした検索を効果的に行えるツールです また 不慣れなコードベースを 理解する際に役立つ 優れたツールでもあります ブレークポイントアクションと 式の評価を使うことで コーディングのスキルを活かし デバッグしながらコードを実行できます その際 プロジェクトの 再コンパイルは必要ありません 本日ご紹介したアイデアを バグの特定の スピードアップに お役立ていただければ幸いです バグの根本原因を特定したら 見逃していた可能性のある テストカバレッジの追加もお忘れなく ご視聴ありがとうございました
-
-
8:09 - WatchLater button
Button(action: { watchLater.toggle(video: video) }) { let inList = watchLater.isInList(video: video) Label(inList ? "In Watch Later" : "Add to Watch Later", systemImage: inList ? "checkmark" : "plus") }
-
12:54 - Printing watch later list information
p watchLater.count p watchLater.last!.name
-
13:45 - Breakpoint actions: Printing name of the most recently added video
p "last video is \(watchLater.last?.name)"
-
14:42 - Breakpoint actions: on the command line
b DetailView.swift:70 break command add p "last video is \(watchLater.last?.name)" continue DONE
-
26:46 - @DebugDescriptio macro example
// Type summaries @DebugDescription struct WatchLaterItem { let video: Video let name: String let addedOn: Date var debugDescription: String { "\(name) - \(addedOn)" } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。