ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
LLDB:「po」の先へ
LLDBは、実行時にAppの確認とデバッグができる強力なツールです。このセッションでは、Appの値を表示するさまざまな方法、カスタムのデータ型をフォーマットする方法、独自のPython 3スクリプトを使用してLLDBを拡張する方法について説明します。
リソース
関連ビデオ
WWDC22
WWDC21
WWDC18
-
ダウンロード
(音楽)
こんにちは Debugging Technologies Teamの ダビデです 同僚のジョナスと進めます “po”をご存じですね LLDBで変数を出力します 今日は この仕組みを説明します ソースコード中の 変数を出力する別の方法や フォーマットの仕組みも 紹介します LLDBはXcodeの変数ビューを 機能させるデバッガです 変数と型を見ることができます デバッグ中に コマンドの入力も可能です 画面右下のコンソールで LLDBを操作します アプリケーションの バグを調査する時に 変数の値を出力できます LLDBには いくつかの方法があり それぞれに特性があります 見てみましょう 例として 構造体“Trip”を使います プロパティはnameと 配列のdestinationsです ではクルーズに出かけましょう 1つめの検証はpoコマンドです “print object”の意味ですね このコマンドは オブジェクトの記述を返します 型のインスタンスを テキストで表したものです デフォルトの記述ですが カスタマイズもできます CustomDebugStringConvertibleに 準拠させればいいのです 必要なプロパティは debugDescriptionです これで デバッガに出力すると カスタマイズした形で 表示されます 変わるのは最上位だけです
下位も変えたい場合は CustomReflectableプロトコルの ドキュメントを確認してください Objective-Cでも可能です descriptionメソッドを 実装してください 単なる表示だけではありません 例えば nameを大文字に変換したり destinationsを アルファベット順にしたりできます 基本的には任意の式が使えます コンパイルできるものであれば 引数として渡せるのです 実は poはexpressionコマンドの エイリアスです “object-description” オプションを含みます LLDBは キー入力を簡略にできるのです 独自のpoを実装するには aliasコマンドを使います 引数は独自のコマンド名と 元になるコマンドです こうすると 他のコマンドと同じように使えます では poの仕組みを 詳しく見ていきましょう poが値を出力するステップです 各言語の表現性を生かすため LLDBは式の解析や 評価をしません 与えられた式をコンパイルし ソースコードを生成します ご覧の抜粋と同じです 次に 組み込みのSwiftで コンパイルし デバッグするプログラムの コンテキストで実行します 実行が完了すると LLDBが結果にアクセスします
そこから オブジェクト記述を拾うため 直近の結果を 別のソースにラップしておきます これもコンパイルし 同じコンテキストで実行します この実行結果は文字列です LLDBがpoの結果として 表示します LLDBの変数出力は poの他に2つあります 見てみましょう 2つめの方法はpコマンドです “print”の意味ですね 表示を見てみましょう まず気がつくのは poのものと少し違うということです とはいえ 表示される内容は同等です それから 実行結果に “$R0”という名前がついています これはLLDBの特殊な仕様です すべての結果に連番名がつきます “$R1”“$R2”という具合です この名前は あとで使うことができます 他の変数と同じように $R0を参照できるのです 構造体のメンバも表示できます pもpoと同じで 短縮形のコマンドです expressionコマンドの エイリアスですが “object-description” オプションを含みません 先ほどのpoと同様に pの仕組みを見ていきましょう
オブジェクトの記述が不要なため 処理は簡潔です poの説明で使った図を 思い出しますね 確かに 冒頭の部分― コンパイルから式の評価までは 全く同じです 結果を取得すると LLDBが型の動的解決を行います 詳しく説明します サンプルコードを少し修正します やってみましょう 構造体Tripを変更して プロトコル“Activity”を 実装します Swiftではソースコード上の 静的な型と― 実行時の動的な型が 異なっても構いません このタイプのプロトコルで 変数を宣言することもあります このcruiseの静的な型は Activityです 実行時は構造体Tripの インスタンスです こちらは動的です cruiseの値として Tripの型が表示されます 結果のメタデータから― その時点で最も正確な型を LLDBが表示するためです これが動的解決です pコマンドは この処理を 結果に対して1度だけ行います cruiseのメンバを 見たいとします LLDBがpコマンドで この式を評価すると cruiseの型はActivityであり nameというメンバはありません 結果はエラーになります なぜ こうなるのでしょうか LLDBがpコマンド実行時の コンパイルで参照するのは ソースコード中の静的な型です ソースコードに“cruise.name”と 書くのと同じなのです 静的コンパイルでは エラーになります エラーを出さずに実行するには 動的な型に キャストしなければいけません その上でアクセスします これはデバッガでも ソースコードでも同様です
まだ処理は続きます 型の動的解決をしてから LLDBがフォーマッタに 結果を渡します 私たちが読める形で表示するために 必要な処理です 見てみましょう 仕組みを説明するために― インプットとアウトプットを お見せします これが フォーマットしていないものです rawオプションで確認できます 標準ライブラリでは― string型もint型も 見慣れない表示です 速度とサイズの最適化のためです フォーマッタを使うと 思いどおりに文字列が並んで 表示されます LLDBには よく使うフォーマットが 多数 用意されています カスタマイズも可能です 後ほど説明します pコマンドとpoコマンドに続き 3つめのコマンドを紹介します vコマンドです アウトプットは pコマンドと同じです フォーマッタも適用されます vコマンドもエイリアスです 元はframe variableコマンドで Xcode 10.2から導入されました 先ほどの2つと違い コンパイルされないので― 速く処理できます vコマンドの構文は独自のものです デバッグ中の言語と 違っても構いません ドットや演算子を使って フィールドにアクセスします しかし オーバーロードはせず プロパティも評価しません コードを実行する必要が あるからです 適宜 pかpoを使いましょう お察しのとおり vコマンドは 他の2つとは仕組みが異なります では 詳細を説明します おなじみの図ですが vのものです 変数を出力する場合 まずはプログラムの状態を調べて 変数をメモリに配置します 次に メモリから 変数を読み込みます それから動的解決を行います サブフィールドを出力する場合は 個数分の処理を繰り返し その都度 動的解決をします 完了したら 結果をフォーマッタに渡します vコマンドは複数回 動的解決を行います これは重要なポイントで pコマンドと大きく異なる点です フォーマッタの動的解決は 1度だけです 処理を見てみましょう
pコマンドでエラーになった サンプルに戻ります 各ステップで 動的解決を行うので vコマンドではcruiseが 構造体Tripだと分かり メモリにアクセスできます これが vがpより 優れている点です キャストせずに 構造体を参照できるのです 以上 3種類の変数出力コマンドを 説明しました
まとめです po p v 各コマンドの違いを 比較してみましょう まずは変数の表示方法です poコマンドは オブジェクトの記述を用い pとvはフォーマッタを用います 結果の取得方法も重要です poとpは式をコンパイルして すべての言語に対応します vコマンドは独自の構文を持ち 解釈の各ステップで 動的解決を行います
先ほど カスタマイズについて触れました 詳細は同僚のジョナスが説明します Debugging Technologies Teamの ジョナスです LLDBはデバッガの変数表示形式を フォーマッタで定義します
共通のフォーマッタがあります vコマンドを使うと 構造体のメンバを出力できます 配列要素は読みやすく フォーマットされます 通常は デフォルトフォーマッタが― ユーザ定義型にも 標準データ型にも作用します それでも カスタマイズしたいことが あるでしょう
それを実現してくれるのが フォーマッタの拡張性です 型ごとに定義できます カスタマイズに使えるのは― フィルタ サマリ synthetic childrenです
フィルタは 表示を制限するものです 今はTripのdestinationsは 数個ですが 数が増えれば 表示が乱雑になります フィルタを追加すると nameだけを表示できます これはコンソールの 表示だけでなく 変数ビューでも有効です フィルタを外して 進めましょう
サマリは 一目で型が分かるように 型の表示を変えます フォーマッタの定義は poで設定したものと同じです フィルタと同様に 変数ビューでも有効です Trip自体にはサマリがないので 変更してみます
最初と最後のdestinationsを 表示したいです サマリの文字列は テキストを格納でき― 実行時のフィールドに アクセスできる特別な変数です この変数は $で始まり {}でくくります 構文はvコマンドと同じです サマリ定義する変数は “var”で参照します Tripのメンバはvar.nameと― var.destinationsとします しかし問題があります destinationsが3個の場合しか 成立しません 配列の個数を取得できないため 最後のインデックスが 固定値なのです
ここで強力なツールが使えます Pythonで定義できるのです このフォーマッタは 任意の計算ができ LLDBのAPIに フルアクセスできます 現在のデバッグセッションを 扱うAPIです
ターゲットはデバッグ中の プログラムです プロセス スレッド フレーム すべてがランタイム情報に アクセスします 変数やレジスタや式の値は SBValueクラスで表されます データフォーマッタには 好都合です 型と値の操作には 慣れているからです 詳細はオンラインドキュメントを ご覧ください
Xcode 11からPython 3になります Python 2からの移行については Xcodeのリリースノートを 確認してください
LLDB APIを見てみましょう scriptコマンドで Pythonインタプリタを起動します 現在のフレームはlldb.frameで アクセスできます SBFrameのインスタンスが 返されます 現在のフレームには 変数cruiseがありますね FindVariableを使って SBValueを取得します 内部でフォーマッタが動くので 先ほどの表示と同じに見えるのは 当然ですね
メンバ変数名が分かるので GetChildMemberWithNameで アクセスできます 結果は別のSBValueで 配列として表示されます
Pythonでフォーマッタを 再現してみましょう 最後のインデックスは 固定にしません GetNumChildrenで 要素数を取得します GetChildAtIndexで― 最初の配列や最後の配列に アクセスできます 出力値はコンテキスト依存です インデックスは 配列で保持しています 親リレーションのコンテキストを 保持するのです これで1つの文字列にできました しかし期待どおりではありません SBValueの記述が 出力されています 見たいのはサマリです 代わりに GetSummaryの値を使えます これでdestinationsだけを 表示できました 統合しましょう フォーマッタは コンソール入力の他に ファイル読み込みも可能です ここでは“Trip.py”という ファイルを作成します ファイルに定義する場合は 現在のフレームを使わず― SBValueを引数として渡します このあとの手順は 先ほどと ほぼ同じです Pythonを使う もう1つの利点は 制御フローです Tripにdestinationsがない場合 “Empty trip”と表示できます 最初と最後のdestinationsを取得し サマリを返します
では LLDBに 新しいサマリをロードします “command script import” コマンドを使います 次に このフォーマッタを Trip専用にします “type summary add”で フォーマットを適用する型と サマリを指定します 必ず完全修飾型にしてください これでcruiseに Pythonのサマリが使われます
コンソールだけではなく 変数ビューにも適用されます フィルタと サマリの説明をしました 最後のカスタマイズ方法は synthetic childrenです 変数ビューの表示が 複雑な型など どんなものでも カスタマイズできます Pythonでは すべての子に サマリを持てます
synthetic childrenは サマリに似ています ただし関数ではなく メソッドを実装したクラスです 初期処理の他に 子の数の取得メソッドや― 子自身やインデックスの 取得メソッドを定義します サンプルは このセッションの リソースリンクで参照できます
先ほどと同様にPythonの ソースコードをLLDBにロードします ファイルはロード済みなので 再ロードされます フォーマッタを設定するため “type synthetic add”で 型とクラスを指定します
せっかくカスタマイズしたので デバッグ終了時に 消したくないですね コンソールで実行したコマンドは ホームディレクトリの .lldbinitファイルに保存されます デバッグセッションの開始時に 自動でロードされます
LLDBにはプログラムの状態を 確認する様々な機能があります v p poは変数を出力します 値の表示 コードの実行 記述の取得など 目的で使い分けます フォーマッタのカスタマイズには フィルタ サマリ synthetic childrenを使います 最後に― Python 2のスクリプトは Python 3互換に修正してください バージョン変更は Xcode 11からです
詳細はデベロッパWebサイトを ご覧ください
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。