axiom-lldb について
axiom-lldb は、Swift アプリ開発者向けの LLDB デバッグスキルです。クラッシュやハングの診断、Swift 値の確認、ブレークポイント、実行時の式評価を手順別に扱います。
主な機能
- 01停止理由とバックトレースからクラッシュを調査
- 02v、p、poでSwift値を確認
- 03スレッド状態からハングを診断
- 04条件付きブレークポイントとログポイントを設定
- 05式を評価して実行時の状態を変更
活用シーン
- 01再現可能なアプリクラッシュの原因を特定
- 02実行時に誤ったSwift値を確認
- 03ハングやデッドロック中のスレッド状態を追跡
- 04再ビルドせずにデバッグ仮説を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-lldbインストール後、Claude Code で/axiom-lldbを入力して読み込まれたか確認します。
詳細
LLDB によるデバッグ
LLDB を使った対話型デバッグです。デバッガーは時間を止めるため、実行中のアプリを詳しく調べられます。変数の確認、式の評価、スレッドの移動を行い、何が問題を引き起こしたのかを正確に把握できます。
重要な洞察:「LLDB は使い物にならない」というのは、実際には「Swift の型にどのコマンドを使えばよいかわからない」という意味です。これはツールの問題ではなく、知識のギャップによる問題です。
要注意の兆候 — こんなときはこのスキルを確認してください
| 症状 | このスキルが役立つケース |
|---|---|
| 実行時に変数を調べる必要がある | はい — ブレークポイント+確認 |
| ローカルで再現できるクラッシュがある | はい — クラッシュ箇所の前にブレークポイントを設定 |
| 実行時の値が誤っているが、コードは正しく見える | はい — ステップ実行して確認 |
| ハング中のスレッド状態を把握する必要がある | はい — 一時停止+スレッドのバックトレース |
po が動作しない/不正な値を表示する | はい — Playbook 3 に代替手段があります |
| クラッシュログを分析済みで、再現する必要がある | はい — クラッシュ時のコンテキストからブレークポイントを設定 |
| 再ビルドせずに修正をテストする必要がある | はい — 式の評価 |
| すべての例外で停止したい | はい — 例外ブレークポイント |
| アプリの動作が遅いが応答はある | いいえ — axiom-performance-profiling を使用 |
| メモリ使用量が時間とともに増加する | いいえ — まず axiom-memory-debugging を使用 |
| アプリが完全にフリーズしている | 場合によります — まず axiom-hang-diagnostics を使用し、その後 LLDB でスレッドを確認 |
| 本番環境でクラッシュし、ローカルで再現できない | いいえ — まず axiom-testflight-triage を使用 |
LLDB とその他のツール
digraph tool_selection {
"What do you need?" [shape=diamond];
"axiom-testflight-triage" [shape=box];
"axiom-hang-diagnostics" [shape=box];
"axiom-memory-debugging" [shape=box];
"axiom-performance-profiling" [shape=box];
"LLDB (this skill)" [shape=box, style=bold];
"What do you need?" -> "axiom-testflight-triage" [label="Crash log from field,\ncan't reproduce locally"];
"What do you need?" -> "axiom-hang-diagnostics" [label="App frozen,\nneed diagnosis approach"];
"What do you need?" -> "axiom-memory-debugging" [label="Memory growing,\nneed leak pattern"];
"What do you need?" -> "axiom-performance-profiling" [label="Need to measure\nCPU/memory over time"];
"What do you need?" -> "LLDB (this skill)" [label="Need to inspect state\nat a specific moment"];
}
**目安:**Instruments は測定し、LLDB は確認します。ある時点で何が起きているかを把握する必要がある場合は LLDB を使用してください。時間の経過に伴う傾向を把握する必要がある場合は Instruments を使用してください。
回答の形式
LLDB によるデバッグを支援するときは、次の形式で回答してください。
- 初期診断(1~3 個の箇条書き。確信度を HIGH/MEDIUM/LOW で表示)
- 実行するコマンド(番号付き。コピーしてすぐ実行できる形式で、
(lldb)を付ける) - 確認する内容(コマンド → 期待される出力 → 解釈)
- 考えられる根本原因(可能性の高い順)
- 次回のブレークポイント計画(次回はより早い段階で捕捉する)
- デバッガーがアタッチされていない場合(クラッシュログのみを使う代替手順)
Playbook 1:クラッシュのトリアージ
目標: 停止した地点から調査を始め、アプリがクラッシュした理由を把握します。
Step 1:停止理由を確認する
デバッガーが停止したとき、最初に確認する内容:
(lldb) thread info
停止理由が表示されます。一般的な停止理由は次のとおりです。
| 停止理由 | 意味 | 次の手順 |
|---|---|---|
EXC_BAD_ACCESS (SIGSEGV) | 無効なメモリにアクセスした(null ポインター、ダングリング参照) | アドレスを確認します — 0x0~0x10 は nil のデリファレンスです |
EXC_BAD_ACCESS (SIGBUS) | アラインメントが不正、または無効なアドレス | 多くの場合、C との相互運用または安全でないポインターの問題です |
EXC_BREAKPOINT (SIGTRAP) | トラップに到達した — Swift ランタイムのチェックに失敗 | fatalError()、preconditionFailure()、nil の強制アンラップ、配列の範囲外アクセスを確認します |
EXC_CRASH (SIGABRT) | 意図的な中断 — アサーションまたは未捕捉の例外 | メッセージを確認するため、「Application Specific Information」を確認します |
breakpoint | 設定したブレークポイントに到達した | 正常です — 状態を確認します |
Step 2:バックトレースを取得する
(lldb) bt
上から下へ読みます。自分のコード内にある最初のフレーム(システムフレームワークではないもの)を見つけます。そこから調査を始めます。
(lldb) bt 10
トレース全体に余分な情報が多い場合は、フレーム数を 10 に制限します。
Step 3:自分のフレームに移動する
(lldb) frame select 3
フレーム 3(または自分のコードに該当するフレーム)に移動します。
Step 4:状態を確認する
(lldb) v
(lldb) v self.someProperty
(lldb) v localVariable
Swift の値を確実に確認するには、po ではなく v を使います。詳細は Playbook 3 を参照してください。
ステップ 5:分類して修正する
| 例外の種類 | 典型的な原因 | 修正パターン |
|---|---|---|
EXC_BAD_ACCESS(低アドレス) | nil のオプショナルの強制アンラップ | guard let / if let |
EXC_BAD_ACCESS(高アドレス) | 解放後の使用/ダングリングポインター | オブジェクトのライフタイム、[weak self] を確認する |
EXC_BREAKPOINT | Swift ランタイムのトラップ(境界チェック、アンラップ、事前条件) | 違反した事前条件を修正する |
SIGABRT | キャッチされない ObjC 例外、または fatalError() | 例外メッセージを読み、根本原因を修正する |
ステップ 6:条件付きブレークポイントを設定して早期に検出する
(lldb) breakpoint set -f MyFile.swift -l 42 -c "value == nil"
これにより、42 行目で value が nil の場合にのみ停止し、クラッシュする前に問題を検出できます。
プレイブック 2:ハング/デッドロックの診断
目標: すべてのスレッドの状態を調べて、アプリがフリーズした理由を把握します。
ステップ 1:アプリを一時停止する
アプリがハングした場合は、Xcode の一時停止ボタン(⌃⌘Y)を押すか、次を実行します。
(lldb) process interrupt
ステップ 2:すべてのスレッドのバックトレースを取得する
(lldb) thread backtrace all
または、短縮形を使います。
(lldb) bt all
ステップ 3:スレッドの状態を分類する
Thread 0(メインスレッド)を確認します。メインスレッドはすべての UI イベントを処理します。ブロックされていると、アプリはフリーズします。
メインスレッドが同期的な待機でブロックされている場合:
frame #0: libsystem_kernel.dylib`__psynch_mutexwait
frame #1: libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait
...
frame #5: MyApp`ViewController.viewDidLoad()
説明:メインスレッドはミューテックスロックを待っています。別の何かがそのロックを保持しています。
メインスレッドが dispatch_sync でブロックされている場合:
frame #0: libdispatch.dylib`_dispatch_sync_f_slow
...
frame #3: MyApp`DataManager.fetchData()
説明:バックグラウンドから DispatchQueue.main.sync が呼び出されています。典型的なデッドロックです。
メインスレッドが処理中の場合(CPU 負荷の高い処理):
frame #0: MyApp`ImageProcessor.processAllImages()
frame #1: MyApp`ViewController.viewDidLoad()
説明:メインスレッドで負荷の高い処理が行われています。バックグラウンドに移動してください。
ステップ 4:デッドロックを確認する
2 つのスレッドが、互いが保持しているものを待っている場合:
(lldb) thread list
互いのロックを参照する waiting 状態のスレッドが複数あるか確認します。
ステップ 5:特定のスレッドを調べる
(lldb) thread select 3
(lldb) bt
(lldb) v
別のスレッドに切り替えて、その状態を調べます。
相互参照: ハングの原因を特定した後の修正パターン → /skill axiom-hang-diagnostics
プレイブック 3:Swift の値の検査
このスキルの核となる価値です。 多くの開発者が、Swift の型に対して po が安定して動作しないため LLDB の使用をやめてしまいます。実際に使える方法を紹介します。
4 つの出力コマンド
| コマンド | 正式形 | 動作 | 最適な用途 |
|---|---|---|---|
v | frame variable | コンパイルせず、メモリを直接読み取る | Swift の構造体、列挙型、ローカル変数 — デフォルトで使います |
p | expression(フォーマッター付き) | 式をコンパイルし、整形した結果を表示する | 計算プロパティ、関数呼び出し |
po | expression --object-description | debugDescription を呼び出す | CustomDebugStringConvertible に準拠したクラス |
expr | expression | 任意のコードを評価する | メソッドの呼び出し、状態の変更 |
それぞれの使いどころ
まずは v を使います — 保存プロパティの確認では最も速く、最も安定しています。
(lldb) v self.userName
(lldb) v self.items[0]
(lldb) v localStruct
v はメモリを直接読み取ります。何もコンパイルしないため、式のコンパイルエラーで失敗することはありません。
v の制限: 保存プロパティのみを読み取ります。計算プロパティ、lazy var(初回アクセス前)、プロパティラッパーの投影値($binding)は、意味のある値を表示しません。v でフィールドの値がおかしい、または表示されない場合は、代わりに p を試してください。
v で取得できない場合は p を使います:
(lldb) p self.computedProperty
(lldb) p self.items.count
(lldb) p someFunction()
p は式をコンパイルして実行します。計算プロパティや関数呼び出しで必要です。
クラスの説明には po を使います:
(lldb) po myObject
(lldb) po error
(lldb) po notification
po は結果に対して debugDescription を呼び出します。意味のある説明を持つオブジェクト(NSError、Notification など)に最適です。
「LLDB が動かない」とき
| 表示される内容 | 理由 | 対処法 |
|---|---|---|
<uninitialized> | po が失敗しました。オプティマイザーによって変数がまだ設定されていません | 代わりに v を使います |
expression failed to parse, unknown type name | Swift の式パーサーが型を解決できません | ObjC オブジェクトの場合は expr -l objc -- (id)0x12345 を試すか、v を使います |
<variable not available> | コンパイラーによって最適化で削除されています(Release ビルド) | Debug で再ビルドするか、ファイル単位で -Onone を指定するか、最後の手段として register read を使います |
error: Couldn't apply expression side effects | 式に LLDB が元に戻せない副作用がありました | より単純な式を試し、状態の変更を避けます |
po が値ではなくメモリアドレスを表示する | オブジェクトが CustomDebugStringConvertible に準拠していません | 生の値には v を使うか、このプロトコルを実装します |
cannot find 'self' in scope | ブレークポイントが self のないコンテキスト(static、クロージャー)にあります | 明示的な変数名を指定して v を使います |
p は $R0 = ... を表示するが、po がクラッシュする | コンパイル経路が異なります | 動作する場合は p を使います。po は追加の説明処理を行うため、失敗することがあります |
Optional の調査
(lldb) v optionalValue
表示例:(String?) some = "hello" または (String?) none
po optionalValue は使わないでください。Optional("hello") だけが表示される場合があり、あまり役に立ちません。
コレクションの調査
(lldb) v myArray
(lldb) v myArray[2]
(lldb) v myDict
大きなコレクションでは、出力を制限します。
(lldb) p Array(myArray.prefix(5))
SwiftUI の状態の調査
SwiftUI の @State は、アンダースコア接頭辞が付いた格納プロパティによって保持されます。
(lldb) v self._isPresented
(lldb) v self._items
@Observable モデルの場合:
(lldb) v self.viewModel.propertyName
「ビューが更新されない」問題の診断: v でプロパティの変更を確認できるのに SwiftUI ビューが再描画されない場合は、bt を使って変更が発生したスレッドを確認します。SwiftUI が変更を検知するには、@Observable の変更は @MainActor 上で発生する必要があります。バックグラウンドアクター上での変更はビューの更新を引き起こしません。ビューのボディ内で Self._printChanges() を使うと、どのプロパティが再描画を引き起こしたか(または引き起こさなかったか)を確認できます。
(lldb) expr Self._printChanges()
観測に関する診断ツリー全体はこちら → /skill axiom-swiftui-debugging
アクターの調査
アクターの状態を調べるには、分離の問題を気にせずメモリを直接読み取る v が最適です。
(lldb) v actor
すべての格納プロパティが表示されます。LLDB はプロセス全体を一時停止するため、コンパイル時の概念であるアクター分離に関係なく、任意のメモリを読み取れます。
実行時の値の変更
(lldb) expr self.debugFlag = true
(lldb) expr myArray.append("test")
(lldb) expr self.view.backgroundColor = UIColor.red
再ビルドせずに値を変更できます。仮説の検証に役立ちます。
以前の結果の参照
LLDB は結果変数($R0、$R1 など)を割り当てます。
(lldb) p someValue
$R0 = 42
(lldb) p $R0 + 10
$R1 = 52
プレイブック 4:ブレークポイント戦略
ソースブレークポイント(基本)
(lldb) breakpoint set -f ViewController.swift -l 42
(lldb) b ViewController.swift:42
単純なケースでは、短縮形の b が使えます。
条件付きブレークポイント
条件が真の場合にのみ停止します。
(lldb) breakpoint set -f MyFile.swift -l 42 -c "index > 100"
(lldb) breakpoint set -f MyFile.swift -l 42 -c "name == \"test\""
反復回数に基づく方法: N 回ヒットした後に停止します。
(lldb) breakpoint set -f MyFile.swift -l 42 -i 50
最初の 50 回のヒットを無視し、その後に停止します。
ログポイント(アクションと自動続行)
停止せずにログを出力します。print 文のように使えますが、再ビルドは不要です。
(lldb) breakpoint set -f MyFile.swift -l 42
(lldb) breakpoint command add 1
> v self.value
> continue
> DONE
または Xcode で、ブレークポイントを編集 → アクションを追加 →「ログメッセージ」→ @self.value@ トークン構文を使用 →「自動的に続行」をチェックします。
シンボリックブレークポイント
メソッド名を指定して、任意の呼び出しで停止します。
(lldb) breakpoint set -n viewDidLoad
(lldb) breakpoint set -n "MyClass.myMethod"
セレクターに対するすべての ObjC メッセージで停止します。
(lldb) breakpoint set -S "layoutSubviews"
例外ブレークポイント
Swift エラー(throw で停止):
(lldb) breakpoint set -E swift
Objective-C 例外(throw で停止):
(lldb) breakpoint set -E objc
Xcode の場合: ブレークポイントナビゲーター → + → Swift エラーブレークポイント/例外ブレークポイント
クラッシュのデバッグで最も役立つブレークポイントです。catch/クラッシュした箇所ではなく、throw した箇所で停止します。
ウォッチポイント
変数の値が変わったときに停止します。
(lldb) watchpoint set variable self.count
(lldb) watchpoint set variable -w read_write myGlobal
ウォッチポイントはハードウェアでサポートされています。プロセスあたり約 4 個に制限されますが、非常に高速です。
ワンショットブレークポイント
一度だけ停止し、その後自動的に削除します。
(lldb) breakpoint set -f MyFile.swift -l 42 -o
ブレークポイントの管理
(lldb) breakpoint list
(lldb) breakpoint disable 3
(lldb) breakpoint enable 3
(lldb) breakpoint delete 3
(lldb) breakpoint delete
プレイブック 5:非同期処理/並行処理のデバッグ
非同期フレームの特定
Swift の並行処理のバックトレースにはノイズが多く、swift_task_switch、_dispatch_call_block_and_release、エグゼキューターの内部処理がコードに混在していることがあります。ランタイムのノイズが 40 フレーム以上あっても、気にしないでください。自分のモジュールのフレームに注目します。
Swift の並行処理のバックトレースでは、swift-task のフレームを探します。
Thread 3:
frame #0: MyApp`MyActor.doWork()
frame #1: swift_task_switch
frame #2: MyApp`closure #1 in ViewController.loadData()
swift_task_switch フレームは、非同期のサスペンションポイントを示します。自分のコードのフレームは、モジュール名(上記の例では MyApp)が先頭に付いているものです。
タスクの状態を調べる
(lldb) thread backtrace all
フレームに swift_task が含まれるスレッドを探します。それぞれが実行中の Swift タスクを表します。
アクター分離されたコード
アクター内で停止した場合:
(lldb) v self
アクターの状態をすべて表示します。LLDB はプロセス全体を一時停止するため、これが可能です(デフォルトのアクターでは、アクター分離はコンパイル時の概念であり、ランタイムのロックではありません)。
タスクグループの調査
タスクグループをデバッグするときは、グループのクロージャー内でブレークし、次を調べます。
(lldb) v
(lldb) bt
各子タスクは独自のスレッドで実行されます。bt all を使うと、それらを確認できます。
関連項目: Swift の並行処理パターンと修正方法について → /skill axiom-swift-concurrency。非同期パフォーマンスのプロファイリングについて → /skill axiom-concurrency-profiling
プレッシャーのかかる状況
シナリオ 1:「リリース版のみでクラッシュする — リリース版では LLDB は役に立たない」
状況: クラッシュはリリースビルドで発生しますが、デバッグビルドでは発生しません。チームは「デバッグできない」と言っています。
なぜうまくいかないのか: リリース最適化によってタイミングやメモリレイアウトが変わり、変数が削除されることもあります。そのため、デバッグビルドではクラッシュを再現できません。
適切な方法:
- デバッグ構成を使いながら、リリースに近い設定でビルドします。
- 最適化レベル:
-O(-Ononeではありません) - デバッグシンボルは引き続き含めます(
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym)
- 最適化レベル:
- アドレスサニタイザー(
-fsanitize=address)を有効にします。2~3 倍のオーバーヘッドでメモリエラーを検出します。 - クラッシュレポートを使って、クラッシュ箇所にブレークポイントを設定します。
- 例外ブレークポイントを設定し、クラッシュ前にエラーを捕捉します。
(lldb) breakpoint set -E swift (lldb) breakpoint set -E objc - 変数に
<optimized out>と表示される場合は、そのファイルだけ最適化レベルを下げます。- ビルド設定 → ファイルごとのフラグ → 対象ファイルに
-Ononeを指定
- ビルド設定 → ファイルごとのフラグ → 対象ファイルに
- 最終手段として、レジスターの値を直接読み取ります(変数は最適化で削除される前にレジスターに格納されています)。
ARM64 では、(lldb) register read (lldb) register read x0 x1 x2x0= self、x1~x7= 最初の 7 個の引数です。詳しくは/skill axiom-lldb-refのパート 1 を確認してください。
シナリオ 2:「print 文を追加するだけ」
状況: 開発者がデバッグのために print() 呼び出しを追加し、再ビルドして実行し、コンソールを確認します。これを繰り返します。
なぜうまくいかないのか: print によるデバッグを 1 回行うたびに、3~5 分かかります(編集 → ビルド → 実行 → 状態まで移動 → 出力を確認)。LLDB のブレークポイントなら 30 秒です。
適切な方法:
print()を追加する行にブレークポイントを設定します。(lldb) b MyFile.swift:42- 再ビルドせずに「print のような」動作をさせるため、ログポイントを追加します。
- ブレークポイントを編集 → アクションを追加 → ログメッセージ → 「自動的に続行」にチェック
- 変数を直接調べます:
v self.someValue - 実行時に変数を変更して仮説を検証します:
expr self.debugMode = true - ブレークポイントを使った 1 回のセッションで、print によるデバッグを 5~10 回繰り返す代わりになります。
所要時間の比較(一般的な制御フローのデバッグ):
| 方法 | 調査 1 回あたり | 変数 5 個 |
|---|---|---|
| print() 文 | 3~5 分(ビルド+実行) | 15~25 分 |
| LLDB ブレークポイント | 30 秒(設定+確認) | 2.5 分 |
例外: ループ内のヒット頻度が非常に高い場合(1 秒あたり数千回)、ログポイントはヒットごとにオーバーヘッドを加えます。-i を使って目的の反復までスキップするか、そのループに限って一時的な print() を使います。
シナリオ 3:「po が動かないので、LLDB は壊れている」
状況: 開発者が po myStruct と入力すると、意味不明な結果が表示されます。Swift では LLDB が壊れていると結論づけ、プリントデバッグに戻ります。
これが、開発者が LLDB を使わなくなる最大の理由です。
Swift の構造体で po が失敗する理由: po は debugDescription を呼び出しますが、そのためにはデバッガーのコンテキストで式をコンパイルする必要があります。Swift の構造体では、型メタデータの不足、ジェネリクス、モジュール解決の問題などにより、このコンパイルが失敗することがよくあります。
正しい方法:
poの代わりにvを使います。メモリを直接読み取るため、コンパイルは不要です。(lldb) v myStruct (lldb) v myStruct.propertyName- 計算プロパティには
pを使います。(lldb) p myStruct.computedValue poはCustomDebugStringConvertibleに準拠したクラスにのみ使います。pも失敗する場合は、言語を指定してみてください。(lldb) expr -l objc -- (id)0x12345- すべて失敗する場合でも、メソッド内では常に
v selfが使えます。
アンチパターン
| アンチパターン | 問題点 | より良い方法 |
|---|---|---|
すべてに po を使う | Swift の構造体、列挙型、オプショナルでは失敗します | 値には v を使い、po はクラスにのみ使います |
| プリントデバッグを繰り返す | 1 サイクルに 3〜5 分かかるのに対し、ブレークポイントなら 30 秒です | ログポイントアクション付きのブレークポイントを使います |
| 「LLDB は Swift では動かない」と考える | 実際には動きます。コマンドの選択が誤っています | v は Swift の値向けに設計されています |
| バックトレースを無視する | トレースを読まずに推測で進めてしまいます | まず bt を実行し、その後フレームを移動します |
| 停止するたびに条件付きブレークポイントを使う | 条件の評価にコストがかかると実行が遅くなります | 可能な場合は -i(無視する回数)を使います |
| 最適化された(Release)ビルドをデバッグする | 変数がなくなったり、コードの順序が変わったりします | Debug 構成を使うか、ファイル単位で -Onone を指定します |
| 例外を強制的に続行する | 本当のエラーが隠れてしまいます | 例外を抑制せず、原因を修正します |
| 例外ブレークポイントを設定しない | スローされた場所ではなく、システムコードでクラッシュします | Swift Error と ObjC Exception のブレークポイントを必ず追加します |
デバッグチェックリスト
デバッグセッションを開始する前:
- Debug ビルド構成になっている(Release ではない)
- 例外ブレークポイントが有効になっている(Swift Error と ObjC Exception)
- 問題が疑われる箇所より前にブレークポイントを設定している
- 使用するコマンドを把握している:値には
v、計算プロパティにはp、説明にはpo
デバッグセッション中:
- 何よりも先に停止理由(
thread info)を確認する - バックトレース(
bt)を取得し、自分のフレームを見つける - 自分のフレームに移動する(
frame select N) - 関連する状態を調べる(
v self、v localVar) - 修正を書く前に原因を理解する
問題を見つけた後:
- 再発を捕捉するための条件付きブレークポイントを設定する
- このケースに対する assertion/precondition の追加を検討する
- 一時的なブレークポイントを削除する
リソース
WWDC:2019-429、2018-412、2022-110370
ドキュメント:/xcode/stepping-through-code-and-inspecting-variables-to-isolate-bugs、/xcode/setting-breakpoints-to-pause-your-running-app、/xcode/diagnosing-memory-thread-and-crash-issues-early
スキル:axiom-lldb-ref、axiom-testflight-triage、axiom-hang-diagnostics、axiom-memory-debugging、axiom-swift-concurrency、axiom-concurrency-profiling