WWDC Quick Look 💓 By SwiftGGTeam
Run, Break, Inspect: Explore effective debugging in LLDB

Run, Break, Inspect: Explore effective debugging in LLDB

观看原视频

Highlight

LLDB 将调试建模为 Run → Break → Inspect 的循环:每次暂停后检查状态,根据结果决定继续执行还是重新运行,逐步缩小问题范围直到定位 bug。


核心内容

print 调试是大多数人最先学会的调试方式:插入 print 语句,重新编译,运行,看输出,不满意就再来一轮。这个过程有三个问题——每次改 print 都要重新编译;print 语句容易遗留到生产环境;而且你能检查的状态完全取决于你事先想到了什么。LLDB 提供了一套更高效的替代方案:在运行时动态设断点、检查变量、计算表达式,全部不需要重新编译。

这场 Session 把调试抽象成一个搜索问题。程序在某一点出现了错误行为,bug 存在于程序启动到出错之间的某段代码中。你的目标就是找到那段代码。方法是在不同时间点检查程序状态,每次检查都让你离 bug 更近一步。LLDB 提供了四个核心工具来完成这个搜索:backtrace(回溯调用栈)、变量检查、断点、表达式求值。

Session 以 Destination Video 这个多平台视频播放 App 为示例,围绕一个真实的调试场景展开:开发者给 App 添加了 “Watch Later” 功能,需要理解 SwiftUI 闭包的执行时机,排查 JSON 解码错误,以及自定义数据类型在调试器中的显示格式。每种场景都对应 LLDB 的一个核心能力。


详细内容

crashlog 离线分析

程序崩溃时,Apple 平台会自动收集崩溃时刻的状态信息,生成 crashlog。LLDB 可以直接消费 crashlog,模拟出一个调试会话——不需要运行程序就能定位问题(04:37)。

在 Xcode 中右键点击 crashlog 文件,选择用 Xcode 打开并关联到对应项目。Xcode 会用 LLDB 创建一个调试会话,高亮崩溃行。通过 backtrace 可以查看完整的调用链:当前帧是 JSON 加载函数,上层帧显示正在导入视频元数据,再往上是程序初始化。一条 crashlog 有时就足够定位 bug。

前提条件:项目需要 checkout 到与 crashlog 相同的 commit,并且对应的 dSYM bundle 可用。

SwiftUI 中的一行断点解析为多个位置

在 SwiftUI 的 Button 构造函数上设一个行断点,LLDB 会将其解析为多个独立的断点位置(08:29):

Button(action: { watchLater.toggle(video: video) }) {
  let inList = watchLater.isInList(video: video)
  Label(inList ? "In Watch Later" : "Add to Watch Later",
  systemImage: inList ? "checkmark" : "plus")
}

关键点:

  • LLDB 将这一行解析为 3 个断点位置,ID 分别为 1.1、1.2、1.3
  • 1.1 是 Button 构造函数的调用点,在创建 UI 时触发
  • 1.2 是 action 闭包(watchLater.toggle(video: video)),只在用户点击按钮时触发
  • 1.3 是 trailing closure(label 闭包),由构造函数自身调用
  • 这是 SwiftUI 等声明式框架的常见场景:同一行代码通过不同代码路径执行
  • breakpoint list 命令可以查看每个位置的行号和列号
  • 每个位置可以单独启用或禁用

断点动作与命令行操作

在 Break/Inspect 循环中,反复手动执行相同命令很繁琐。断点动作(breakpoint actions)可以在命中断点时自动执行命令(13:17):

p "last video is \(watchLater.last?.name)"

在 Xcode 中右键断点 → Edit Breakpoint → 添加 Debugger Command action,还可以勾选 “Continue after evaluation” 让程序自动继续执行。

在命令行中同样可以操作(14:42):

b DetailView.swift:70
break command add
p "last video is \(watchLater.last?.name)"
continue
DONE

