axiom-lldb

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

LLDB Debugging

LLDB 调试

Interactive debugging with LLDB. The debugger freezes time so you can interrogate your running app — inspect variables, evaluate expressions, navigate threads, and understand exactly why something went wrong.
Core insight: "LLDB is useless" really means "I don't know which command to use for Swift types." This is a knowledge-gap problem, not a tool problem.
使用LLDB进行交互式调试。调试器会冻结程序运行状态,让你可以探查正在运行的应用——检查变量、计算表达式、查看线程,精准定位问题原因。
核心认知: 所谓“LLDB没用”其实是“我不知道针对Swift类型该用哪个命令”。这是知识缺口问题,而非工具本身的问题。

Red Flags — Check This Skill When

适用场景警示——遇到以下情况请使用本技能

SymptomThis Skill Applies
Need to inspect a variable at runtimeYes — breakpoint + inspect
Crash you can reproduce locallyYes — breakpoint before crash site
Wrong value at runtime but code looks correctYes — step through and inspect
Need to understand thread state during hangYes — pause + thread backtrace
po
doesn't work / shows garbage
Yes — Playbook 3 has alternatives
Crash log analyzed, need to reproduceYes — set breakpoints from crash context
Need to test a fix without rebuildingYes — expression evaluation
Want to break on all exceptionsYes — exception breakpoints
App feels slow but responsiveNo — use axiom-performance-profiling
Memory grows over timeNo — use axiom-memory-debugging first
App completely frozenMaybe — use axiom-hang-diagnostics first, then LLDB for thread inspection
Crash in production, no local reproNo — use axiom-testflight-triage first
症状是否适用本技能
需要在运行时检查变量是——设置断点+检查
可在本地复现的崩溃是——在崩溃位置前设置断点
代码逻辑正确但运行时值异常是——单步执行并检查
需要排查程序挂起时的线程状态是——暂停程序+查看线程回溯
po
命令无效/输出乱码
是——手册3提供了替代方案
已分析崩溃日志,需要复现问题是——根据崩溃上下文设置断点
需要在不重新构建的情况下测试修复方案是——使用表达式计算
希望捕获所有异常是——设置异常断点
应用响应缓慢但未完全冻结否——使用axiom-performance-profiling
内存占用持续增长否——优先使用axiom-memory-debugging
应用完全冻结可能——优先使用axiom-hang-diagnostics,再用LLDB检查线程
生产环境崩溃,无法在本地复现否——优先使用axiom-testflight-triage

LLDB vs Other Tools

LLDB与其他工具对比

dot
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"];
}
Rule of thumb: Instruments measures. LLDB inspects. If you need to understand what's happening at a specific moment in time, use LLDB. If you need to understand trends over time, use Instruments.
dot
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。

Response Format

响应格式

When helping with LLDB debugging, structure your output as:
  1. Immediate diagnosis (1-3 bullets, confidence-tagged: HIGH/MEDIUM/LOW)
  2. Commands to run (numbered, copy-paste ready, with
    (lldb)
    prefix)
  3. What to look for (command → expected output → interpretation)
  4. Likely root causes (ranked by probability)
  5. Next breakpoint plan (catch it earlier next time)
  6. If no debugger attached (crash-log-only fallback path)

