目次
Session概要
LLDBは、実行時にアプリの確認とデバッグができる強力なツールです。このセッションでは、アプリの値を表示する様々な方法、カスタムのデータ型をフォーマットする方法、独自のPython 3 スクリプトを使用してLLDBを拡張する方法についての説明 https://developer.apple.com/videos/play/wwdc2019/429/
LLDBについて
- Xcodeで扱う変数のビューアーとして機能させるデバッガ
- 変数と型を確認可能
- デバッグ中にコマンドの入力も可能
- po (print object): オブジェクトの記述・説明を返す、型のインスタンスをテキストで表す
- カスタマイズ方法
- デバッグ対象オブジェクトをCustomDebugStringConvertible に準拠させ最上位の説明を変更可能
- オブジェクトの下位も変更する場合は、 CustomReflectable に準拠させる
- コンパイルできるAPIであれば、引数として渡すことができる
- poはexpressionコマンドのエイリアスであり、 expression –object-description を含んでいる
- そのため、独自の「po」を実装するにはaliasコマンドを使用する
struct Trip {
var name: String
var destinations: [String]
}
// 最上位
extension Trip: CustomDebugStringConvertible {
var debugDescription: String { "Trip description" }
}
// 下位
extension Trip: CustomReflectable {
...
}
(lldb) po cruise
▿ Trip description // カスタマイズした形で表示される
- name : "Mediterranean Cruise"
▿ destinations : 3 elements
- 0 : "Sorrento"
- 1 : "Capri"
- 2 : "Taormina"
// 独自のpo(my_po)のエイリアス登録
(lldb) command alias my_po expression --object-description
(lldb) my_po cruise
po(print object)の仕組み
poが値を出力するフロー
- LLDBは式の解析や評価をしない
- 与えられた式をコンパイルしてソースコードを生成する
- デバッグするプログラムのコンテキストで実行する
- 実行が完了するとLLDBが結果にアクセスする
- オブジェクトの説明を表示するために、直近の結果を別のソースにラップする
- その後ラップしたソースもコンパイルし、同じコンテキストで実行する
- 実行結果は文字列で表現

その他のLLDBでの出力コマンド
p(print)コマンド
- poと表示される内容は同等
- pコマンド実行後、LLDB特有の変数が連番で生成され、後から参照可能
- poと同様 expressionコマンドのエイリアスですが、 object-descriptionオプションを含まない
pが値を出力するフロー
- コンパイルから式の評価まではpoと同じ
- 結果を取得すると、LLDBが方の動的解決を行う
- pで出力する際に対象オブジェクトの最も正確な型をLLDBが表示するため
- 例えば、protocol Aに準拠したstruct Bをinstance化したhogeを
p hoge
すると型としては動的な型Bが出力される - ただし、LLDBがpコマンド実行時のコンパイルで参照するのは、ソースコード中の静的な型であるため、動的な型(ここではstruct B)のプロパティにアクセスは出来ない
- エラーを出さずに実行するには、動的な型にキャストして実行する必要がある
- 動的解決後、LLDBがフォーマッタに結果を渡す
- ヒューマンリーダブルな形で表示するために必要
- フォーマッタのカスタマイズも可能

vコマンド
- 出力結果はpコマンドと同じ
- フォーマッタも適用される
- エイリアスは frame variableコマンドであり、Xcode 10.2から導入された
- po, pと異なり、コンパイルされない
- 対象オブジェクトに対して、オーバーロードせず、プロパティの評価もしない
vが値を出力するフロー
- プログラムの状態を調べて変数をメモリに配置する
- メモリから変数を読み込む
- 動的解決を行う
- サブフィールド(struct内のpropertyなど)を出力する場合は、個数分の処理を繰り返し、その都度 動的解決を行う
- 完了後、結果をフォーマッタに渡す

各ステップで動的解決を行うため、vコマンドではデバッグ対象オブジェクトの動的な型を判断可能。この点がvがpより優れている点(キャストせず構造体を参照可能)
po, p, v コマンドの比較表

