在 Ubuntu 上排查 Rust 程序,最容易卡住的通常不是语法,而是“程序能不能带着足够信息被调起来”。这篇文章把准备工作、GDB 与 LLDB 的常用调试流程、VS Code 图形化配置,以及日志和回溯的补充手段拆开讲清楚。读完你可以判断:当前问题更适合命令行单步调试,还是直接用 IDE、日志和 backtrace 更省时间。
先把调试环境准备对
正式调试前,至少要确认两件事:系统里有可用的调试器,构建出来的二进制包含调试符号。没有调试符号,调试器就很难正确显示变量名、源代码行号和调用关系,后面很多操作都会打折扣。

Ubuntu 默认仓库里同时提供 GDB 和 LLDB,可以直接安装:
sudo apt install gdb lldb
如果你用 Cargo 的默认开发模式构建项目,通常已经会生成调试信息,产物位于 target/debug/。这也是大多数本地排查场景最省事的入口。
需要注意的是,发布模式下默认更偏向优化和体积控制。如果你想在 --release 构建中也保留调试符号,Rust 1.70 及以上版本可以这样做:
cargo build --release --debug
这一步的意义很直接:当问题只会在接近生产环境的编译条件下出现时,你仍然可以拿到可调试的二进制,而不必在“性能形态”和“可观察性”之间二选一。
命令行调试:GDB 和 LLDB 怎么选、怎么用
在 Ubuntu 上,GDB 和 LLDB 都能完成 Rust 的基础调试任务。区别主要在使用体验和对 Rust 语法、类型信息的友好程度。对很多小项目来说,GDB 足够;如果你更看重现代化体验,LLDB 往往更顺手。

用 rust-gdb 调试 Rust 程序
直接用 gdb 也能调,但更推荐通过 rust-gdb 启动。它会自动加载 Rust 相关的调试支持,省去手动配置步骤:
rust-gdb target/debug/your_program
进入之后,下面这些命令基本覆盖了日常排查需求:
break main:在main函数入口设置断点break src/main.rs:10:在源码第 10 行设置断点run:启动程序并运行到断点next或n:单步执行,跳过函数内部step或s:单步执行,进入函数内部print variable_name:查看变量值backtrace或bt:查看调用栈quit:退出调试器
如果你是在定位“程序在哪一层开始走偏”,GDB 这种从断点、单步到栈回溯的流程很直接,尤其适合先把问题范围缩小。
用 rust-lldb 获得更贴近 Rust 的体验
LLDB 同样建议通过 Rust 提供的包装命令启动:
rust-lldb target/debug/your_program
它和 GDB 的调试思路一致,但命令形式略有不同:
breakpoint set --name main:在main函数设断点breakpoint set --file src/main.rs --line 10:在源码第 10 行设断点run:启动程序next或n:单步跳过step或s:单步进入frame variable:查看当前栈帧中的全部变量print variable_name:查看单个变量bt:查看调用栈quit:退出
如果你的问题涉及较多类型检查、复杂栈帧或更频繁的交互操作,LLDB 通常会更适合长期使用。尤其是在 Rust 项目里,很多开发者会直接把它当作默认选择。
在 VS Code 里做图形化调试
如果你不是只偶尔排查一次,而是日常开发中经常需要断点、观察变量和查看调用栈,那么 VS Code 配合 rust-analyzer 往往是更高效的方案。它的优势不是“功能更多”,而是减少了在命令行里来回切换状态的成本。
先安装 rust-analyzer 扩展,然后在项目根目录创建 .vscode/launch.json,填入下面这份基于 LLDB 的配置:
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug Rust Program",
"program": "${workspaceFolder}/target/debug/your_program",
"args": [],
"cwd": "${workspaceFolder}",
"sourceLanguages": ["rust"]
}
]
}
配置完成后,直接在代码左侧点击行号区域就能设置断点,按 F5 启动调试。程序命中断点后,可以在界面里继续、单步跳过、单步进入,也能同步查看变量和调用栈。
这一方式特别适合两类场景:一类是刚接触 Rust 调试、对命令记忆负担比较敏感的开发者;另一类是需要反复观察多个变量状态、希望把上下文都放在一个窗口里的人。
调试器之外:日志、dbg! 与回溯怎么配合用
并不是每个问题都值得先开调试器。有些错误只是想快速确认某个值、某段逻辑有没有执行到,这时候日志和断言往往更省事,也更适合批量排查。

最直接的方法是 println!:
println!("变量值: {}", variable);
如果希望输出时顺带拿到文件名和行号,dbg! 通常更好用:
dbg!(variable);
对于稍大一些的项目,建议尽早换成结构化日志。文中的做法是配合 log 和 env_logger:
log::info!("状态: {:?}", state);
而当程序已经出现 panic 或异常退出时,环境变量 RUST_BACKTRACE 很关键。它可以直接把完整调用栈打印出来:
RUST_BACKTRACE=1 cargo run
如果你是手动处理错误,也可以在退出前把错误明确打印出来:
if let Err(e) = your_function() {
eprintln!("Error: {:?}", e);
std::process::exit(1);
}
这套方法的价值在于,它不依赖交互式调试环境,适合复现条件不稳定、需要快速增加观测点,或者先在线索不足时做第一轮筛查。
最后怎么选:按问题类型决定工具
把这些方法放到一起看,思路其实很清楚。
rust-gdb 和 rust-lldb 本质上都是在标准调试器外面加了一层 Rust 适配,优势是自动处理源码和调试符号加载,使用门槛比直接调用 gdb 或 lldb 更低。它们的核心命令体系保持不变,所以切换成本也不高。
如果项目规模不大,只是想快速断点、单步和看栈,GDB 完全能胜任;如果更看重对 Rust 的支持和整体体验,LLDB 通常是更稳妥的默认选项;而在日常开发里,VS Code 集成调试往往最省时间,尤其适合频繁设置断点和持续观察变量。
至于日志、dbg!、RUST_BACKTRACE,它们并不是调试器的替代品,而是补充视角。简单说,能靠输出快速定位的问题,就不一定非要先进入单步调试;而当输出已经不足以解释执行路径时,再切回 GDB、LLDB 或 VS Code,会更高效。