在协助进行LLDB调试时,请按以下结构输出内容:
  1. 即时诊断(1-3条要点,标注置信度:HIGH/MEDIUM/LOW)
  2. 待执行命令(编号格式,可直接复制粘贴,前缀为
    (lldb)
  3. 检查要点(命令→预期输出→解读)
  4. 可能的根因(按概率排序)
  5. 后续断点计划(提前捕获问题)
  6. 未附加调试器时的方案(仅依赖崩溃日志的 fallback 路径)

Playbook 1: Crash Triage

手册1:崩溃排查

Goal: Understand why the app crashed, starting from the stop point.
目标: 从程序停止的位置入手,理解应用崩溃的原因。

Step 1: Read the Stop Reason

步骤1:查看停止原因

When the debugger stops, the first thing to check:
(lldb) thread info
This shows the stop reason. Common stop reasons:
Stop ReasonMeaningNext Step
EXC_BAD_ACCESS (SIGSEGV)
Accessed invalid memory (null pointer, dangling reference)Check the address —
0x0
to
0x10
= nil dereference
EXC_BAD_ACCESS (SIGBUS)
Misaligned or invalid addressUsually C interop or unsafe pointer issue
EXC_BREAKPOINT (SIGTRAP)
Hit a trap — Swift runtime check failedCheck for
fatalError()
,
preconditionFailure()
, force-unwrap of nil, array out of bounds
EXC_CRASH (SIGABRT)
Deliberate abort — assertion or uncaught exceptionLook at "Application Specific Information" for the message
breakpoint
Your breakpoint was hitNormal — inspect state
当调试器暂停程序时,首先执行以下命令:
(lldb) thread info
该命令会显示程序停止的原因。常见停止原因如下:
停止原因含义下一步操作
EXC_BAD_ACCESS (SIGSEGV)
访问了无效内存(空指针、野指针)检查地址——
0x0
0x10
表示空指针解引用
EXC_BAD_ACCESS (SIGBUS)
内存地址对齐错误或无效通常是C语言交互或不安全指针导致的问题
EXC_BREAKPOINT (SIGTRAP)
触发了陷阱——Swift运行时检查失败检查是否存在
fatalError()
preconditionFailure()
、强制解包空值、数组越界等情况
EXC_CRASH (SIGABRT)
主动终止程序——断言失败或未捕获异常查看“Application Specific Information”中的错误信息
breakpoint
命中了你设置的断点正常情况——检查程序状态

Step 2: Get the Backtrace

步骤2:获取回溯信息

(lldb) bt
Read top-to-bottom. Find the first frame in YOUR code (not system frameworks). That's where to start investigating.
(lldb) bt 10
Limit to 10 frames if the full trace is noisy.
(lldb) bt
从上到下阅读回溯信息,找到属于你的代码的第一个栈帧(而非系统框架),这就是你需要开始排查的位置。
(lldb) bt 10
如果完整回溯信息过于冗长,可以限制只显示前10个栈帧。

Step 3: Navigate to Your Frame

步骤3:跳转到目标栈帧

(lldb) frame select 3
Jump to frame 3 (or whichever frame is in your code).
(lldb) frame select 3
跳转到第3个栈帧(或属于你的代码的任意栈帧)。

Step 4: Inspect State

步骤4:检查程序状态

(lldb) v
(lldb) v self.someProperty
(lldb) v localVariable
Use
v
(not
po
) for reliable Swift value inspection. See Playbook 3 for details.
(lldb) v
(lldb) v self.someProperty
(lldb) v localVariable
使用
v
(而非
po
)来可靠地检查Swift类型的值。详情请参考手册3。

Step 5: Classify and Fix

步骤5:分类问题并修复

Exception TypeTypical CauseFix Pattern
EXC_BAD_ACCESS
at low address
Force-unwrap nil optional
guard let
/
if let
EXC_BAD_ACCESS
at high address
Use-after-free / dangling pointerCheck object lifetime,
[weak self]
EXC_BREAKPOINT
Swift runtime trap (bounds, unwrap, precondition)Fix the violated precondition
SIGABRT
Uncaught ObjC exception or
fatalError()
Read the exception message, fix the root cause
异常类型典型原因修复方案
EXC_BAD_ACCESS
(低地址)
强制解包空可选值使用
guard let
/
if let
EXC_BAD_ACCESS
(高地址)
内存释放后使用/野指针检查对象生命周期,使用
[weak self]
EXC_BREAKPOINT
Swift运行时陷阱(越界、解包、前置条件失败)修复违反的前置条件
SIGABRT
未捕获的OC异常或
fatalError()
读取异常信息,修复根因

Step 6: Set a Conditional Breakpoint to Catch It Earlier

步骤6:设置条件断点提前捕获问题

(lldb) breakpoint set -f MyFile.swift -l 42 -c "value == nil"
This breaks only when
value
is nil at line 42 — catches the problem before the crash.

(lldb) breakpoint set -f MyFile.swift -l 42 -c "value == nil"
该断点仅会在第42行的
value
为空时触发——在崩溃发生前就捕获问题。

Playbook 2: Hang/Deadlock Diagnosis

手册2:挂起/死锁排查

Goal: Understand why the app is frozen by inspecting all thread states.
目标: 通过检查所有线程状态,理解应用冻结的原因。

Step 1: Pause the App

步骤1:暂停应用

If the app is hung, press the pause button in Xcode (⌃⌘Y) or:
(lldb) process interrupt
如果应用挂起,点击Xcode中的暂停按钮(⌃⌘Y)或执行以下命令:
(lldb) process interrupt

Step 2: Get All Thread Backtraces

步骤2:获取所有线程的回溯信息

(lldb) thread backtrace all
Or the shorthand:
(lldb) bt all
(lldb) thread backtrace all
或使用简写:
(lldb) bt all

Step 3: Classify Thread States

步骤3:分类线程状态

Look at Thread 0 (main thread) — it processes all UI events. If it's blocked, the app is frozen.
Main thread blocked on synchronous wait:
frame #0: libsystem_kernel.dylib`__psynch_mutexwait
frame #1: libsystem_pthread.dylib`_pthread_mutex_firstfit_lock_wait
...
frame #5: MyApp`ViewController.viewDidLoad()
Translation: Main thread is waiting for a mutex lock. Something else holds it.
Main thread blocked on dispatch_sync:
frame #0: libdispatch.dylib`_dispatch_sync_f_slow
...
frame #3: MyApp`DataManager.fetchData()
Translation:
DispatchQueue.main.sync
called from background → classic deadlock.
Main thread busy (CPU-bound):
frame #0: MyApp`ImageProcessor.processAllImages()
frame #1: MyApp`ViewController.viewDidLoad()
Translation: Expensive work on main thread. Move to background.
重点查看线程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()
解读:主线程执行了耗时操作,需要将其移至后台线程。

Step 4: Check for Deadlocks

步骤4:检查死锁

If two threads are both waiting on something the other holds:
(lldb) thread list
Look for multiple threads with state
waiting
that reference each other's locks.
如果两个线程都在等待对方持有的资源:
(lldb) thread list
查找多个状态为
waiting
且引用了对方锁的线程。

Step 5: Inspect Specific Thread

步骤5:检查特定线程

(lldb) thread select 3
(lldb) bt
(lldb) v
Switch to another thread to inspect its state.
Cross-reference: For fix patterns once you've identified the hang cause →
/skill axiom-hang-diagnostics

(lldb) thread select 3
(lldb) bt
(lldb) v
切换到其他线程检查其状态。
交叉参考: 当你定位到挂起原因后,可参考修复方案 →
/skill axiom-hang-diagnostics

Playbook 3: Swift Value Inspection

手册3:Swift值探查

This is the core value of this skill. Most developers abandon LLDB because
po
doesn't work reliably with Swift types. Here's what actually works.
这是本技能的核心价值。 大多数开发者放弃LLDB是因为
po
命令无法可靠地处理Swift类型。以下是真正有效的方法。

The Four Print Commands

四个打印命令

CommandFull FormWhat It DoesBest For
v
frame variable
Reads memory directly, no compilationSwift structs, enums, locals — your default
p
expression
(with formatter)
Compiles expression, shows formatted resultComputed properties, function calls
po
expression --object-description
Calls
debugDescription
Classes with
CustomDebugStringConvertible
expr
expression
Evaluates arbitrary codeCalling methods, modifying state
命令全称功能最佳适用场景
v
frame variable
直接读取内存,无需编译Swift结构体、枚举、局部变量——默认首选
p
expression
(带格式化)
编译表达式,显示格式化结果计算属性、函数调用
po
expression --object-description
调用
debugDescription
实现了
CustomDebugStringConvertible
的类
expr
expression
执行任意代码调用方法、修改状态

When to Use Each

各命令的使用时机

Start with
v
— it's fastest and most reliable for stored properties:
(lldb) v self.userName
(lldb) v self.items[0]
(lldb) v localStruct
v
works by reading memory directly. It doesn't compile anything, so it can't fail due to expression compilation errors.
v
limitation:
It only reads stored properties — computed properties,
lazy var
(before first access), and property wrapper projected values (
$binding
) won't show meaningful values. If a field looks wrong or missing with
v
, try
p
instead.
Use
p
when
v
can't reach it:
(lldb) p self.computedProperty
(lldb) p self.items.count
(lldb) p someFunction()
p
compiles and executes the expression. Needed for computed properties and function calls.
Use
po
for class descriptions:
(lldb) po myObject
(lldb) po error
(lldb) po notification
po
calls
debugDescription
on the result. Best for objects that have meaningful descriptions (NSError, Notification, etc.).
优先使用
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等)。

