LLDB 是 LLVM 项目的重要组成部分,是一个功能强大的现代调试器。作为 Xcode 的默认调试器,它专门用于调试 C、C++、Objective-C 和 Swift 程序,同时也可以在命令行环境中独立使用。
LLDB 提供了全面的调试能力,让你能够:
- 程序控制:启动、暂停、继续程序执行
- 断点管理:设置、修改、删除断点,支持条件断点
- 执行跟踪:单步执行、步入函数、步出函数
- 状态检查:查看变量值、调用栈、内存内容
- 动态修改:在运行时修改变量值、内存内容
- 深度分析:分析程序结构、符号表、寄存器状态
本文将详细介绍 LLDB 的核心概念、基本使用方法、常用命令以及高级调试技巧,帮助你掌握这个强大的调试工具。
实际使用示例
让我们通过一个简单的 C++ 程序来演示 LLDB 的使用。首先创建一个示例程序:
// example.cpp
#include <iostream>
#include <vector>
int calculate_sum(const std::vector<int>& numbers) {
int sum = 0;
for (int i = 0; i < numbers.size(); i++) {
sum += numbers[i];
}
return sum;
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
int result = calculate_sum(numbers);
std::cout << "Sum is: " << result << std::endl;
return 0;
}
编译程序(确保包含调试信息):
clang++ -std=c++20 -g example.cpp -o example
现在让我们使用 LLDB 来调试这个程序:
- 启动 LLDB:
lldb example
- 设置断点:
# 在 calculate_sum 函数处设置断点
(lldb) b calculate_sum
# 在 main 函数处设置断点
(lldb) b main
- 运行程序:
(lldb) run
- 程序会在 main 函数处停止,查看变量:
(lldb) frame variable
# 输出:
# (std::vector<int>) numbers = size=5 {
# [0] = 1
# [1] = 2
# [2] = 3
# [3] = 4
# [4] = 5
# }
- 单步执行:
(lldb) n # 执行下一行
(lldb) s # 进入 calculate_sum 函数
- 在 calculate_sum 函数中:
# 查看局部变量
(lldb) frame variable
# 输出:
# (int) sum = 0
# (const std::vector<int> &) numbers = size=5 {
# [0] = 1
# [1] = 2
# [2] = 3
# [3] = 4
# [4] = 5
# }
# (int) i = 0
# 设置条件断点,当 i == 2 时停止
(lldb) b example.cpp:8 -c "i == 2"
- 继续执行:
(lldb) c
- 查看调用栈:
(lldb) bt
# 输出:
# * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
# * frame #0: 0x0000000100003f50 example`calculate_sum(numbers=size=5) at example.cpp:8
# frame #1: 0x0000000100003f20 example`main at example.cpp:15
- 修改变量值:
(lldb) expr sum = 100
- 继续执行直到程序结束:
(lldb) c
LLDB 核心概念详解
为了更好地理解和使用 LLDB,我们需要深入理解其核心概念。这些概念构成了 LLDB 调试能力的基础。
1. Frame(帧)
Frame 是调用栈中的一个层级,代表程序执行过程中的一个函数调用。每个 frame 包含:
- 函数信息:当前执行的函数名和参数
- 局部变量:该函数作用域内的所有变量
- 程序计数器:当前执行的指令地址
- 返回地址:函数执行完毕后要返回的地址
# 查看当前帧的变量
(lldb) frame variable
# 简写:fr v
# 查看所有帧(调用栈)
(lldb) bt
# 输出示例:
# * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
# * frame #0: 0x0000000100003f50 example`calculate_sum(numbers=size=5) at example.cpp:8
# frame #1: 0x0000000100003f20 example`main at example.cpp:15
2. Thread(线程)
Thread 是程序执行的基本单位,代表一个独立的执行路径:
- 单线程程序只有一个主线程
- 多线程程序有多个线程同时执行
- 每个线程都有自己的调用栈
# 查看所有线程
(lldb) thread list
# 切换到指定线程
(lldb) thread select 2
3. Breakpoint(断点)
Breakpoint 是程序执行过程中的暂停点:
# 设置断点
(lldb) b calculate_sum
# 条件断点
(lldb) b example.cpp:8 -c "i == 2"
# 查看断点
(lldb) br l
4. Watchpoint(监视点)
Watchpoint 用于监控变量值的变化:
# 当变量值改变时停止
(lldb) watchpoint set variable sum
# 当变量被写入时停止
(lldb) watchpoint set write sum
5. Process(进程)
Process 是正在被调试的程序实例:
# 启动进程
(lldb) run
# 附加到运行中的进程
(lldb) process attach -p <pid>
6. Target(目标)
Target 代表要调试的程序,包含程序的二进制文件和调试信息:
# 创建目标
(lldb) target create example
# 查看目标信息
(lldb) target list
7. Module(模块)
Module 是程序加载的代码单元,通常是动态库或可执行文件:
# 查看所有模块
(lldb) image list
# 查看特定模块的符号
(lldb) image lookup -n function_name
8. Symbol(符号)
Symbol 是程序中的函数名、变量名等标识符:
# 查找符号
(lldb) image lookup -n main
# 查看符号表
(lldb) image lookup -a address
9. Image(镜像)
Image 是 LLDB 中表示可执行文件或动态库的概念,与 Module 概念相关:
# 列出所有加载的镜像
(lldb) image list
# 输出示例:
# [ 0] 12345678-1234-1234-1234-123456789ABC 0x0000000100000000 /path/to/example
# [ 1] 87654321-4321-4321-4321-CBA987654321 0x00007fff12345678 /usr/lib/libc++.1.dylib
# 查看特定镜像的详细信息
(lldb) image list -o example
# 查看镜像的符号表
(lldb) image lookup -v -n function_name
# 查看镜像的调试信息
(lldb) image lookup -v -a 0x0000000100001234
10. Memory(内存)
Memory 操作允许你直接查看和修改程序的内存内容:
# 读取内存内容
(lldb) memory read address
# 简写:x address
# 读取指定大小的内存
(lldb) memory read -s 4 -c 10 address
# -s 4: 每次读取 4 字节
# -c 10: 读取 10 次
# 以不同格式显示内存
(lldb) memory read -f x address # 十六进制
(lldb) memory read -f d address # 十进制
(lldb) memory read -f s address # 字符串
(lldb) memory read -f i address # 指令
# 写入内存
(lldb) memory write address value
# 搜索内存中的特定值
(lldb) memory find -s 4 -e 0x12345678 start_address end_address
11. Expression(表达式)
Expression 允许你在调试过程中执行 C/C++ 表达式:
# 执行表达式并显示结果
(lldb) expr variable_name
# 简写:p variable_name
# 执行复杂表达式
(lldb) expr (int)strlen("hello")
# 修改变量值
(lldb) expr variable_name = new_value
# 调用函数
(lldb) expr function_name(arguments)
# 创建临时变量
(lldb) expr int temp = 42
# 执行多行表达式
(lldb) expr {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
sum;
}
12. Variable(变量)
Variable 操作专门用于查看和操作变量:
# 查看当前帧的所有变量
(lldb) frame variable
# 简写:fr v
# 查看特定变量
(lldb) frame variable variable_name
# 查看变量的详细信息
(lldb) frame variable -L variable_name
# 查看变量的类型信息
(lldb) frame variable -T variable_name
# 查看全局变量
(lldb) target variable global_variable_name
# 查看静态变量
(lldb) target variable static_variable_name
13. Register(寄存器)
Register 操作允许你查看和修改 CPU 寄存器:
# 查看所有寄存器
(lldb) register read
# 简写:re r
# 查看特定寄存器
(lldb) register read rax
# 查看寄存器组
(lldb) register read --all
# 修改寄存器值
(lldb) register write rax 0x12345678
# 查看浮点寄存器
(lldb) register read --all --fp
概念之间的关系
Process (进程)
├── Thread (线程)
│ ├── Frame #0 (当前函数)
│ │ ├── Variable (局部变量)
│ │ └── Register (寄存器状态)
│ ├── Frame #1 (调用者)
│ └── ...
├── Module/Image (模块/镜像)
│ ├── Symbol (符号)
│ └── Memory (内存区域)
├── Breakpoint/Watchpoint (断点/监视点)
└── Expression (表达式执行环境)
常用命令
1. 启动调试
lldb program_name
2. 断点操作
# 设置断点
breakpoint set -f file.cpp -l 10
# 简写形式
b file.cpp:10
# 列出所有断点
breakpoint list
# 简写形式
br l
# 删除断点
breakpoint delete 1
# 简写形式
br del 1
# 条件断点
breakpoint set -f file.cpp -l 10 -c "variable > 5"
# 使用正则表达式设置断点*
breakpoint set -r "function_name"
# 保存断点配置
breakpoint write -f breakpoints.txt
3. 程序控制
# 运行程序
run
# 简写形式
r
# 继续执行
continue
# 简写形式
c
# 单步执行(不进入函数)
next
# 简写形式
n
# 单步执行(进入函数)
step
# 简写形式
s
# 退出当前函数
finish
4. 查看变量和内存
# 打印变量值
print variable_name
# 简写形式
p variable_name
# 查看变量类型
frame variable -L
# 简写形式
fr v -L
# 查看内存
memory read address
# 简写形式
x address
# 使用 watchpoint 监控变量变化
watchpoint set variable variable_name
5. 调用栈操作
# 查看调用栈
backtrace
# 简写形式
bt
# 切换到指定帧
frame select 2
# 简写形式
f 2
LLDB 脚本编程
LLDB 支持多种脚本语言,让你能够自动化调试过程、创建自定义命令和扩展调试功能。LLDB 内置了 Python 脚本支持,下面以 python 脚本为例,讲述如何使用脚本。
内联脚本
你也可以直接在 LLDB 命令行中执行 Python 代码:
# 执行单行 Python 代码
(lldb) script print("Hello from Python!")
# 执行多行 Python 代码
(lldb) script
>>> target = lldb.debugger.GetSelectedTarget()
>>> process = target.GetProcess()
>>> print(f"Process ID: {process.GetProcessID()}")
>>> DONE
# 使用 Python 表达式
(lldb) script print(lldb.debugger.GetSelectedTarget().GetExecutable().GetFilename())
自定义命令
1. 创建自定义命令
# custom_commands.py
import lldb
def print_call_stack(debugger, command, result, internal_dict):
"""自定义命令:美化显示调用栈"""
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
print("📞 Call Stack:")
for i in range(thread.GetNumFrames()):
frame = thread.GetFrameAtIndex(i)
function = frame.GetFunction()
line_entry = frame.GetLineEntry()
if function.IsValid():
func_name = function.GetName()
else:
func_name = "<unknown>"
if line_entry.IsValid():
file_spec = line_entry.GetFileSpec()
line_num = line_entry.GetLine()
print(f" {i:2d}: {func_name} at {file_spec.GetFilename()}:{line_num}")
else:
print(f" {i:2d}: {func_name}")
def analyze_variables(debugger, command, result, internal_dict):
"""自定义命令:分析变量类型和值"""
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
variables = frame.GetVariables(True, True, True, True)
print("🔍 Variable Analysis:")
for var in variables:
name = var.GetName()
value = var.GetValue()
type_name = var.GetType().GetName()
print(f" {name} ({type_name}): {value}")
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f custom_commands.print_call_stack pcs')
debugger.HandleCommand('command script add -f custom_commands.analyze_variables pva')
2. 使用自定义命令
# 加载自定义命令
(lldb) command script import custom_commands.py
# 使用美化调用栈命令
(lldb) pcs
# 使用变量分析命令
(lldb) pva
使用 lldb -s 命令执行脚本
lldb -s 命令允许你在启动 LLDB 时自动执行脚本文件,这是自动化调试的强大工具。
基本用法
# 启动 LLDB 并执行脚本文件
lldb -s script_file.lldb program_name
# 或者使用简写形式
lldb -S script_file.lldb program_name
脚本文件格式
LLDB 脚本文件使用 LLDB 命令语法,每行一个命令:
# debug_session.lldb
# 设置断点
breakpoint set -n main
breakpoint set -n calculate_sum
# 运行程序
run
# 查看变量
frame variable
# 单步执行
next
# 查看调用栈
bt
# 继续执行
continue
结合 Python 脚本
你可以在 LLDB 脚本中加载和执行 Python 脚本:
# debug_with_python.lldb
# 加载 Python 脚本
command script import debug_utils.py
# 设置断点
breakpoint set -n main
# 运行程序
run
# 使用 Python 脚本中的自定义命令
pvars
pcs
# 继续调试
continue
对应的 Python 脚本 debug_utils.py:
# debug_utils.py
import lldb
def print_variables(debugger, command, result, internal_dict):
"""打印所有变量"""
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
frame = thread.GetSelectedFrame()
variables = frame.GetVariables(True, True, True, True)
print("📋 Variables:")
for var in variables:
print(f" {var.GetName()}: {var.GetValue()}")
def print_call_stack(debugger, command, result, internal_dict):
"""美化显示调用栈"""
target = debugger.GetSelectedTarget()
process = target.GetProcess()
thread = process.GetSelectedThread()
print("📞 Call Stack:")
for i in range(thread.GetNumFrames()):
frame = thread.GetFrameAtIndex(i)
function = frame.GetFunction()
if function.IsValid():
print(f" {i}: {function.GetName()}")
else:
print(f" {i}: <unknown>")
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -f debug_utils.print_variables pvars')
debugger.HandleCommand('command script add -f debug_utils.print_call_stack pcs')
在 VSCode 中调试 C/C++ 代码
使用 CodeLLDB 在 VSCode 中调试程序
VSCode 提供了强大的 LLDB 调试支持,通过 CodeLLDB 扩展可以方便地进行 C/C++ 程序调试。以下是详细设置步骤:
- 安装 CodeLLDB 插件
- 创建
launch.json文件,并配置:
{
"version": "0.2.0",
"configurations": [
{
"name": "CodeLLDB Debug",
"type": "lldb",
"request": "launch",
"program": "executable file", // 必须用 -g 生成调试信息
}
]
}
使用 LLDB DAP 在 vscode 中 调试 C++
lldb-dap 是 LLDB 的调试适配器协议(Debug Adapter Protocol)实现,它允许你在支持 DAP 的编辑器中使用 LLDB 进行调试。以下是详细的使用指南:
- 安装 lldb-dap
- 配置 VSCode
- 创建
.vscode/launch.json文件:
{
"version": "0.2.0",
"configurations": [
{
"name": "lldb-dap Debug",
"type": "lldb-dap",
"request": "launch",
"program": "executable file",
}
]
}
- 配置
settings.json:
{
"lldb-dap.executable": "/path/to/lldb-dap",
}