关键点:

  • bbreakpoint set 的简写
  • break command add 为最近一个断点添加动作,输入 DONE 结束
  • 命令行设置的动作会覆盖 Xcode 中设置的动作
  • helpapropos 命令可以搜索 LLDB 的所有命令

条件断点与高频断点的处理

当断点在循环中被触发太多次时,有三种过滤手段(16:09):

  1. 条件断点:用 break modify --condition 添加布尔表达式,如 video.duration > 60
  2. 临时断点(tbreak):在断点动作中用 tbreak 创建只触发一次的断点,适合”从 A 执行到 B 才停下来”的场景
  3. 忽略计数break modify --ignore-count 10 跳过前 N 次命中

当一行代码执行百万次时,上述方法仍会导致 LLDB 每次都停下来判断条件,拖慢执行速度。这时建议在代码中加 if 判断,内部调用 raise(SIGSTOP) 让调试器接管。

p 命令与表达式求值

从 Xcode 15 开始,p 被重新定义为 “do what I mean” print 的别名,覆盖了大多数变量检查和表达式求值场景(19:57)。

在排查 JSON 解码错误时,可以用 p 逐步构建复杂表达式(24:06):

p watchLater.count
p watchLater.last!.name

关键点:

  • p 可以在任何 backtrace 帧中求值表达式
  • 可以逐步构建表达式,每步查看中间结果,不需要重新编译
  • 变量也可以在 Xcode 的变量查看器或源码悬停提示中查看
  • 部分类型(如 URL、Image)有 Quick Look 按钮,提供额外可视化
  • Swift Error breakpoint 可以在 Swift Error 抛出时自动暂停程序

@DebugDescription 宏

Swift 6 引入了 @DebugDescription 宏,让自定义类型在调试器中显示可读摘要(26:46):

@DebugDescription
struct WatchLaterItem {
    let video: Video
    let name: String
    let addedOn: Date

    var debugDescription: String {
        "\(name) - \(addedOn)"
    }
}

关键点:

  • @DebugDescription 标注类型,再提供 debugDescription 计算属性
  • debugDescription 必须使用字符串插值和存储属性构建
  • 标注后,变量查看器和 p 命令中会直接显示摘要,不需要手动展开每个条目
  • 如果你之前用 CustomDebugStringConvertible 协议配合 po 命令,且实现只用了字符串插值和计算属性,可以迁移到 @DebugDescription,与调试器集成更好

核心启发

  • 做什么:用断点动作替代 print 调试。为什么值得做:断点动作不需要重新编译,可以动态添加和修改,避免了 print 语句遗留到生产环境的风险。怎么开始:在 Xcode 中右键断点 → Edit Breakpoint → 添加 Debugger Command action,输入 p 表达式并勾选自动继续。
  • 做什么:为项目中频繁出现在集合中的核心数据类型添加 @DebugDescription。为什么值得做:调试时在变量查看器中直接看到每条记录的摘要,而不是逐个展开,大幅提升调试效率。怎么开始:用 @DebugDescription 标注类型,实现 debugDescription 计算属性,用字符串插值输出关键信息。
  • 做什么:在 CI 流水线中归档每个构建的 dSYM bundle。为什么值得做:crashlog 离线分析依赖 dSYM 进行符号化,没有 dSYM 就只能看到内存地址,无法定位到源码行号。怎么开始:确认 Xcode Archive 时 dSYM 生成选项已开启,App Store Connect 会自动处理上传的 dSYM,内部测试分发需要自行归档管理。
  • 做什么:用条件断点和忽略计数处理高频断点,而非放弃设断点。为什么值得做:循环中的断点可能触发成千上万次,手动点继续不现实;条件断点只在满足条件时暂停,忽略计数跳过已知无关的迭代,两者结合能精准定位目标。怎么开始:右键断点 → Edit Breakpoint → 填写 Condition 或 Ignore Count,命令行用 break modify --condition--ignore-count

关联 Session

评论

GitHub Issues · utterances