The "LLDB Is Broken" Moments

"LLDB失效"场景处理

What You SeeWhyFix
<uninitialized>
po
failed; variable hasn't been populated by optimizer
Use
v
instead
expression failed to parse, unknown type name
Swift expression parser can't resolve the typeTry
expr -l objc -- (id)0x12345
for ObjC objects, or use
v
<variable not available>
Compiler optimized it out (Release build)Rebuild with Debug, per-file
-Onone
, or
register read
as last resort
error: Couldn't apply expression side effects
Expression had side effects LLDB couldn't reverseTry a simpler expression; avoid mutating state
po
shows memory address instead of value
Object doesn't conform to
CustomDebugStringConvertible
Use
v
for raw value, or implement the protocol
cannot find 'self' in scope
Breakpoint is in a context without
self
(static, closure)
Use
v
with the explicit variable name
p
shows
$R0 = ...
but
po
crashes
Different compilation pathsUse
p
when it works;
po
adds an extra description step that can fail
现象原因修复方案
<uninitialized>
po
执行失败;变量被优化器移除
改用
v
expression failed to parse, unknown type name
Swift表达式解析器无法识别类型对于OC对象,尝试
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
的上下文(静态方法、闭包)
使用
v
加显式变量名
p
显示
$R0 = ...
po
崩溃
编译路径不同
p
可用时使用
p
po
多了一个描述步骤,可能会失败

