mcpField
A
axiom-lldb

axiom-lldb

github.com/charleswiltgen開発1.2k108 回インストール今日更新
リポジトリ

axiom-lldb について

axiom-lldb は、Swift アプリ開発者向けの LLDB デバッグスキルです。クラッシュやハングの診断、Swift 値の確認、ブレークポイント、実行時の式評価を手順別に扱います。

主な機能

  1. 01停止理由とバックトレースからクラッシュを調査
  2. 02v、p、poでSwift値を確認
  3. 03スレッド状態からハングを診断
  4. 04条件付きブレークポイントとログポイントを設定
  5. 05式を評価して実行時の状態を変更

活用シーン

  1. 01再現可能なアプリクラッシュの原因を特定
  2. 02実行時に誤ったSwift値を確認
  3. 03ハングやデッドロック中のスレッド状態を追跡
  4. 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. 初期診断(1~3 個の箇条書き。確信度を HIGH/MEDIUM/LOW で表示)
  2. 実行するコマンド(番号付き。コピーしてすぐ実行できる形式で、(lldb) を付ける)
  3. 確認する内容(コマンド → 期待される出力 → 解釈)
  4. 考えられる根本原因(可能性の高い順)
  5. 次回のブレークポイント計画(次回はより早い段階で捕捉する)
  6. デバッガーがアタッチされていない場合(クラッシュログのみを使う代替手順)

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_BREAKPOINTSwift ランタイムのトラップ(境界チェック、アンラップ、事前条件)違反した事前条件を修正する
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 つの出力コマンド
コマンド正式形動作最適な用途
vframe variableコンパイルせず、メモリを直接読み取るSwift の構造体、列挙型、ローカル変数 — デフォルトで使います
pexpression(フォーマッター付き)式をコンパイルし、整形した結果を表示する計算プロパティ、関数呼び出し
poexpression --object-descriptiondebugDescription を呼び出すCustomDebugStringConvertible に準拠したクラス
exprexpression任意のコードを評価するメソッドの呼び出し、状態の変更
それぞれの使いどころ

まずは 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 nameSwift の式パーサーが型を解決できません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 は役に立たない」

状況: クラッシュはリリースビルドで発生しますが、デバッグビルドでは発生しません。チームは「デバッグできない」と言っています。

なぜうまくいかないのか: リリース最適化によってタイミングやメモリレイアウトが変わり、変数が削除されることもあります。そのため、デバッグビルドではクラッシュを再現できません。

適切な方法:

  1. デバッグ構成を使いながら、リリースに近い設定でビルドします。
    • 最適化レベル:-O(-Onone ではありません)
    • デバッグシンボルは引き続き含めます(DEBUG_INFORMATION_FORMAT = dwarf-with-dsym)
  2. アドレスサニタイザー(-fsanitize=address)を有効にします。2~3 倍のオーバーヘッドでメモリエラーを検出します。
  3. クラッシュレポートを使って、クラッシュ箇所にブレークポイントを設定します。
  4. 例外ブレークポイントを設定し、クラッシュ前にエラーを捕捉します。
    (lldb) breakpoint set -E swift
    (lldb) breakpoint set -E objc
    
  5. 変数に <optimized out> と表示される場合は、そのファイルだけ最適化レベルを下げます。
    • ビルド設定 → ファイルごとのフラグ → 対象ファイルに -Onone を指定
  6. 最終手段として、レジスターの値を直接読み取ります(変数は最適化で削除される前にレジスターに格納されています)。
    (lldb) register read
    (lldb) register read x0 x1 x2
    
    ARM64 では、x0 = self、x1~x7 = 最初の 7 個の引数です。詳しくは /skill axiom-lldb-ref のパート 1 を確認してください。
シナリオ 2:「print 文を追加するだけ」

状況: 開発者がデバッグのために print() 呼び出しを追加し、再ビルドして実行し、コンソールを確認します。これを繰り返します。

なぜうまくいかないのか: print によるデバッグを 1 回行うたびに、3~5 分かかります(編集 → ビルド → 実行 → 状態まで移動 → 出力を確認)。LLDB のブレークポイントなら 30 秒です。

適切な方法:

  1. print() を追加する行にブレークポイントを設定します。
    (lldb) b MyFile.swift:42
    
  2. 再ビルドせずに「print のような」動作をさせるため、ログポイントを追加します。
    • ブレークポイントを編集 → アクションを追加 → ログメッセージ → 「自動的に続行」にチェック
  3. 変数を直接調べます:v self.someValue
  4. 実行時に変数を変更して仮説を検証します:expr self.debugMode = true
  5. ブレークポイントを使った 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 の構造体では、型メタデータの不足、ジェネリクス、モジュール解決の問題などにより、このコンパイルが失敗することがよくあります。

正しい方法:

  1. po の代わりに v を使います。メモリを直接読み取るため、コンパイルは不要です。
    (lldb) v myStruct
    (lldb) v myStruct.propertyName
    
  2. 計算プロパティには p を使います。
    (lldb) p myStruct.computedValue
    
  3. po は CustomDebugStringConvertible に準拠したクラスにのみ使います。
  4. p も失敗する場合は、言語を指定してみてください。
    (lldb) expr -l objc -- (id)0x12345
    
  5. すべて失敗する場合でも、メソッド内では常に 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

コメント