ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
LLDBによるSwiftのデバッギング
複雑なSwiftプロジェクトを設定してデバッグを行う方法を紹介します。LLDBの内部とデバッグ情報について詳しく解説します。また、ビルドサーバに構築されたコードやカスタムビルドシステムのコードのデバッグなど、複雑なシナリオでのベストプラクティスも紹介します。
リソース
関連ビデオ
WWDC21
WWDC19
WWDC18
-
ダウンロード
♪ ♪
みなさん こんにちは 私の名前はAdrianです LLDBで素晴らしいデバッグ体験をする プロジェクトの設定方法についてお話します LLDBは Xcodeに同梱されている デバッグの基礎となる技術です LLDBでは Appにブレークポイントを設定したり 実行を一時停止したり 変数やオブジェクトの状態を検査したり コードを探索したりと さまざまなことが可能です LLDBは コードが何を 行っているか理解するのに役立ち コードの動作が期待から乖離するポイントを 見つけることを可能にします コードを理解し探求する強力なツールです LLDBを更に知りたい方はWWDC21のビデオ 「ブレークポイントの改善」をご覧ください 今日は Swift コードのデバッグに ユニークな意味を持つ いくつかの高度なワークフロー について見ていきます サードパーティのフレームワークを Appに統合しているかもしれません あなたのAppとチームはコードのほとんどを 継続的 統合システムで 構築するまで成長されたかも 会社のインフラと統合するため カスタムビルドの システムを使用しているかもしれません 他のソフトウェア開発者のため ソフトウェアを作っているかも あるいは LLDBについてもっと知りたいという方 私の目標は LLDBがどのように 動作し 機能するために ビルドシステムからどの情報を 必要かを理解することです ここに 実行例として使用する 小さなプロジェクトがあります 私はコンパイラエンジニアですが ゲームが好きなので 暇なときはテキストアドベンチャーの パーサーを書いたりしています これは最近 純粋なSwiftで始めたものです 今までのものをお見せしましょう このゲームはテキストインターフェイスを 使うので Terminalで実行しています 冒険の常としてまずインベントリを確認します
このゲームの舞台は現代です 私はiPhoneを持っていますね 次に 周囲の環境を見てみましょう
うーん このセンサは興味をそそられますね センサーにiPhoneが使えるかも?
iPhoneを落とした? えー お見せしたかったのはこれではありません 私のゲームにはバグがあるようです デバッガー談義でよかった パーサにブレークポイントを設定し もう一度コマンドを実行しましょう
コマンドが正しく読まれたことを 確認する必要があります words変数には トークン化 されたコマンドが格納されます
ああ これは予想通りにいきませんでした どうなっているんでしょう? 昨日は問題なくデバッガーを使っていましたが 昨晩 ターミナル上テキストを スタイリングするための このUIフレームワークを統合しました このフレームワーク開発者は 継続的統合システムによって フレームワークを毎晩ビルドしており 私はその最新版に直接リンクしています このフレームワークは 私のデバグの 悩みと関係があるでしょうか 例えばデバッグビルドを明示的に ダウンロードしたに関わらずフレームワークの ソースコードに踏み込めない ことに既に気づきました 見てください
分解しか見れません
そこで何が起こったのか ソースコードが見れなかった 理由を解明してみましょう LLDBがソースコードを 表示するために必要なものは? コンパイラは関数をコンパイルするとき マシンコードを生成します
また デバッガにパンくずを残すので 実行ファイルアドレスと ソースファイルや行番号の 対応付けができその逆も可能です このパンくずをデバッグ情報と呼びます Appleプラットフォームでは デバッグ情報はオブジェクトファイルに入ります アーカイブや配布のためデバッグ 情報を.dSYMバンドルにリンクできます デバッグ情報リンカは dsymutilと呼ばれています LLDB は Spotlightを使い .dSYMバンドルを見つけるので ディスク上のどこにあるかという 点ではかなり柔軟です さて デバッグ情報の仕組みが わかったので例に戻りましょう LLDB がフレームワーク用の DSYMを見つけたか確認しましょう これは 画像一覧コマンドで行えます UIフレームワークは 「TerminalInterface」と呼ばれます
はい LLDB はフレームワークの DSYM を発見しました つまり デバッグ情報にアクセスできるのです 「画像検索」で現住所の 詳細な情報を得ることができるのです
様々なオプションについて もっと知りたい場合は LLDBに素晴らしいヘルプが内蔵されています
なぜソースコードが ないかわかった気がします: このソースパスはビルドサーバ上の ソースの場所を指しており 私のローカルマシン上の ソースの場所ではありません 解決することができます LLDBにはパスをリダイレクトする ソースマップが組み込まれています
今すぐコマンドを入力することもできますが 私はこの変更をより恒久的なものにしたいです ProductのSchemeからEdit schemeまたは 再生ボタンクリックで表示される Schemeエディターでプロジェクトごとに LLDB initファイルを定義することができます 今回のプロジェクトでは すでに1つ追加しています
LLDB を設定したので もう一度プロジェクトを実行ましょう
ソースコードもあります
LLDBはsettings set target.source-map でソースパスを再マップできます このコマンドをプロジェクトの .lldbinitファイルに記述することで 自動的に実行されるようになります 各 .dSYM バンドルには XML .plistファイルが含まれ ここにパスプレフィックス 再マッピング辞書を置けます サーバから最新のビルドを取得する ダウンロードスクリプトがあれば そのスクリプトを修正し ダウンロードした .dSYM に 適切な再マッピング辞書を 自動的に注入できます このプロセスの詳細は LLDBのサイトをご覧ください
ソースパスは言語に全く依存しないので この方法はSwift C++、 Objective-C プロジェクトで同様に機能します Appleプラットフォームのシンボルの詳細は このセッションをご覧ください WWDC21「シンボリケーション:基礎を超えて」 ビルドサーバファームで ソースコードをコンパイルする場合 ソースファイルのリモートパスが マシンごとに異なる可能性があります マシンごとにリマッププレフィックス を定義する必要がないように デバッグ情報に入れる前に ソースパスを正規化するように コンパイラに指示することができます これは debug-prefix-map オプションを使い行います この方法は マシン固有の パスプレフィックスを一意の正規の プレースホルダー名に置き換えて LLDB のローカルパスに 再マップすることができます ソースの余談になる前に 「言葉」のオブジェクト記述を 印刷しようとしていました
うまくいきませんでした 実際「言葉」という表現の 評価さえうまくいいませんでした
少なくとも 変数ビューで 変数を確認することはできます
Xcodeの変数表示に相当するコンソールは frame variableまたはvコマンドを使用します
これらコマンドのニュアンス について詳しく知りたい方は WWDC19「LLDB:’po’の先へ」をご覧ください では poとは何かなぜうまくいっていないのか? この意味を理解するため LLDBに ついて詳しく知る必要があります 注意点としてLLDBはデバッガーです しかし LLDBは単なるデバッガではありません コンパイラでもあるのです! LLDBにはデバッガの機能に加えて SwiftおよびClangコンパイラの 完全な機能コピーも含まれます これらのコンパイラはLLDBの 式評価器を動かすもので pやpoコマンドのエイリアスで 知っている方も多いでしょう 式評価器では変数を見るだけでなく 計算を実行したり関数を呼び出したり プログラムの状態を変更したりできます WWDC18「XcodeとLLDBでの高度なデバッグ」で それらのコマンドで何が可能かわかります デバッガーはどのように ローカル変数をフォーマットするか? コンパイラ提供のデバッグ情報は デバッガに変数が メモリ内のどこに格納されるかを知らせます しかし その情報だけでは LLDBはランダムな生バイトの 詰め合わせしか表示できません では LLDBはどのように フォーマット出力に変えるのか? 答えは タイプです 型情報によりLLDB はソース変数の構造と メモリレイアウトを理解することができます タイプ情報を使うとLLDBは 集約型がどのフィールドを持つかを 把握し 型によってLLDBは 適切なデータフォーマッタを使用し きれいに印刷することができるようになります では タイプ情報はどこから来ているのでしょう フレーム変数やvコマンドが 存在するデバッガ側では LLDBはDebug Infoから型情報を取得します LLDBはSwiftの反映メタデータ からタイプも取得します コンパイラ側では式評価器とpoが存在し LLDBはModuleからタイプ情報を取得します このきれいな分離はXcode 14の新機能で 式評価器が機能しなくても変数ビューが 完全に機能する理由を説明しています モジュールは コンパイラが タイプ宣言を整理する物です Swiftコンパイラはモジュールの インポート方法を多く知りますが その前に 便利な新機能を お見せしたいと思います
コンパイラ側で起きている問題は どう診断すればいいでしょうか? 今年 LLDBは新たに swift-healthcheck コマンドを追加しました モジュールのインポートに 失敗したかを判断する最初の手段です この仕組みを紹介しましょう 問題が発生した後にswift-healthcheckを実行し Swift式評価器の設定のログにアクセスできます
ログの最後にLLDBが "TerminalUI" Swiftモジュールの インポートに問題があったことがわかります 名前からしてTerminalInterface フレームワークの 実装の詳細だと思われます このモジュールの欠落は selfの型がUI実装上の総称であり そのタイプを含むモジュール がないと 式評価器がselfの 動的なタイプを実現 できないため問題となります フレームワークの開発者にメッセージを送り 調査を依頼しています 私の経験ではいつもとても反応が良いです もしかしたら この映像が終わる前に 解決策が見つかるかも LLDBコンパイラが Swiftモジュール をどう見つけるか見ましょう
私のAppには 独自のSwiftモジュールがあります Foundationなどシステムフレームワーク をインポートすることもあります システム構成はSDK内に存在する テキスト形式Swiftファイルです どのSwiftモジュールもClangモジュールを インポートするかもしれません これはモジュールマップファイルの 助けを借りて一緒にグループ化された 1つ以上のヘッダファイル のための空想の名前です Clangモジュールは 他の Clangモジュールに依存できます
私のAppは ローカル構築された構成に 属するSwift モジュールを取り込むこともあります また SDKに含まれないテキスト形式の Swiftインターフェースファイルを インポートすることも可能でした 方法を学びたい方はWWDC19のをご覧ください 「Swiftのバイナリフレームワーク」 私のAppは Swift コードを 含む静的ライブラリに リンクすることもあり その場合 Swift モジュールも付属しています うーん まだ終わっていません ブリッジングヘッダもありClangモジュールを インポートもできることをお伝えしておきます 最後に LLDBだけの特別な機能として デバッグ情報のみからモジュールの 内容を再構築することができます すごい数のソースです! LLDBはどうそれらを全て見つけるのでしょう?
LLDBがモジュールを見つけられるよう パッケージするのはビルドシステムの仕事です システムフレームワークの モジュールはSDKに残ります LLDB は プログラムにアタッチする際 一致するSDK を見つけ 読み取ります オブジェクトファイルから 直接デバッグする場合 LLDBはビルド時にあった 全非SDK モジュールを見つけます Dsymutilは全ダイナミックライブラリ 構成 dylib および実行可能ファイルに .DSYMバンドルと呼ばれる デバッグ情報アーカイブをパッケージ化します
それぞれの.dSYM バンドルはバイナリ Swiftモジュールを含むことができ それはブリッジングヘッダを含むことができ 最も重要なのはデバッグ情報を 含むことができることです これですべてカバーできます 全て? 静的アーカイブに属する Swiftモジュール以外すべて
Swiftのモジュールを dsymutilに取り込ませるには リンカーに登録する必要があります ダイナミックライブラリと実行可能ファイル については ビルドシステムが自動的に行います しかし 静的アーカイブは リンカが生成するものではなく ZIPファイルのようにオブジェクト ファイルの集合体に過ぎません つまり Swiftのモジュールを リンカに登録する責任は 静的アーカイブをリンクする 実行ファイルや動的ライブラリの すべてにあるのです 多くの場合 Xcodeのビルドシステムが あなたのためにこれを行います 独自のカスタムビルドシステムを 保守している場合や カスタムビルドルールを定義している場合は この点に注意する必要があります
Appleリンカを使用する場合 Swiftモジュールは-add-ast-path オプションで登録の必要があります ビルドログで確認してください dsymutil を使って実行ファイルの シンボルテーブルをダンプし grepでswiftmoduleを検索すれば 動作の確認ができます
Linuxのような他のプラットフォームでは swiftドライバはバイナリ Swiftモジュールファイルを デバッグ情報の残りの部分と一緒に バイナリにリンクできるオブジェクトに 変換する-modulewrapアクションを サポートしています LLDBはそこで見つけることができます フレームワークの開発者の方々は 驚くほどの対応をしてくれました 予想通り 構成のビルドシステムの 一部として 静的アーカイブが 使用されていることが判明しました その静的アーカイブに属するSwiftモジュールが dSYMのバンドルから抜けていたのです 現在 修正版のフレームワークを インストールしています リンカに不足している 静的モジュールを登録したので dsymutilはそれを収集することができました
これで自己解決できます
そして "words "のオブジェクト 記述を印刷ができます
コンソールを使っているので sエイリアスを使って parseFrom関数に踏み込んでいます
ここでは単なるコピー&ペーストのミスである バグも簡単に見つけることが できるようになりました
これで消えたSwiftモジュールの 謎が解けただけでなく ゲームの最初の謎も解けたのです
最後に もうひとつ気をつけたいことがあります Swiftコンパイラは Clangヘッダの検索パスやその他の 関連オプションをバイナリの .swiftmodule ファイルにシリアライズします 素晴らしいことで ビルド中に そのClangモジュール依存性を インポートするだけで動作するようになります しかし 別のマシンでビルドする場合 これらローカルパスは不利な場合があります そのため バイナリの.swiftmodule を他のマシンに出荷する前に -no-serialize-debugging-options コンパイラフラグで ビルドすることを検討してください XcodeではSWIFT_SERIALIZE_DEBUGGING_OPTIONS の設定により制御されます
これら検索パスは 以下の設定で LLDBに再導入ができます 学んだことを振り返ってみましょう あるマシンから別のマシンへ コードを出荷する場合 どの程度のデバッグを想定するか 自問自答する必要があります 例えば バイナリフレームワークを 別の開発者に出荷し 彼らがデバッガであなたのコードに 踏み込むことを期待しない場合 テキスト形式の.swiftinterfaceファイル としてSwiftモジュールの出荷が最善です もしあなたがビルドサーバまたは 継続的統合システムを設定して 開発者がダウンロードされたビルド成果物を デバッグすることを期待されるなら Swiftのバイナリモジュールを ビルドすることを確認し 検索パスのシリアライズを オフにすることを検討したいでしょう また -debug-prefix-map オプションを使用すると デバッグ情報においてサーバ上の ソースパスを正規化できます 私からは以上です LLDBのデバッガとコンパイラの 2つの性格を学びました デバッガが機能するためには デバッグ情報と反映メタデータが必要で Xcodeの変数ビューとvコマンドを提供します コンパイラはModuleを必要とし 検索パスに敏感です expr p poコマンドの後ろにあります コンパイラの診断を受けるには LLDBの新しいコマンドである swift-healthcheckが便利です ご視聴ありがとうございました!
-
-
5:04 - Show info about all loaded dylibs
image list
-
5:24 - Show debug info for a code address
image lookup -va $pc
-
5:58 - Show help for target.source-map
settings list target.source-map
-
6:37 - Remap source paths in LLDB
settings set target.source-map /Volumes/BUILD_SERVER/projects /Users/demo/Desktop/Adventure/3rdparty
-
7:02 - Source path remapping
settings set target.source-map prefix new
-
8:13 - Debug prefix map
-debug-prefix-map $PWD=/BUILDROOT
-
8:32 - Print object description of "words"
po words expr -O -- words
-
8:40 - Evaluate the expression "words"
p words expr words
-
8:58 - Display the variable "words"
v words frame variable words
-
10:10 - Raw memory of a Swift variable
mem read UnsafePointer<Items>(self.inventory)
-
11:59 - See diagnostics from LLDB's embedded Swift compiler
swift-healthcheck
-
15:47 - Register Swift modules with the Linker
ld … -add_ast_path /path/to/My.swiftmodule
-
16:05 - Verify Swift modules were registered in binary
dsymutil -s MyApp | grep .swiftmodule
-
16:12 - Wrapping Swift modules in object files on Linux
swiftc -modulewrap My.swiftmodule -o My.swiftmodule.o
-
16:52 - Evaluate the expression "self"
p self
-
16:58 - Print object description of "words"
po words expr -O -- words
-
17:08 - Step into function call
s thread step-in
-
17:10 - Step over instruction
n thread step-over
-
18:23 - Avoiding serialized search paths in Swift modules (command line)
-no-serialize-debugging-options
-
18:24 - Avoiding serialized search paths in Swift modules (Xcode)
SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO
-
18:32 - Reintroducing search paths in LLDB
settings set target.swift-extra-clang-flags … settings set target.swift-framework-search-paths … settings set target.swift-module-search-paths …
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。