Inspecting Optionals

探查可选值

(lldb) v optionalValue
Shows:
(String?) some = "hello"
or
(String?) none
Don't use
po optionalValue
— it may show just
Optional("hello")
which is less useful.
(lldb) v optionalValue
显示结果:
(String?) some = "hello"
(String?) none
不要使用
po optionalValue
——它可能只显示
Optional("hello")
,信息价值较低。

Inspecting Collections

探查集合类型

(lldb) v myArray
(lldb) v myArray[2]
(lldb) v myDict
For large collections, limit output:
(lldb) p Array(myArray.prefix(5))
(lldb) v myArray
(lldb) v myArray[2]
(lldb) v myDict
对于大型集合,限制输出内容:
(lldb) p Array(myArray.prefix(5))

Inspecting SwiftUI State

探查SwiftUI状态

SwiftUI
@State
is backed by stored properties with underscore prefix:
(lldb) v self._isPresented
(lldb) v self._items
For
@Observable
models:
(lldb) v self.viewModel.propertyName
Diagnosing "view doesn't update": If a property changes (confirmed with
v
) but the SwiftUI view doesn't re-render, check which thread the mutation happens on with
bt
.
@Observable
mutations must happen on
@MainActor
for SwiftUI to observe them — mutations on a background actor won't trigger view updates. Use
Self._printChanges()
inside a view body to see which property triggered (or didn't trigger) a re-render:
(lldb) expr Self._printChanges()
For the full observation diagnostic tree →
/skill axiom-swiftui-debugging
SwiftUI的
@State
由带下划线前缀的存储属性支持:
(lldb) v self._isPresented
(lldb) v self._items
对于
@Observable
模型:
(lldb) v self.viewModel.propertyName
排查“视图未更新”问题: 如果确认属性已修改(通过
v
验证)但SwiftUI视图未重新渲染,使用
bt
检查修改操作所在的线程。
@Observable
的修改必须在
@MainActor
上执行,SwiftUI才能观测到——在后台actor上的修改不会触发视图更新。在视图体中使用
Self._printChanges()
查看哪个属性触发(或未触发)了重渲染:
(lldb) expr Self._printChanges()
如需完整的观测诊断树 →
/skill axiom-swiftui-debugging

Inspecting Actors

探查Actor

Actor state is best inspected with
v
, which reads memory directly without isolation concerns:
(lldb) v actor
Shows all stored properties. This works because LLDB pauses the entire process — you can read any memory regardless of actor isolation (which is a compile-time concept).
使用
v
探查Actor状态是最佳选择,它直接读取内存,无需考虑隔离限制:
(lldb) v actor
显示所有存储属性。这是因为LLDB会暂停整个进程——你可以读取任意内存,不受Actor隔离(这是编译时概念)的限制。

Modifying Values at Runtime

运行时修改值

(lldb) expr self.debugFlag = true
(lldb) expr myArray.append("test")
(lldb) expr self.view.backgroundColor = UIColor.red
Modify values without rebuilding. Useful for testing theories.
(lldb) expr self.debugFlag = true
(lldb) expr myArray.append("test")
(lldb) expr self.view.backgroundColor = UIColor.red
无需重新构建即可修改值,适用于验证假设。

Referencing Previous Results

引用之前的结果

LLDB assigns result variables (
$R0
,
$R1
, etc.):
(lldb) p someValue
$R0 = 42
(lldb) p $R0 + 10
$R1 = 52

LLDB会为结果分配变量(
$R0
$R1
等):
(lldb) p someValue
$R0 = 42
(lldb) p $R0 + 10
$R1 = 52

Playbook 4: Breakpoint Strategies

手册4:断点策略

Source Breakpoints (Basic)

源码断点(基础)

(lldb) breakpoint set -f ViewController.swift -l 42
(lldb) b ViewController.swift:42
Short form
b
works for simple cases.
(lldb) breakpoint set -f ViewController.swift -l 42
(lldb) b ViewController.swift:42
简写
b
适用于简单场景。

Conditional Breakpoints

条件断点

Break only when a condition is true:
(lldb) breakpoint set -f MyFile.swift -l 42 -c "index > 100"
(lldb) breakpoint set -f MyFile.swift -l 42 -c "name == \"test\""
Iteration-based: Break after N hits:
(lldb) breakpoint set -f MyFile.swift -l 42 -i 50
Ignores the first 50 hits, then breaks.
仅当条件满足时触发断点:
(lldb) breakpoint set -f MyFile.swift -l 42 -c "index > 100"
(lldb) breakpoint set -f MyFile.swift -l 42 -c "name == \"test\""
基于迭代次数: 跳过前N次触发,第N+1次触发:
(lldb) breakpoint set -f MyFile.swift -l 42 -i 50
忽略前50次触发,第51次触发断点。

Logpoints (Action + Auto-Continue)

日志断点(动作+自动继续)

Log without stopping — like a print statement but no rebuild needed:
(lldb) breakpoint set -f MyFile.swift -l 42
(lldb) breakpoint command add 1
> v self.value
> continue
> DONE
Or in Xcode: Edit breakpoint → Add Action → "Log Message" → use
@self.value@
token syntax → Check "Automatically continue"
无需暂停程序即可记录日志——类似print语句,但无需重新构建:
(lldb) breakpoint set -f MyFile.swift -l 42
(lldb) breakpoint command add 1
> v self.value
> continue
> DONE
在Xcode中操作:编辑断点 → 添加动作 → "Log Message" → 使用
@self.value@
令牌语法 → 勾选"Automatically continue"

Symbolic Breakpoints

符号断点

Break on ANY call to a method by name:
(lldb) breakpoint set -n viewDidLoad
(lldb) breakpoint set -n "MyClass.myMethod"
Break on all ObjC messages to a selector:
(lldb) breakpoint set -S "layoutSubviews"
按方法名在任意调用位置触发断点:
(lldb) breakpoint set -n viewDidLoad
(lldb) breakpoint set -n "MyClass.myMethod"
为OC选择器的所有消息触发断点:
(lldb) breakpoint set -S "layoutSubviews"

Exception Breakpoints

异常断点

Swift errors (break on throw):
(lldb) breakpoint set -E swift
Objective-C exceptions (break on throw):
(lldb) breakpoint set -E objc
In Xcode: Breakpoint Navigator → + → Swift Error Breakpoint / Exception Breakpoint
This is the single most useful breakpoint for crash debugging. It stops at the throw site instead of the catch/crash site.
Swift错误(在throw时触发):
(lldb) breakpoint set -E swift
Objective-C异常(在throw时触发):
(lldb) breakpoint set -E objc
在Xcode中操作: 断点导航器 → + → Swift Error Breakpoint / Exception Breakpoint
这是崩溃调试中最有用的断点类型,它会在抛出异常的位置暂停,而非在捕获/崩溃的位置。

Watchpoints

观察点

Break when a variable's value changes:
(lldb) watchpoint set variable self.count
(lldb) watchpoint set variable -w read_write myGlobal
Watchpoints are hardware-backed — limited to ~4 per process but very fast.
当变量值改变时触发断点:
(lldb) watchpoint set variable self.count
(lldb) watchpoint set variable -w read_write myGlobal
观察点由硬件支持——每个进程最多支持约4个,但速度极快。

One-Shot Breakpoints

一次性断点

Break once, then auto-delete:
(lldb) breakpoint set -f MyFile.swift -l 42 -o
触发一次后自动删除:
(lldb) breakpoint set -f MyFile.swift -l 42 -o

Managing Breakpoints

断点管理

(lldb) breakpoint list
(lldb) breakpoint disable 3
(lldb) breakpoint enable 3
(lldb) breakpoint delete 3
(lldb) breakpoint delete

(lldb) breakpoint list
(lldb) breakpoint disable 3
(lldb) breakpoint enable 3
(lldb) breakpoint delete 3
(lldb) breakpoint delete

Playbook 5: Async/Concurrency Debugging

手册5:异步/并发调试

Identifying Async Frames

识别异步栈帧

Swift concurrency backtraces are noisy — expect
swift_task_switch
,
_dispatch_call_block_and_release
, and executor internals mixed in with your code. Don't be discouraged by 40+ frames of runtime noise. Focus on frames from YOUR module.
In Swift concurrency backtraces, look for
swift-task
frames:
Thread 3:
frame #0: MyApp`MyActor.doWork()
frame #1: swift_task_switch
frame #2: MyApp`closure #1 in ViewController.loadData()
The
swift_task_switch
frame indicates an async suspension point. Your code frames are the ones prefixed with your module name (
MyApp
above).
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
)。

