lldb
LLDB Debugger (LLDB) 是一个开源、底层调试器(low level debugger), 具有 REPL (Read-Eval-Print Loop, 交互式解释器)、C++ 和 Python 插件, 位于 Xcode 窗口底部控制台中, 也有其他 IDE 加入了 LLDB 调试器, 如 CLion, 当然其也可以在 terminal 中使用。搜罗了一圈, 发现大多数教程还是可视化的使用, 本文就命令行使用 LLDB 调试程序做一个总结
# 基本概念
LLDB(Low Level Debugger)是 Xcode 内置的调试器,在调试程序时通过底部控制台输入命令与调试器交互。
重要:LLDB 控制台中直接输入变量名不是合法命令,会报
error: 'xxx' is not a valid command。打印变量必须使用po、p、expr等命令。
# 执行控制
| 命令 | 缩写 | 作用 |
|---|---|---|
continue | c | 继续执行到下一个断点 |
next | n | 单步跳过(不进入函数内部) |
step | s | 单步进入(进入函数内部) |
finish | — | 执行到当前函数返回,然后暂停 |
stepi | si | 单步执行一条机器指令 |
nexti | ni | 单步跳过一条机器指令 |
# 打印变量
# 常用打印命令
| 命令 | 作用 | 示例 |
|---|---|---|
po <expr> | 打印对象(print object),调用 description 方法 | po self、po data、po user.name |
p <expr> | 打印基本类型/结构体(print) | p count、p data.count、p view.frame |
expr <expr> | 执行表达式并打印结果 | expr data、expr count + 1 |
frame variable | fr v | 打印当前栈帧所有局部变量 |
frame variable <name> | fr v <name> | 打印指定局部变量 |
# po 和 p 的区别
po | p | |
|---|---|---|
| 全称 | print object | |
| 适用 | 引用类型对象(类、String、Data、NSArray 等) | 基本类型(Int、Bool、CGPoint、结构体) |
| 输出 | 调用对象的 description / debugDescription | 打印原始值或内存结构 |
| 示例输出 | 32 bytes | (Foundation.Data) data = 32 bytes { ... } |
# 打印可选值
po optionalVar // 打印可选值,nil 时输出 nil,有值时输出 Optional(...)
p optionalVar // 打印可选值的详细结构
po optionalVar! // 强制解包后打印(如果为 nil 会崩溃)
1
2
3
2
3
# 打印复杂对象的属性
po self.view.frame
po user.name
po array[0]
po dictionary["key"]
1
2
3
4
2
3
4
# 表达式执行(运行时修改)
expr(缩写 e)可以在调试时执行任意表达式,包括修改变量值:
expr count = 10 // 修改局部变量的值
expr self.title = "新标题" // 修改属性
expr array.append("new") // 调用方法
expr import UIKit // 导入框架(有时需要)
1
2
3
4
2
3
4
注意:修改后变量值只在内存中生效,不会写回源码,程序继续执行时使用修改后的值。
# 断点管理
# 设置断点
| 命令 | 作用 |
|---|---|
breakpoint set -n <函数名> | b <函数名>,按函数名设断点 |
breakpoint set -f <文件> -l <行号> | b <文件>:<行号>,按文件和行号设断点 |
breakpoint set -n "-[Class method:]" | 给 OC 方法设断点 |
breakpoint set -n "Class.method()" | 给 Swift 方法设断点 |
breakpoint set -r <正则> | 按正则匹配符号设断点 |
# 管理断点
| 命令 | 缩写 | 作用 |
|---|---|---|
breakpoint list | br l | 列出所有断点 |
breakpoint delete <编号> | br d <编号> | 删除指定断点 |
breakpoint delete | br d | 删除所有断点(会确认) |
breakpoint disable <编号> | br dis <编号> | 禁用指定断点 |
breakpoint enable <编号> | br en <编号> | 启用指定断点 |
breakpoint command add <编号> | br com add <编号> | 给断点添加自动执行的命令 |
# 条件断点
breakpoint set -n "functionName" --condition "count > 5"
# 只有 count > 5 时才会断住
1
2
2
# 断点执行命令(断住后自动执行)
breakpoint command add 1
> po self
> continue
> DONE
# 断点 1 断住后自动打印 self 然后继续执行
1
2
3
4
5
2
3
4
5
# 调用栈与线程
# 调用栈
| 命令 | 缩写 | 作用 |
|---|---|---|
thread backtrace | bt | 打印当前线程调用栈 |
thread backtrace all | bt all | 打印所有线程调用栈 |
up | — | 上移一帧(看调用者) |
down | — | 下移一帧(看被调用者) |
frame select <编号> | fr s <编号> | 跳转到指定栈帧 |
frame info | fr i | 查看当前栈帧信息 |
# 线程管理
| 命令 | 作用 |
|---|---|
thread list | 列出所有线程 |
thread select <编号> | 切换到指定线程 |
thread info | 查看当前线程信息 |
thread continue <编号> | 只继续指定线程(其他线程暂停) |
# 内存查看
| 命令 | 作用 | 示例 |
|---|---|---|
memory read <地址> | x <地址>,读取内存 | x 0x12345678 |
memory read -c <数量> -f <格式> <地址> | 按指定格式读取内存 | x -c 16 -f x 0x1234 |
memory write <地址> <值> | 修改内存 | memory write 0x1234 0xff |
image lookup -a <地址> | 查找地址对应的符号/代码 | image lookup -a 0x1234 |
image list | 列出所有加载的模块(二进制/动态库) |
# 内存格式说明
x 命令的格式参数 -f:
x:十六进制d:十进制u:无符号十进制o:八进制c:字符s:字符串i:指令(反汇编)
x -c 32 -f x 0x12345678 # 读取 32 字节,十六进制显示
x -c 100 -f s 0x12345678 # 读取 100 字节,字符串显示
x -i 0x12345678 # 反汇编指定地址
1
2
3
2
3
# 寄存器查看
| 命令 | 作用 |
|---|---|
register read | re r,读取所有寄存器 |
register read <寄存器名> | 读取指定寄存器,如 re r x0 |
register write <寄存器> <值> | 修改寄存器值 |
常见寄存器(ARM64):
x0-x7:函数参数和返回值sp:栈指针pc:程序计数器(当前执行地址)lr:链接寄存器(返回地址)
# 镜像(模块)查询
| 命令 | 作用 |
|---|---|
image list | 列出所有加载的模块 |
image lookup -n <符号名> | 按符号名查找 |
image lookup -a <地址> | 按地址查找对应的代码位置 |
image lookup -r -n <正则> | 按正则查找符号 |
image dump sections <模块> | 查看模块的段信息 |
image lookup -a 0x0000000102a3b1c0 # 查崩溃地址对应哪行代码
image lookup -n "ViewController.viewDidLoad" # 查方法地址
1
2
2
# 常见调试场景
# 1. 查看崩溃位置
程序崩溃后:
bt # 查看调用栈,找到崩溃位置
frame select 1 # 跳转到崩溃的栈帧
po self # 查看当前对象
po error # 查看错误信息
1
2
3
4
2
3
4
# 2. 修改变量值绕过逻辑
expr isLoggedIn = true # 临时修改登录状态
c # 继续执行
1
2
2
# 3. 条件断点排查循环问题
b ViewController.cellForRowAtIndexPath
breakpoint modify 1 --condition "indexPath.row == 5"
# 只有第 5 行才断住
1
2
3
2
3
# 4. 查看对象的属性
po self.view.subviews
po user.dictionary
po array.count
1
2
3
2
3
# 5. 调用方法
po self.view.description
expr [self doSomething] # OC 方法调用
expr self.doSomething() # Swift 方法调用
1
2
3
2
3
# 常见问题
# Q: 输入变量名报 error: 'xxx' is not a valid command
A: LLDB 把第一个词当成命令名,直接输入变量名不是命令。打印变量必须用 po 或 p:
po data # 正确
p data # 正确
data # 错误,不是有效命令
1
2
3
2
3
# Q: po 打印对象没有输出或输出不完整
A: 尝试用 p 代替,或者用 expr -O -- object(强制对象描述)。Swift 对象有时需要 po 才能正确打印。
# Q: 断点不生效
A:
- 确认代码编译的是 Debug 配置(不是 Release)
- 确认断点位置的代码确实会执行到
- 用
breakpoint list确认断点已启用 - 清理构建文件夹后重新编译:Product → Clean Build Folder
# Q: 打印 Swift 对象时报错
A: 有时需要先导入模块:
expr import UIKit
expr import MyAppName
po self
1
2
3
2
3
# Q: 怎么在 LLDB 中执行多行命令
A: 用 expr 后跟大括号:
expr {
let a = 1
let b = 2
print(a + b)
}()
1
2
3
4
5
2
3
4
5
# 命令速查表
| 操作 | 命令 |
|---|---|
| 继续 | c |
| 单步跳过 | n |
| 单步进入 | s |
| 跳出函数 | finish |
| 打印对象 | po <expr> |
| 打印值 | p <expr> |
| 执行表达式 | expr <expr> |
| 所有局部变量 | fr v |
| 调用栈 | bt |
| 所有线程调用栈 | bt all |
| 设断点 | b <函数名/文件:行号> |
| 断点列表 | br l |
| 删除断点 | br d <编号> |
| 读内存 | x <地址> |
| 查地址对应代码 | image lookup -a <地址> |
| 线程列表 | thread list |
| 切换线程 | thread select <编号> |
# 链接
上次更新: 2026/09/10, 20:52:09