axiom-lldb by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-lldb使用 LLDB 进行交互式调试。调试器可以冻结时间,让你能够审问正在运行的应用——检查变量、评估表达式、浏览线程,并准确理解出错的原因。
核心见解: "LLDB 没用" 实际上意味着 "我不知道该用哪个命令来处理 Swift 类型。" 这是一个知识差距问题,而不是工具问题。
| 症状 | 此技能适用 |
|---|---|
| 需要在运行时检查变量 | 是 — 断点 + 检查 |
| 可以在本地复现的崩溃 | 是 — 在崩溃点前设置断点 |
| 运行时值错误但代码看起来正确 | 是 — 单步执行并检查 |
| 需要理解挂起时的线程状态 | 是 — 暂停 + 线程回溯 |
po 不工作 / 显示垃圾信息 | 是 — Playbook 3 提供了替代方案 |
| 分析了崩溃日志,需要复现 | 是 — 根据崩溃上下文设置断点 |
| 需要在不重新构建的情况下测试修复 | 是 — 表达式求值 |
| 想要在所有异常处中断 | 是 — 异常断点 |
| 应用感觉慢但有响应 | 否 — 使用 axiom-performance-profiling |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 内存随时间增长 | 否 — 首先使用 axiom-memory-debugging |
| 应用完全冻结 | 可能 — 首先使用 axiom-hang-diagnostics,然后使用 LLDB 进行线程检查 |
| 生产环境崩溃,无法本地复现 | 否 — 首先使用 axiom-testflight-triage |
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 调试时,请按以下结构组织你的输出:
(lldb) 前缀)目标: 从停止点开始,理解应用崩溃的原因。
当调试器停止时,首先要检查:
(lldb) thread info
这会显示停止原因。常见的停止原因:
| 停止原因 | 含义 | 下一步 |
|---|---|---|
EXC_BAD_ACCESS (SIGSEGV) | 访问了无效内存(空指针、悬垂引用) | 检查地址 — 0x0 到 0x10 = 空值解引用 |
EXC_BAD_ACCESS (SIGBUS) | 未对齐或无效地址 | 通常是 C 语言互操作或不安全指针问题 |
EXC_BREAKPOINT (SIGTRAP) | 遇到陷阱 — Swift 运行时检查失败 | 检查 fatalError()、preconditionFailure()、强制解包 nil、数组越界 |
EXC_CRASH (SIGABRT) | 故意中止 — 断言或未捕获的异常 | 查看 "Application Specific Information" 中的消息 |
breakpoint | 你的断点被命中 | 正常 — 检查状态 |
(lldb) bt
从上到下阅读。找到第一个在 你的 代码中的帧(不是系统框架)。那就是开始调查的地方。
(lldb) bt 10
如果完整跟踪信息太多,限制为 10 帧。
(lldb) frame select 3
跳转到第 3 帧(或你代码所在的任何帧)。
(lldb) v
(lldb) v self.someProperty
(lldb) v localVariable
使用 v(而不是 po)来可靠地检查 Swift 值。详见 Playbook 3。
| 异常类型 | 典型原因 | 修复模式 |
|---|---|---|
EXC_BAD_ACCESS 在低地址 | 强制解包 nil 可选值 | guard let / if let |
EXC_BAD_ACCESS 在高地址 | 释放后使用 / 悬垂指针 | 检查对象生命周期,[weak self] |
EXC_BREAKPOINT | Swift 运行时陷阱(越界、解包、前置条件) | 修复被违反的前置条件 |
SIGABRT | 未捕获的 ObjC 异常或 fatalError() | 读取异常消息,修复根本原因 |
(lldb) breakpoint set -f MyFile.swift -l 42 -c "value == nil"
这仅在 value 在第 42 行为 nil 时中断 — 在崩溃前捕获问题。
目标: 通过检查所有线程状态来理解应用为何冻结。
如果应用挂起,在 Xcode 中按暂停按钮 (⌃⌘Y) 或:
(lldb) process interrupt
(lldb) thread backtrace all
或者简写:
(lldb) bt all
查看线程 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()
解释:在主线程上进行昂贵的工作。移到后台。
如果两个线程都在等待对方持有的东西:
(lldb) thread list
查找状态为 waiting 且相互引用对方锁的多个线程。
(lldb) thread select 3
(lldb) bt
(lldb) v
切换到另一个线程以检查其状态。
交叉参考: 一旦确定了挂起原因,关于修复模式 → /skill axiom-hang-diagnostics
这是本技能的核心价值。 大多数开发者放弃 LLDB 是因为 po 不能可靠地处理 Swift 类型。以下是实际有效的方法。
| 命令 | 完整形式 | 作用 | 最适合 |
|---|---|---|---|
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 等)。
| 你看到的情况 | 原因 | 修复方法 |
|---|---|---|
<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 的上下文中(静态、闭包) | 使用带有显式变量名的 v |
p 显示 $R0 = ... 但 po 崩溃 | 不同的编译路径 | 当 p 有效时使用它;po 增加了一个额外的描述步骤,可能会失败 |
(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 @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
Actor 状态最好用 v 检查,它直接读取内存,无需考虑隔离问题:
(lldb) v actor
显示所有存储属性。这之所以有效,是因为 LLDB 暂停了整个进程 — 你可以读取任何内存,无论 Actor 隔离如何(这是一个编译时概念)。
(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
(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 次命中,然后中断。
记录而不停止 — 就像打印语句,但无需重新构建:
(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"
通过方法名中断对它的 任何 调用:
(lldb) breakpoint set -n viewDidLoad
(lldb) breakpoint set -n "MyClass.myMethod"
中断发送到选择器的所有 ObjC 消息:
(lldb) breakpoint set -S "layoutSubviews"
Swift 错误(在抛出时中断):
(lldb) breakpoint set -E swift
Objective-C 异常(在抛出时中断):
(lldb) breakpoint set -E objc
在 Xcode 中: 断点导航器 → + → Swift Error Breakpoint / Exception Breakpoint
这是用于崩溃调试的最有用的断点。它在抛出点停止,而不是在捕获/崩溃点。
当变量的值改变时中断:
(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
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 任务。
当在 Actor 内部停止时:
(lldb) v self
显示所有 Actor 状态。这之所以有效,是因为 LLDB 暂停了整个进程 — Actor 隔离是一个编译时概念,而不是运行时锁(对于默认 Actor)。
调试任务组时,在组闭包内部中断并检查:
(lldb) v
(lldb) bt
每个子任务在自己的线程上运行。使用 bt all 查看它们。
交叉参考: 关于 Swift 并发模式和修复策略 → /skill axiom-swift-concurrency。关于分析异步性能 → /skill axiom-concurrency-profiling
情况: 崩溃发生在 Release 构建中,但 Debug 中不发生。团队说 "我们无法调试它。"
为何失败: Release 优化改变了时序、内存布局,并且可能消除变量 — 使得崩溃在 Debug 中无法复现。
正确方法:
使用 Debug 配置但类似 Release 的设置进行构建:
-O(不是 -Onone)DEBUG_INFORMATION_FORMAT = dwarf-with-dsym)启用 Address Sanitizer(-fsanitize=address)— 捕获内存错误,开销为 2-3 倍
使用崩溃报告在崩溃点设置断点
设置异常断点以在崩溃前捕获错误:
(lldb) breakpoint set -E swift
(lldb) breakpoint set -E objc
如果变量显示 <optimized out>,减少该文件的优化:
-Onone最后手段 — 直接读取寄存器值(变量在被优化掉之前存在于寄存器中):
(lldb) register read
(lldb) register read x0 x1 x2
在 ARM64 上:x0 = self,x1-x7 = 前 7 个参数。详情请查看 /skill axiom-lldb-ref 第 1 部分。
情况: 开发者添加 print() 调用来调试,重新构建,运行,读取控制台。重复。
为何失败: 每个打印调试周期花费 3-5 分钟(编辑 → 构建 → 运行 → 导航到状态 → 读取输出)。一个 LLDB 断点花费 30 秒。
正确方法:
在你想要添加 print() 的行设置断点:
(lldb) b MyFile.swift:42
添加一个日志点以实现 "类似打印" 的行为,无需重新构建:
直接检查变量:v self.someValue
在运行时修改变量以测试理论:expr self.debugMode = true
一个断点会话可以替代 5-10 个打印调试周期。
时间对比(典型的控制流调试):
| 方法 | 每次调查 | 5 个变量 |
|---|---|---|
| print() 语句 | 3-5 分钟(构建 + 运行) | 15-25 分钟 |
| LLDB 断点 | 30 秒(设置 + 检查) | 2.5 分钟 |
例外: 在紧密循环中(每秒数千次命中),日志点会增加每次命中的开销。使用 -i 跳过到你关心的迭代,或者在该特定循环中使用临时的 print()。
情况: 开发者输入 po myStruct 并得到垃圾信息。得出结论 LLDB 对 Swift 无效。回到打印调试。
这是开发者放弃 LLDB 的 #1 原因。
为何 po 对 Swift 结构体失败: po 调用 debugDescription,这需要在调试器上下文中编译表达式。对于 Swift 结构体,这种编译经常由于缺少类型元数据、泛型或模块解析问题而失败。
正确方法:
使用 v 代替 po — 直接读取内存,无需编译:
(lldb) v myStruct
(lldb) v myStruct.propertyName
使用 p 处理计算属性:
(lldb) p myStruct.computedValue
仅对符合 CustomDebugStringConvertible 的类使用 po
如果 p 也失败,尝试指定语言:
(lldb) expr -l objc -- (id)0x12345
如果一切都失败,v self 在方法内部总是有效的。
| 反模式 | 为何错误 | 更好的替代方案 |
|---|---|---|
对所有东西都用 po | 对 Swift 结构体、枚举、可选值失败 | 对值使用 v,仅对类使用 po |
| 打印调试循环 | 每个周期 3-5 分钟 vs 30 秒断点 | 带有日志点动作的断点 |
| "LLDB 对 Swift 无效" | 它有效 — 命令选择错误 | v 是为 Swift 值设计的 |
| 忽略回溯 | 直接猜测而不是读取跟踪信息 | 先 bt,然后导航帧 |
| 每次命中都使用条件断点 | 如果条件开销大,会减慢执行速度 | 尽可能使用 -i(忽略计数) |
| 调试优化(Release)构建 | 变量缺失,代码重排 | Debug 配置,或每个文件设置 -Onone |
| 强制继续越过异常 | 隐藏了真正的错误 | 修复异常,不要抑制它 |
| 未设置异常断点 | 崩溃发生在系统代码中,而不是抛出点 | 始终添加 Swift Error + ObjC Exception 断点 |
开始调试会话前:
v 用于值,p 用于计算属性,po 用于描述调试会话期间:
thread info)bt)— 找到你的帧frame select N)v self,v localVar)找到问题后:
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
每周安装量
37
仓库
GitHub 星标
590
首次出现
2026年2月19日
安全审计
安装于
opencode35
codex35
github-copilot34
kimi-cli34
gemini-cli34
amp34
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.
| Symptom | This Skill Applies |
|---|---|
| Need to inspect a variable at runtime | Yes — breakpoint + inspect |
| Crash you can reproduce locally | Yes — breakpoint before crash site |
| Wrong value at runtime but code looks correct | Yes — step through and inspect |
| Need to understand thread state during hang | Yes — pause + thread backtrace |
po doesn't work / shows garbage | Yes — Playbook 3 has alternatives |
| Crash log analyzed, need to reproduce | Yes — set breakpoints from crash context |
| Need to test a fix without rebuilding | Yes — expression evaluation |
| Want to break on all exceptions | Yes — exception breakpoints |
| App feels slow but responsive | No — use axiom-performance-profiling |
| Memory grows over time | No — use axiom-memory-debugging first |
| App completely frozen | Maybe — use axiom-hang-diagnostics first, then LLDB for thread inspection |
| Crash in production, no local repro | No — use axiom-testflight-triage first |
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.
When helping with LLDB debugging, structure your output as:
(lldb) prefix)Goal: Understand why the app crashed, starting from the stop point.
When the debugger stops, the first thing to check:
(lldb) thread info
This shows the stop reason. Common stop reasons:
| Stop Reason | Meaning | Next 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 address | Usually C interop or unsafe pointer issue |
EXC_BREAKPOINT (SIGTRAP) | Hit a trap — Swift runtime check failed | Check for fatalError(), preconditionFailure(), force-unwrap of nil, array out of bounds |
(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) frame select 3
Jump to frame 3 (or whichever frame is in your code).
(lldb) v
(lldb) v self.someProperty
(lldb) v localVariable
Use v (not po) for reliable Swift value inspection. See Playbook 3 for details.
| Exception Type | Typical Cause | Fix 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 pointer | Check object lifetime, [weak self] |
EXC_BREAKPOINT | Swift runtime trap (bounds, unwrap, precondition) | Fix the violated precondition |
(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.
Goal: Understand why the app is frozen by inspecting all thread states.
If the app is hung, press the pause button in Xcode (⌃⌘Y) or:
(lldb) process interrupt
(lldb) thread backtrace all
Or the shorthand:
(lldb) bt all
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.
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 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
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.
| Command | Full Form | What It Does | Best For |
|---|---|---|---|
v | frame variable | Reads memory directly, no compilation | Swift structs, enums, locals — your default |
p | expression (with formatter) | Compiles expression, shows formatted result | Computed properties, function calls |
po | expression --object-description |
Start withv — 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.
Usep 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.
Usepo 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.).
| What You See | Why | Fix |
|---|---|---|
<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 type | Try expr -l objc -- (id)0x12345 for ObjC objects, or use v |
<variable not available> |
(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 myArray
(lldb) v myArray[2]
(lldb) v myDict
For large collections, limit output:
(lldb) p Array(myArray.prefix(5))
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
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).
(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 assigns result variables ($R0, $R1, etc.):
(lldb) p someValue
$R0 = 42
(lldb) p $R0 + 10
$R1 = 52
(lldb) breakpoint set -f ViewController.swift -l 42
(lldb) b ViewController.swift:42
Short form b works for simple cases.
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.
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"
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"
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.
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.
Break once, then auto-delete:
(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
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).
(lldb) thread backtrace all
Look for threads with swift_task in their frames. Each represents an active Swift task.
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).
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
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:
Build with Debug configuration but Release-like settings:
-O (not -Onone)DEBUG_INFORMATION_FORMAT = dwarf-with-dsym)Enable Address Sanitizer (-fsanitize=address) — catches memory errors with 2-3x overhead
Use the crash report to set breakpoints at the crash site
Set exception breakpoints to catch the error before the crash:
(lldb) breakpoint set -E swift
(lldb) breakpoint set -E objc
If variable shows <optimized out>, reduce optimization for that one file:
-Onone for the specific fileOn ARM64: x0 = self, x1-x7 = first 7 arguments. Check /skill axiom-lldb-ref Part 1 for details.
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:
Set a breakpoint at the line you'd add a print():
(lldb) b MyFile.swift:42
Add a logpoint for "print-like" behavior without rebuilding:
Inspect variables directly: v self.someValue
Modify variables at runtime to test theories: expr self.debugMode = true
One breakpoint session replaces 5-10 print-debug cycles.
Time comparison (typical control-flow debugging):
| Approach | Per investigation | 5 variables |
|---|---|---|
| print() statements | 3-5 min (build + run) | 15-25 min |
| LLDB breakpoint | 30 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.
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.
Whypo 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:
Use v instead of po — reads memory directly, no compilation:
(lldb) v myStruct
(lldb) v myStruct.propertyName
Use p for computed properties:
(lldb) p myStruct.computedValue
Use po only for classes with CustomDebugStringConvertible
If p also fails, try specifying the language:
(lldb) expr -l objc -- (id)0x12345
| Anti-Pattern | Why It's Wrong | Better Alternative |
|---|---|---|
po everything | Fails for Swift structs, enums, optionals | v for values, po only for classes |
| Print-debug cycles | 3-5 min per cycle vs 30 sec breakpoint | Breakpoints with logpoint actions |
| "LLDB doesn't work with Swift" | It does — wrong command choice | v is designed for Swift values |
| Ignoring backtraces | Jumping to guesses instead of reading the trace | bt first, then navigate frames |
| Conditional breakpoints on every hit |
Before starting a debug session:
v for values, p for computed, po for descriptionsDuring debug session:
thread info) before anything elsebt) — find your frameframe select N)v self, v localVar)After finding the issue:
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
Weekly Installs
37
Repository
GitHub Stars
590
First Seen
Feb 19, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode35
codex35
github-copilot34
kimi-cli34
gemini-cli34
amp34
Outbound Optimizer:基于Alex Hormozi原则的冷启动外联优化工具,提升邮件回复率与销售转化
120 周安装
后端架构模式指南:整洁架构、六边形架构与领域驱动设计实践
123 周安装
GitHub PR 自动创建工具 - 支持任务验证、测试执行与 Conventional Commits 规范
121 周安装
Anthropic Claude API 开发指南:Messages API、工具使用与提示工程实战
122 周安装
演讲幻灯片内容创作指南:掌握陈述式、提问式标题与极简设计原则
121 周安装
响应式设计完整指南:移动优先、Flexbox、CSS Grid与性能优化实践
124 周安装
EXC_CRASH (SIGABRT) | Deliberate abort — assertion or uncaught exception | Look at "Application Specific Information" for the message |
breakpoint | Your breakpoint was hit | Normal — inspect state |
SIGABRTUncaught ObjC exception or fatalError() |
| Read the exception message, fix the root cause |
Calls debugDescription |
Classes with CustomDebugStringConvertible |
expr | expression | Evaluates arbitrary code | Calling methods, modifying state |
| 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 reverse | Try 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 paths | Use p when it works; po adds an extra description step that can fail |
Last resort — read register values directly (variables live in registers before being optimized out):
(lldb) register read
(lldb) register read x0 x1 x2
If everything fails, v self always works inside a method.
| Slows execution if condition is expensive |
Use -i (ignore count) when possible |
| Debugging optimized (Release) builds | Variables missing, code reordered | Debug configuration, or per-file -Onone |
| Force-continuing past exceptions | Hides the real error | Fix the exception, don't suppress it |
| No exception breakpoints set | Crashes land in system code, not throw site | Always add Swift Error + ObjC Exception breakpoints |