Inspecting Task State

探查任务状态

(lldb) thread backtrace all
Look for threads with
swift_task
in their frames. Each represents an active Swift task.
(lldb) thread backtrace all
查找包含
swift_task
的线程,每个这样的线程代表一个活跃的Swift任务。

Actor-Isolated Code

Actor隔离代码

When stopped inside an actor:
(lldb) v self
Shows all actor state. This works because LLDB pauses the entire process — actor isolation is a compile-time concept, not a runtime lock (for default actors).
当在Actor内部暂停时:
(lldb) v self
显示所有Actor状态。这是因为LLDB会暂停整个进程——Actor隔离是编译时概念,而非运行时锁(默认Actor)。

Task Group Inspection

任务组探查

When debugging task groups, break inside the group closure and inspect:
(lldb) v
(lldb) bt
Each child task runs on its own thread. Use
bt all
to see them.
Cross-reference: For Swift concurrency patterns and fix strategies →
/skill axiom-swift-concurrency
. For profiling async performance →
/skill axiom-concurrency-profiling

调试任务组时,在组闭包内设置断点并执行以下命令:
(lldb) v
(lldb) bt
每个子任务在独立线程运行,使用
bt all
查看所有子任务。
交叉参考: Swift并发模式及修复策略 →
/skill axiom-swift-concurrency
。异步性能分析 →
/skill axiom-concurrency-profiling