データフォーマッタ(Data Formatter)のカスタマイズ
Filter
- 出力する表示を制限する
struct Trip {
var name: String
var destinations: [String]
}
let cruise = Trip(
name: "Mediterranean Cruise",
destinations: ["Sorrento", "Capri", "Taormina"])
// フィルターの設定
(lldb) type filter add Travel.Trip --child name
(lldb) v cruise
(Travel.Trip) cruise = (name = "Mediterranean Cruise")
// フィルターを削除
(lldb) type filter delete Travel.Trip
String summaries
- ひと目で型がわかるように型の表示を変更する
- 下記のように、サマリ定義する変数を
var
で参照してカスタマイズする
struct Trip {
var name: String
var destinations: [String]
}
let cruise = Trip(
name: "Mediterranean Cruise",
destinations: ["Sorrento", "Capri", "Taormina"])
(lldb) type summary add Travel.Trip --summary-string
"${var.name} from ${var.destinations[0]} to ${var.destinations[2]}"
(lldb) v cruise
(Travel.Trip) cruise = "Mediterranean Cruise" from "Sorrento" to "Taormina"
Python Formatter
- 任意の計算が可能で、LLDBのAPIにフルアクセスできる
- Xcode 11から Python 3が使用される
- scriptコマンドでPythonインタプリタを起動
- 現在のフレームはlldb.frameでアクセス可能で、SBFrameが返る
- ファイル読み込みも可能

struct Trip {
var name: String
var destinations: [String]
}
let cruise = Trip(
name: "Mediterranean Cruise",
destinations: ["Sorrento", "Capri", "Taormina"])
(lldb) script
>>> cruise = lldb.frame.FindVariable("cruise")
>>> print(cruise)
(Travel.Trip) cruise = {
name = "Mediterranean Cruise"
destinations = 3 values {
…
}
>>> destinations = cruise.GetChildMemberWithName("destinations")
>>> print(destinations)
([String]) destinations = 3 values {
[0] = "Sorrento"
[1] = "Capri"
[2] = "Taormina"
}
>>> count = destinations.GetNumChildren()
>>> begin = destinations.GetChildAtIndex(0)
>>> print(begin)
(String) [0] = "Sorrento"
>>> end = destinations.GetChildAtIndex(count - 1)
>>> print(end)
(String) [2] = "Taormina"
>>> print("Trip from {} to {}".format(begin, end))
Trip from (String) name = "Sorrento" to (String) name = "Taormina"
>>> print("Trip from {} to {}".format(begin.GetSummary(), end.GetSummary()))
Trip from "Sorrento" to "Taormina"
pythonファイルの読み込み
// Trip.py
def SummaryProvider(value, _):
destinations = value.GetChildMemberWithName("destinations")
count = destinations.GetNumChildren()
if count == 0:
return "Empty trip"
begin = destinations.GetChildAtIndex(0).GetSummary()
end = destinations.GetChildAtIndex(count - 1).GetSummary()
return "Trip with {} stops from {} to {}".format(count, begin, end)
// 用意したpythonファイルのimport
(lldb) command script import Trip.py
// summary addでフォーマットを適用する型とサマリを指定
(lldb) type summary add Travel.Trip --python-function Trip.SummaryProvider
(lldb) v cruise
(Travel.Trip) cruise = Trip with 3 stops from "Sorrento" to "Capri"
Synthetic children
- 変数ビューの表示が複雑な型などでもカスタマイズ可能
- pythonで、関数ではなくメソッドを実装したクラスを用意する
// Trip.py
class ExampleSyntheticChildrenProvider:
def __init__(self, value, _):
…
def num_children(self):
…
def get_child_at_index(self, index):
…
def get_child_index(self, name):
…
(lldb) command script import Trip.py
// フォーマッタを設定するため、 type synthetic addで型とクラスを指定
(lldb) type synthetic add Travel.Trip --python-class Trip.ExampleSyntheticChildrenProvider
カスタマイズしたコマンドの永続化
- コンソールで実行したコマンドはホームディレクトリの .lldbinitファイルに保存される
- デバッグセッションの開始時に自動でロードされる