📋 目录
- 一、概述与历史演进
- 二、核心原理与架构
- 三、获取与安装
- 四、命令体系与使用流程
- 五、常用命令详解与 SOP
- 六、关键概念图示与流程
- 七、自定义命令与开发工作流
- 八、伪代码与算法说明
- 九、应用场景与最佳实践
- 十、与其它调试工具的对比
- 参考文献
一、概述与历史演进
1.1 工具简介
Chisel 是 Facebook(Meta) 开源的 LLDB 命令集合,用于辅助调试 iOS 与 macOS 应用。它通过 Python 脚本 调用 LLDB 的 Scripting Bridge API(SB API) 扩展调试器能力,在不修改 Xcode 或 LLDB 本体的前提下,为开发者提供大量高层调试命令——如递归打印视图/控制器层级、在 Mac 上可视化 UIImage/UIView、按类名查找视图、对方法设置符号断点、查看响应链与约束等 [1][2][3]。
与仅使用 LLDB 内置的 po、bt、frame variable 等相比,Chisel 的命令更贴近 UIKit/AppKit 与日常 UI 调试场景,可显著减少手写表达式与重复操作。Chisel 与 Derek Selander 的 LLDB 扩展项目齐名,被广泛视为 iOS 开发者的标配调试增强工具 [1][3]。
仓库:GitHub - facebook/chisel;许可证:MIT。
1.2 历史与版本脉络
| 时期/事件 | 说明 |
|---|---|
| Facebook 开源 | Chisel 由 Facebook 工程师开发并开源,作为内部 iOS 调试的增强工具集 [1][2] |
| LLDB 与 Python | 依赖 LLDB 的 Python 脚本 与 SB API:通过 command script import 加载 fbchisellldb.py,在调试会话中注册自定义命令 [1][2] |
| Homebrew 分发 | 支持 brew install chisel 安装,安装后需在 ~/.lldbinit 中配置 command script import 路径 [1][2] |
| 架构差异 | Intel Mac:常见路径为 /usr/local/opt/chisel/libexec/fbchisellldb.py;Apple Silicon (M1+):为 /opt/homebrew/opt/chisel/libexec/fbchisellldb.py [2] |
| objc.io 推荐 | Chisel 官方 README 推荐阅读 Ari Grant 的 Dancing in the Debugger — A Waltz with LLDB(objc.io 第 19 期),以理解 LLDB 与 Chisel 的配合 [2] |
1.3 典型应用场景
- 视图/控制器层级排查:断点暂停后使用
pviews、pvc快速查看 keyWindow 的视图树与 ViewController 栈,定位层级或 present 关系问题。 - 视图定位与可视化:用
fv/fvc按类名或正则查找视图/控制器并将地址拷到剪贴板;用visualize将 UIImage/UIView/CALayer 等在 Mac 的 Preview 中打开,便于检查图片或布局。 - 临时显示/隐藏与边框:
show/hide、border/unborder、mask/unmask、flicker在不继续执行的情况下修改视图可见性或描边,辅助确认视图位置与遮挡关系。 - 断点与监视:
bmessage对类或其子类上的方法设置符号断点(无需关心具体实现类);wivar对实例变量设置 watchpoint,便于追踪成员变化。 - 响应链、约束与数据:
presponder打印响应链;paltrace、alamborder等辅助 Auto Layout 调试;pcurl、pjson、pdata等方便网络与数据调试。
二、核心原理与架构
2.1 LLDB 与 Python 脚本扩展
LLDB(Low Level Debugger)是 Apple 在 Xcode 中采用的底层调试器,支持 C、C++、Objective-C、Swift 等。除内置命令外,LLDB 提供 Python 脚本接口:在调试会话中可通过 command script import <path> 加载 Python 模块,该模块可调用 LLDB Python API(SB API) 访问调试目标(进程、线程、帧、变量、表达式求值等),并调用 debugger.HandleCommand() 或 SBCommandInterpreter 注册自定义命令 [2][4][5]。
Chisel 的入口脚本为 fbchisellldb.py:被 import 后,会加载 commands/ 目录下各 Python 模块,每个模块通过 FBCommand 基类(或等价接口)定义命令的 name、description、run 以及可选的参数/选项;最终这些命令被注册到当前 LLDB 的 command interpreter,在 (lldb) 提示符下可直接输入使用 [1][2]。
2.2 Chisel 的代码结构(概念)
- fbchisellldb.py:入口,负责加载各子模块并注册命令。
- fbchisellldbbase.py 等:基类与公共逻辑(如 FBCommand、参数解析、raw-input 等)。
- commands/:按功能拆分的命令实现,例如:
- FBPrintCommands.py:pviews、pvc、pclass、pmethods、presponder、pcurl、pjson 等打印类命令。
- FBDisplayCommands.py:border、unborder、show、hide、mask、unmask、caflush、dismiss、present 等显示与视图操作。
- FBFindCommands.py:fv、fvc、taplog、vs 等查找与交互。
- FBDebugCommands.py:bmessage、binside、wivar、mwarning 等断点与监视。
- FBVisualizationCommands.py:visualize。
- FBAutoLayoutCommands.py:paltrace、alamborder、alamunborder。
- 以及 Accessibility、Component、Invocation、TextInput 等 [1][2]。
命令实现中通过 LLDB SB API 获取当前 target、frame、变量,并执行表达式(如 [UIApplication sharedApplication]、keyWindow、subviews)以遍历视图层级或修改属性;部分命令会将数据(如图像)通过 LLDB 传回 Mac 并在本地用 Preview 等打开 [2][4]。
2.3 数据流与执行位置
Chisel 的命令在 开发机(Mac) 上的 LLDB 进程中执行,但 表达式求值 发生在 被调试进程(iOS 模拟器或真机上的 App)中。例如 pviews 会在目标进程中执行获取 keyWindow 与递归 description 的代码,结果回传到 LLDB 并打印到控制台;visualize 则会把目标进程中的 UIImage 等数据提取出来,在 Mac 上写入临时文件并用 Preview 打开 [2][4]。
flowchart LR
subgraph Mac
X[Xcode / LLDB]
C[Chisel Python]
P[Preview / 剪贴板]
end
subgraph 目标进程
A[iOS/macOS App]
end
X --> C
C -->|SB API 求值| A
A -->|返回值/数据| C
C --> X
C --> P
三、获取与安装
3.1 前置条件
| 项目 | 说明 |
|---|---|
| Mac | 运行 macOS,已安装 Xcode 及命令行工具 |
| LLDB | 随 Xcode 提供;Chisel 在调试会话中通过 command script import 加载 |
| Python | LLDB 内置 Python 绑定,无需单独安装 Python;Homebrew 安装的 Chisel 会使用系统或 LLDB 自带 Python |
3.2 通过 Homebrew 安装(推荐 [2])
brew update
brew install chisel
安装后,Chisel 的脚本通常位于:
- Intel Mac:
/usr/local/opt/chisel/libexec/fbchisellldb.py - Apple Silicon (M1+):
/opt/homebrew/opt/chisel/libexec/fbchisellldb.py
3.3 配置 ~/.lldbinit
若 ~/.lldbinit 不存在,可创建并编辑:
touch ~/.lldbinit
open ~/.lldbinit
在 ~/.lldbinit 中增加一行(路径按实际架构二选一):
# Intel Mac
command script import /usr/local/opt/chisel/libexec/fbchisellldb.py
# Apple Silicon (M1+)
# command script import /opt/homebrew/opt/chisel/libexec/fbchisellldb.py
保存后,下次启动 Xcode 并进入调试会话 时,Chisel 命令会自动加载。若已打开 Xcode,可先在 LLDB 中执行 command source ~/.lldbinit 重新加载 [2]。
3.4 从源码安装
从 facebook/chisel 克隆或下载后,在 ~/.lldbinit 中写:
command script import /path/to/chisel/fbchisellldb.py
将 /path/to/chisel 替换为本地 Chisel 仓库路径 [2]。
3.5 验证安装
在 Xcode 中运行任意 iOS/macOS 工程,断点命中后,在 LLDB 控制台输入:
(lldb) help
在输出末尾的「user-defined commands」中应能看到 Chisel 提供的命令(如 pviews、pvc、fv、border 等)。也可直接执行:
(lldb) pviews
若输出了当前 keyWindow 的视图层级,则安装与配置正确 [2]。
四、命令体系与使用流程
4.1 命令分类概览
| 类别 | 代表命令 | 用途 |
|---|---|---|
| 视图/控制器层级 | pviews、pvc | 递归打印 keyWindow 的 view / view controller 描述 |
| 查找 | fv、fvc、fa11y、vs | 按类名/正则/无障碍标签查找视图或控制器,或交互式搜索 |
| 可视化 | visualize | 在 Mac Preview 中打开 UIImage、UIView、CALayer 等 |
| 显示/边框/遮罩 | show、hide、border、unborder、mask、unmask、flicker | 临时显示/隐藏视图、加边框、加遮罩、闪烁 |
| 渲染 | caflush、slowanim、unslowanim | 刷新 Core Animation、慢速动画 |
| 断点与监视 | bmessage、binside、wivar | 方法符号断点、库内偏移断点、实例变量 watchpoint |
| 打印 | presponder、pclass、pmethods、pproperties、pcurl、pjson、pdata、pblock、pinvocation、pivar 等 | 响应链、继承关系、方法列表、属性、curl、JSON、NSData、Block、调用信息、实例变量 |
| Auto Layout | paltrace、alamborder、alamunborder | 约束追踪、歧义约束边框 |
| ViewController | present、dismiss | present / dismiss 指定 VC |
| 其它 | mwarning、setinput、settext、taplog、pcomponents、dcomponents、rcomponents 等 | 模拟内存警告、输入文本、点击日志、Component 相关 |
完整列表可在 LLDB 中执行 help 查看,或参阅 Chisel Wiki [1][2]。
4.2 基本使用流程
- 在 Xcode 中为 iOS 或 macOS 项目设置断点(或运行后点击暂停)。
- 断点命中或暂停后,在 LLDB 控制台 输入 Chisel 命令;多数命令支持 raw-input(即命令后可直接写表达式,如
fv UITableView、border 0x12345678);可执行help raw-input查看说明。 - 查看输出或效果(控制台打印、剪贴板、Preview 窗口等);若需修改命令行为,可查阅
help <command>。 - 继续执行(如
continue)或单步调试,结合其它 LLDB 命令(po、bt、frame variable)完成排查。
五、常用命令详解与 SOP
5.1 视图与控制器层级
| 命令 | 语法与说明 | 典型用法 |
|---|---|---|
| pviews | pviews [--up] [--depth=depth] [view] | 无参数时递归打印 keyWindow 的视图层级;--up 只打印从指定 view 到 window 的上层;--depth 限制深度;传入 view 则从该 view 开始 [2][6] |
| pvc | pvc [viewController] | 递归打印 keyWindow 的 ViewController 层级(含 present 关系);iOS 常用,macOS 不支持 [2][6] |
SOP:布局或层级异常时,先 pviews 看整棵树,再用 fv <ClassName> 找到目标 view 地址,用 border <addr> 或 mask <addr> 在界面上标出位置;若关心 VC 栈则用 pvc。
5.2 查找与可视化
| 命令 | 语法与说明 | 典型用法 |
|---|---|---|
| fv | fv <classNameRegex> | 在 keyWindow 的视图树中按类名正则查找,第一个匹配的 view 地址会写入剪贴板;后续可用 border (id)[剪贴板] 或直接 border <addr> [2][6] |
| fvc | fvc [--name=classNameRegex] [--view=view] | 按 ViewController 类名正则查找,或将拥有某 view 的 VC 打印出来 [2][6] |
| visualize | visualize <expr> | 将 UIImage、CGImageRef、UIView、CALayer、NSData(图像)、UIColor、CIColor、CIImage、CGColorRef、CVPixelBuffer 等在 Preview.app 中打开;expr 为对象表达式 [2][6] |
SOP:需要确认某视图是否在层级中或位置时:fv MyCustomView → 粘贴地址 → border (id)0x... 或 visualize (UIView *)0x...。
5.3 显示、边框与遮罩
| 命令 | 语法与说明 | 典型用法 |
|---|---|---|
| show / hide | show <view/layer> ;hide <view/layer> | 不继续执行即可在设备/模拟器上显示或隐藏该 view/layer,便于确认是谁在遮挡 [2][6] |
| border / unborder | border [--color=] [--width=] [--depth=] <view/layer> | 给 view/layer 画边框;color、width、depth 可选;unborder 移除 [2][6] |
| mask / unmask | mask [--color=] [--alpha=] <view/layer> | 在 view/layer 上叠加半透明矩形,标出范围;unmask 移除 [2][6] |
| flicker | flicker <view> | 快速显示再隐藏一次,用于快速定位视图位置 [2][6] |
5.4 断点与监视
| 命令 | 语法与说明 | 典型用法 |
|---|---|---|
| bmessage | bmessage "<expr>" | 在类或其子类上对方法设符号断点;expr 如 -[MyView setFrame:]、+[MyClass sharedInstance]、-[0xabcd1234 setFrame:];Chisel 会沿继承链找到实际实现该 selector 的类并设条件断点 [2][6] |
| wivar | wivar <object> <ivarName> | 对对象的实例变量设 watchpoint,该 ivar 被写入时断下 [2][6] |
5.5 响应链、约束与数据
| 命令 | 语法与说明 | 典型用法 |
|---|---|---|
| presponder | presponder [responder] | 从指定 responder 起向上打印 响应链 [2][6] |
| paltrace | paltrace [view] | 打印 Auto Layout 的调试 trace,默认 keyWindow [2][6] |
| alamborder / alamunborder | alamborder [--color=] [--width=] ;alamunborder | 给布局歧义的 view 加边框;需 raw-input [2][6] |
| pcurl | pcurl [--embed-data] <NSURLRequest> | 将 NSURLRequest 转成 curl 命令,便于在终端重放 [2][6] |
| pjson | pjson [--plain] <NSDictionary/NSArray> | 以 JSON 形式打印字典或数组 [2][6] |
5.6 命令速查表
| 场景 | 推荐命令 |
|---|---|
| 看当前界面视图树 | pviews |
| 看 ViewController 栈 | pvc |
| 按类名找 view 并标出 | fv <Regex> → border <addr> |
| 在 Mac 上看图/看 view | visualize <expr> |
| 临时隐藏某 view | hide <view> |
| 给 view 加边框 | border [选项] <view> |
| 对某类方法下断点 | bmessage "-[ClassName method:]" |
| 监视某对象 ivar 变化 | wivar <obj> <ivarName> |
| 看响应链 | presponder [responder] |
| 看约束问题 | paltrace;alamborder |
| 把请求变 curl | pcurl <request> |
六、关键概念图示与流程
6.1 Chisel 在调试会话中的位置
flowchart TB
subgraph 开发机
X[Xcode]
L[LLDB]
I[~/.lldbinit]
C[Chisel Python]
end
subgraph 目标
A[iOS/macOS App 进程]
end
X --> L
I -->|command script import| L
L --> C
C -->|SB API / 表达式求值| L
L --> A
A -->|结果/数据| L
L --> C
6.2 典型调试流程(视图问题)
sequenceDiagram
participant D as 开发者
participant L as LLDB
participant C as Chisel
participant A as App
D->>L: 断点命中 / 暂停
D->>L: pviews
L->>C: 执行 pviews
C->>A: 求值 keyWindow / 递归 description
A->>C: 返回字符串
C->>L: 输出到控制台
D->>L: fv MyView
C->>A: 查找并取地址
C->>D: 地址拷到剪贴板
D->>L: border (id)0x...
C->>A: 设置 layer border
A->>D: 界面显示边框
七、自定义命令与开发工作流
7.1 自定义命令接口(概念 [2])
Chisel 支持在本地添加自定义命令,供个人或团队使用。基本方式:
- 编写一个 Python 文件,定义继承自 fbchisellldbbase.FBCommand 的类,实现:
name(self):命令名description(self):简短描述run(self, arguments, options):命令逻辑;内部可调用lldb.debugger.HandleCommand()执行 LLDB 命令,或使用 SB API 获取 frame、变量、求值表达式等。
- 在
~/.lldbinit中先command script importChisel 的fbchisellldb.py,再调用 loadCommandsInDirectory 加载自定义命令所在目录 [2]。
示例(来自 Chisel README):打印 keyWindow 的 windowLevel:
#!/usr/bin/python
# 示例:自定义命令
import lldb
import fbchisellldbbase as fb
def lldbcommands():
return [ PrintKeyWindowLevel() ]
class PrintKeyWindowLevel(fb.FBCommand):
def name(self):
return 'pkeywinlevel'
def description(self):
return 'Print the window level of the key window.'
def run(self, arguments, options):
lldb.debugger.HandleCommand('p (CGFloat)[(id)[(id)[UIApplication sharedApplication] keyWindow] windowLevel]')
更多参数与选项可参考 Chisel 内置命令(如 border、pinvocation)的实现;官方 README 的 Custom Commands 与 Contributing 提供了贡献与扩展说明 [2]。
7.2 开发工作流(调试 Chisel 命令本身 [2])
- 写好命令脚本并放到某目录。
- 在
~/.lldbinit中配置loadCommandsInDirectory加载该目录。 - 启动 LLDB(或 Xcode 调试),断点命中后执行
command source ~/.lldbinit重新加载。 - 运行正在开发的命令,观察行为。
- 修改命令代码后,可使用
script reload(modulename)重载模块,无需重启 Xcode,再重复 4–5 直至满意。
八、伪代码与算法说明
8.1 pviews 类命令的递归描述(概念)
函数 print_view_hierarchy(view, depth, max_depth):
若 max_depth 已设定且 depth >= max_depth: 返回
缩进 = 根据 depth 生成
输出 缩进 + view 的 description(类名、frame 等)
for subview in view.subviews:
print_view_hierarchy(subview, depth + 1, max_depth)
实际实现中,Chisel 通过 LLDB 在目标进程中执行 Objective-C 表达式获取 keyWindow、rootViewController.view、subviews 等,并在本地拼接输出 [2][4]。
8.2 fv 查找视图(概念)
函数 find_view_matching(regex):
window = 求值 "[UIApplication sharedApplication].keyWindow"
results = 在 window 的子树中递归查找 view.class 与 regex 匹配的 view
若 results 非空:
将 results[0] 的地址写入剪贴板
返回 results[0]
否则 返回 nil
8.3 bmessage 符号断点(概念)
函数 bmessage(expr):
# expr 如 "-[MyView setFrame:]"
解析出 class(或 instance)与 selector
遍历 class 及其子类(或 instance 的类及其子类),查找实际实现该 selector 的类
在该类的实现上设置断点(或条件断点,使仅当 receiver 匹配时断下)
这样无需关心 setFrame: 是在 MyView 还是其子类中实现,都能在调用时断下 [2][6]。
九、应用场景与最佳实践
9.1 UI 层级与布局
- 先用 pviews 或 pvc 把握整体结构;再用 fv + border 或 mask 在界面上标出目标 view,确认 frame 与遮挡关系。
- Auto Layout 异常时用 paltrace 看约束冲突/歧义;用 alamborder 在歧义 view 上画边框便于对照。
9.2 图片与渲染
- 用 visualize 将 UIImage、CALayer、UIView 等导出到 Preview,检查内容与尺寸;配合 pviews 找到持有该 image 的 view。
- 若界面未刷新,可尝试 caflush 强制 Core Animation 刷新。
9.3 断点与数据
- 对「谁调用了某方法」不清晰时,用 bmessage 在该方法上设断点,运行到断点后看 bt 与 pinvocation(x86)等。
- 对 NSURLRequest 用 pcurl 转为 curl 在终端重放;对 NSDictionary/NSArray 用 pjson 查看结构。
9.4 官方文档与资源导读
| 资源 | 链接 | 说明 |
|---|---|---|
| Chisel 仓库 | GitHub facebook/chisel | 源码、README、CONTRIBUTING、安装与自定义命令 |
| 命令列表 | Chisel Wiki | 各命令的 Syntax、Arguments、Options 与实现文件 |
| LLDB 与 Chisel 综述 | Dancing in the Debugger — A Waltz with LLDB(objc.io #19) | Ari Grant 撰文,理解 LLDB 与 Chisel 的配合 [2][7] |
| LLDB Python API | LLDB Python API | SB API、自定义命令接口 |
| LLDB 自定义命令教程 | Writing Custom Commands | 官方扩展 LLDB 的教程 |
9.5 注意事项
- Chisel 命令依赖当前暂停的 target 与 frame;若未暂停或 target 不对,部分命令会失败。
- raw-input:多数命令的最后一个参数可直接写表达式(如 view 地址或类名),无需用引号包裹整条表达式;详见各命令
help。 - 真机调试时,visualize 等需要将数据从设备传回 Mac,大图或复杂层级可能略慢。
十、与其它调试工具的对比
| 维度 | Chisel | 纯 LLDB (po/bt/expr) | Lookin / Reveal |
|---|---|---|---|
| 形态 | LLDB 命令集合(Python) | 调试器内置命令与表达式 | 独立 Mac 应用 + App 内/网络 |
| 集成 | 配置 ~/.lldbinit 即可,无需改工程 | 无 | Lookin 需集成 LookinServer;Reveal 需 SDK 或 Loader |
| 视图层级 | pviews/pvc 文本输出;fv + border 等辅助 | 需手写 po 与递归表达式 | 图形化 2D/3D 树与属性面板 |
| 可视化 | visualize 在 Preview 中看图/view | 无 | 在 Lookin/Reveal 内直接看 |
| 断点/监视 | bmessage、wivar 等 | breakpoint set、watchpoint 等需手写 | 不提供 |
| 适用场景 | 断点调试时快速查层级、改显示、下断点、看数据 | 通用底层调试 | 专注 UI 结构审查与属性修改 |
Chisel 与 LLDB 内置能力互补:在保持「断点 + 控制台」工作流的前提下,用少量命令完成视图、VC、约束、请求等常见调试任务;与 Lookin/Reveal 相比,无需改工程、无需额外进程,但视图展示为文本与简单边框/遮罩,而非完整图形化树 [1][2][3]。
参考文献
[1] 掘金等. LLDB 命令库 Chisel 介绍(Facebook、Python、SB API、与 Derek Selander 对比).
[2] Facebook. Chisel. GitHub. github.com/facebook/ch…
[3] 西门桃桃. LLDB;LearnLLDB 等. Chisel 命令用法总结.
[4] LLDB. Python API;Writing Custom Commands;Implementing Standalone Scripts. lldb.llvm.org/python_api.… ;lldb.llvm.org/use/tutoria…
[5] LLDB. Scripting Bridge API. lldb.llvm.org/resources/s…
[6] Facebook. Chisel Wiki (Commands). github.com/facebook/ch…
[7] objc.io. Dancing in the Debugger — A Waltz with LLDB (Ari Grant). Issue 19. www.objc.io/issue-19/ll…