Pressure Scenarios

压力场景处理

Scenario 1: "Release-Only Crash — LLDB Is Useless in Release"

场景1:"仅Release构建崩溃——LLDB在Release中无用"

Situation: Crash happens in Release builds but not Debug. Team says "we can't debug it."
Why this fails: Release optimizations change timing, memory layout, and can eliminate variables — making the crash non-reproducible in Debug.
Correct approach:
  1. Build with Debug configuration but Release-like settings:
    • Optimization Level:
      -O
      (not
      -Onone
      )
    • Still include debug symbols (
      DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
      )
  2. Enable Address Sanitizer (
    -fsanitize=address
    ) — catches memory errors with 2-3x overhead
  3. Use the crash report to set breakpoints at the crash site
  4. Set exception breakpoints to catch the error before the crash:
    (lldb) breakpoint set -E swift
    (lldb) breakpoint set -E objc
  5. If variable shows
    <optimized out>
    , reduce optimization for that one file:
    • Build Settings → Per-file flags →
      -Onone
      for the specific file
  6. Last resort — read register values directly (variables live in registers before being optimized out):
    (lldb) register read
    (lldb) register read x0 x1 x2
    On ARM64:
    x0
    = self,
    x1
    -
    x7
    = first 7 arguments. Check
    /skill axiom-lldb-ref
    Part 1 for details.
情况: 崩溃仅发生在Release构建,而非Debug构建。团队认为“无法调试”。
失败原因: Release构建的优化会改变程序时序、内存布局,甚至移除变量——导致崩溃在Debug构建中无法复现。
正确方案:
  1. 使用Debug配置但启用类Release的设置:
    • 优化级别:
      -O
      (而非
      -Onone
    • 仍保留调试符号(
      DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
  2. 启用Address Sanitizer(
    -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部分。

Scenario 2: "Just Add Print Statements"

场景2:"直接加Print语句"

Situation: Developer adds
print()
calls to debug, rebuilds, runs, reads console. Repeat.
Why this fails: Each print-debug cycle costs 3-5 minutes (edit → build → run → navigate to state → read output). An LLDB breakpoint costs 30 seconds.
Correct approach:
  1. Set a breakpoint at the line you'd add a
    print()
    :
    (lldb) b MyFile.swift:42
  2. Add a logpoint for "print-like" behavior without rebuilding:
    • Edit breakpoint → Add Action → Log Message → Check "Auto continue"
  3. Inspect variables directly:
    v self.someValue
  4. Modify variables at runtime to test theories:
    expr self.debugMode = true
  5. One breakpoint session replaces 5-10 print-debug cycles.
Time comparison (typical control-flow debugging):
ApproachPer investigation5 variables
print() statements3-5 min (build + run)15-25 min
LLDB breakpoint30 sec (set + inspect)2.5 min
Exception: In tight loops (thousands of hits/sec), logpoints add per-hit overhead. Use
-i
to skip to the iteration you care about, or use a temporary
print()
for that specific loop.
情况: 开发者添加
print()
语句进行调试,重新构建、运行、查看控制台,重复该流程。
失败原因: 每次print调试循环耗时3-5分钟(编辑→构建→运行→导航到目标状态→读取输出),而LLDB断点仅需30秒。
正确方案:
  1. 在你想要添加
    print()
    的位置设置断点:
    (lldb) b MyFile.swift:42
  2. 添加日志断点实现“类似print”的行为,无需重新构建:
    • 编辑断点 → 添加动作 → Log Message → 勾选"Auto continue"
  3. 直接探查变量:
    v self.someValue
  4. 运行时修改变量验证假设:
    expr self.debugMode = true
  5. 一次断点会话可替代5-10次print调试循环。
时间对比(典型控制流调试):
方案单次排查耗时5个变量排查耗时
print()语句3-5分钟(构建+运行)15-25分钟
LLDB断点30秒(设置+探查)2.5分钟
例外情况: 在高频循环(每秒数千次触发)中,日志断点会带来额外开销。使用
-i
跳过无关的迭代,或为该特定循环临时添加
print()

Scenario 3: "po Doesn't Work So LLDB Is Broken"

场景3:"po无效所以LLDB对Swift无用"

Situation: Developer types
po myStruct
and gets garbage. Concludes LLDB is broken for Swift. Goes back to print debugging.
This is the #1 reason developers abandon LLDB.
Why
po
fails with Swift structs:
po
calls
debugDescription
which requires compiling an expression in the debugger context. For Swift structs, this compilation often fails due to missing type metadata, generics, or module resolution issues.
Correct approach:
  1. Use
    v
    instead of
    po
    — reads memory directly, no compilation:
    (lldb) v myStruct
    (lldb) v myStruct.propertyName
  2. Use
    p
    for computed properties:
    (lldb) p myStruct.computedValue
  3. Use
    po
    only for classes with
    CustomDebugStringConvertible
  4. If
    p
    also fails, try specifying the language:
    (lldb) expr -l objc -- (id)0x12345
  5. If everything fails,
    v self
    always works inside a method.

情况: 开发者执行
po myStruct
得到乱码,得出LLDB对Swift无用的结论,回到print调试。
这是开发者放弃LLDB的头号原因。
po
对Swift结构体失效的原因:
po
会调用
debugDescription
,这需要在调试器上下文中编译表达式。对于Swift结构体,该编译过程常因缺失类型元数据、泛型或模块解析问题而失败。
正确方案:
  1. 使用
    v
    替代
    po
    ——直接读取内存,无需编译:
    (lldb) v myStruct
    (lldb) v myStruct.propertyName
  2. 使用
    p
    探查计算属性:
    (lldb) p myStruct.computedValue
  3. 仅对实现了
    CustomDebugStringConvertible
    的类使用
    po
  4. 如果
    p
    也失效,尝试指定语言:
    (lldb) expr -l objc -- (id)0x12345
  5. 如果所有命令都失效,在方法内部执行
    v self
    始终有效。

Anti-Patterns

反模式

Anti-PatternWhy It's WrongBetter Alternative
po
everything
Fails for Swift structs, enums, optionals
v
for values,
po
only for classes
Print-debug cycles3-5 min per cycle vs 30 sec breakpointBreakpoints with logpoint actions
"LLDB doesn't work with Swift"It does — wrong command choice
v
is designed for Swift values
Ignoring backtracesJumping to guesses instead of reading the trace
bt
first, then navigate frames
Conditional breakpoints on every hitSlows execution if condition is expensiveUse
-i
(ignore count) when possible
Debugging optimized (Release) buildsVariables missing, code reorderedDebug configuration, or per-file
-Onone
Force-continuing past exceptionsHides the real errorFix the exception, don't suppress it
No exception breakpoints setCrashes land in system code, not throw siteAlways add Swift Error + ObjC Exception breakpoints
反模式错误原因更好的替代方案
所有场景都用
po
对Swift结构体、枚举、可选值失效
v
探查值,仅对类使用
po
Print调试循环每次循环耗时3-5分钟,而断点仅需30秒使用带日志动作的断点
"LLDB对Swift无用"实际是命令选择错误
v
是为Swift值设计的命令
忽略回溯信息直接猜测原因而非读取回溯先执行
bt
,再跳转到目标栈帧
对每次触发都设置条件断点条件判断开销大,拖慢程序执行尽可能使用
-i
(忽略次数)
调试优化后的(Release)构建变量缺失、代码重排使用Debug配置,或为特定文件设置
-Onone
强制跳过异常隐藏真实错误修复异常,而非抑制异常
未设置异常断点崩溃发生在系统代码中,而非抛出位置始终启用Swift Error + ObjC Exception断点

Debugging Checklist

调试检查清单

Before starting a debug session:
  • Debug build configuration (not Release)
  • Exception breakpoints enabled (Swift Error + ObjC Exception)
  • Breakpoint set before suspected problem area
  • Know which command to use:
    v
    for values,
    p
    for computed,
    po
    for descriptions
During debug session:
  • Read stop reason (
    thread info
    ) before anything else
  • Get backtrace (
    bt
    ) — find your frame
  • Navigate to your frame (
    frame select N
    )
  • Inspect relevant state (
    v self
    ,
    v localVar
    )
  • Understand the cause before writing any fix
After finding the issue:
  • Set conditional breakpoint to catch recurrence
  • Consider adding assertion/precondition for this case
  • Remove temporary breakpoints
开始调试会话前:
  • 使用Debug构建配置(而非Release)
  • 启用异常断点(Swift Error + ObjC Exception)
  • 在疑似问题区域前设置断点
  • 明确使用的命令:
    v
    探查值,
    p
    探查计算属性,
    po
    查看描述
调试会话中:
  • 先读取停止原因(
    thread info
  • 获取回溯信息(
    bt
    )——找到你的栈帧
  • 跳转到你的栈帧(
    frame select N
  • 探查相关状态(
    v self
    ,
    v localVar
  • 在编写修复代码前理解问题原因
找到问题后:
  • 设置条件断点防止问题复发
  • 考虑添加断言/前置条件
  • 删除临时断点

Resources

资源

WWDC: 2019-429, 2018-412, 2022-110370
Docs: /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
Skills: axiom-lldb-ref, axiom-testflight-triage, axiom-hang-diagnostics, axiom-memory-debugging, axiom-swift-concurrency, axiom-concurrency-profiling
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