首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >rust-lldb 基础命令 30 分钟速成)

rust-lldb 基础命令 30 分钟速成)

作者头像
不吃草的牛德
发布2026-06-05 21:02:18
发布2026-06-05 21:02:18
20
举报
文章被收录于专栏:RustRust

你有没有经历过这样的崩溃时刻:程序跑着跑着,某个变量莫名其妙变成了 None,你以为它会是个 Some("hello"),结果它给了你一记现实的耳光?或者那该死的 Option::unwrap() 在凌晨两点把你从床上炸起来?

更惨的是,你 println! 大法一顿乱打,输出满屏都是,却还是找不到问题出在哪。等你好不容易找到,天都亮了。

如果你也经历过这种绝望,这篇文章就是为你准备的。

为什么要学 LLDB?

很多人觉得调试器太复杂,不如 println! 来得痛快。但 println! 有个致命问题:它只能告诉你「程序跑完了是什么样」,而调试器能告诉你「程序是怎么一步步变成这个样子的」。

想象一下,println! 就像是案发现场的照片,而 LLDB 是那台能回放整个过程的监控录像。哪个更有用,不言而喻。

而且 Rust 程序不像 C/C++ 那样,编译器会帮你挡住很多坑。Rust 虽然内存安全,但逻辑 Bug 它不管——Option 里面到底是 None 还是 Some,只有调试的时候才知道。

rust-lldb 是 Rust 官方提供的 wrapper(包装脚本),它会调用底层的 lldb,并额外加载 Rust 专用的 Python 脚本(pretty printers),让调试 Rust 程序时能更好地显示 Rust 的数据结构(Vec、String、Option、Enum 等)。

今天我们详细介绍下 rust-lldb 最实用的命令。


一、启动调试:让程序停下来

首先,用调试模式编译你的 Rust 程序:

代码语言:javascript
复制
cargo build

debug 模式下,Cargo 会自动生成调试符号,让 LLDB 能看懂你的代码。

然后启动 LLDB:

代码语言:javascript
复制
rust-lldb ./target/debug/your_program

进去之后,你面对的是 (lldb) 提示符。这时候程序还没跑,你得告诉它从哪开始。


二、断点命令 b / br

断点是调试的核心。没有断点,程序一口气跑完,你还调试个啥?

b -- Set a breakpoint using one of several shorthand formats.

设置断点

最常用的方式是按函数名打断点:

代码语言:javascript
复制
(lldb) b gdb_demo::main
(lldb) b your_module::your_function

或者按文件行号:

代码语言:javascript
复制
(lldb) b src/main.rs:42

小技巧:Rust 的函数名可能很长,不用打全。比如你的函数叫 my_module::process_data::parse_json,直接 b parse_json 往往就能命中。

查看所有断点

代码语言:javascript
复制
(lldb) br list
代码语言:javascript
复制
(lldb) br list
Current breakpoints:
1: name = 'main::main', locations = 0 (pending)

2: name = 'main::sum_vector', locations = 0 (pending)

3: name = 'sum_vector', locations = 1
  3.1: where = gdb_demo`gdb_demo::sum_vector::h9c0f29ea8f72f3be + 33 at main.rs:13:19, address = gdb_demo[0x0000000100002261], unresolved, hit count = 0 

4: name = 'gdb_demo::main', locations = 1
  4.1: where = gdb_demo`gdb_demo::main::h5f24b280e42565ff + 11 at main.rs:2:19, address = gdb_demo[0x000000010000233b], unresolved, hit count = 0 

5: file = 'src/main', line = 12, exact_match = 0, locations = 0 (pending)

6: file = 'src/main.rs', line = 12, exact_match = 0, locations = 1
  6.1: where = gdb_demo`gdb_demo::sum_vector::h9c0f29ea8f72f3be + 33 at main.rs:13:19, address = gdb_demo[0x0000000100002261], unresolved, hit count = 0 

7: name = 'list', locations = 0 (pending)

删除断点

代码语言:javascript
复制
(lldb) br delete 1    # 删除编号为1的断点
(lldb) br del         # 删除所有断点(谨慎使用)

禁用/启用断点

代码语言:javascript
复制
(lldb) br disable 1   # 暂时禁用,以后还能用
(lldb) br enable 1    # 重新启用

实战场景:你怀疑问题出在 process_request 函数,直接 b process_request。程序每次进这个函数都会停下来,你可以仔细检查输入参数对不对。


三、运行控制:让程序听你指挥

断点设置好了,现在让程序跑起来:

运行 r

代码语言:javascript
复制
(lldb) r
代码语言:javascript
复制
(lldb) r
Process 31292 launched: 'gdb_demo/target/debug/gdb_demo' (x86_64)
Process 31292 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x000000010000233b gdb_demo`gdb_demo::main::h5f24b280e42565ff at main.rs:2:19
   1    fn main() {
-> 2        let numbers = vec![1, 2, 3, 4, 5];
   3        let sum = sum_vector(&numbers);
   4        println!("Sum: {}", sum);
   5    
   6        let mut v = Vec::new();
   7        v.push(42);
Target 0: (gdb_demo) stopped.

程序会一直跑,直到撞上断点或者崩溃。

继续运行 c

停在断点后,想让它继续跑:

代码语言:javascript
复制
(lldb) c

单步执行 ns

这是最关键的两个命令:

  • n (next):执行当前行,如果当前行是函数调用,直接跑完整个函数,不进去
  • s (step):执行当前行,如果当前行是函数调用,进入函数内部

举个例子:

代码语言:javascript
复制
let result = calculate_total(items);  // 第42行
println!("{:?}", result);

如果你在 42 行停住了:

  • • 按 n,程序直接跑完 calculate_total,停在第 43 行
  • • 按 s,程序进入 calculate_total 函数内部,你可以一步步看它怎么算的

什么时候应该用哪个?

  • • 你不关心函数内部实现 → 用 n
  • • 你怀疑函数里面有 Bug → 用 s 进去看看

跳出函数 finish

进了函数之后,发现这里没问题,想赶紧出去:

代码语言:javascript
复制
(lldb) finish

程序会执行完当前函数,回到调用它的地方。


四、查看调用栈 btframe

查看调用栈 bt

程序崩溃了,或者你停在某处想知道「我是怎么到这里的」:

代码语言:javascript
复制
(lldb) bt

输出类似这样:

代码语言:javascript
复制
(lldb) bt
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x000000010000233b gdb_demo`gdb_demo::main::h5f24b280e42565ff at main.rs:2:19
    frame #1: 0x000000010000165e gdb_demo`core::ops::function::FnOnce::call_once::h6ddedf0346e35ea0((null)=0x0000000100002330, (null)=<unavailable>) at function.rs:250:5
    frame #2: 0x0000000100001ea1 gdb_demo`std::sys::backtrace::__rust_begin_short_backtrace::h75ca11478f906fc3(f=0x0000000100002330) at backtrace.rs:158:18
    frame #3: 0x0000000100001564 gdb_demo`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hd134b1645c19cbe8 at rt.rs:206:18
    frame #4: 0x000000010000c515 gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] core::ops::function::impls::_$LT$impl$u20$core..ops..function..FnOnce$LT$A$GT$$u20$for$u20$$RF$F$GT$::call_once::hf3ac46c438c11a2e at function.rs:287:21 [opt]
    frame #5: 0x000000010000c50f gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panicking::catch_unwind::do_call::h23b5400e9699d0e5 at panicking.rs:590:40 [opt]
    frame #6: 0x000000010000c50f gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panicking::catch_unwind::h455f28d6fd492edc at panicking.rs:553:19 [opt]
    frame #7: 0x000000010000c50f gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panic::catch_unwind::hfd8b99ae711af814 at panic.rs:359:14 [opt]
    frame #8: 0x000000010000c50f gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::hd633c016c2fbfa9f at rt.rs:175:24 [opt]
    frame #9: 0x000000010000c16a gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panicking::catch_unwind::do_call::h68b3f7dc5c3e0f1e at panicking.rs:590:40 [opt]
    frame #10: 0x000000010000c16a gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panicking::catch_unwind::h9d1581b2521a3be8 at panicking.rs:553:19 [opt]
    frame #11: 0x000000010000c16a gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 [inlined] std::panic::catch_unwind::h44c0363dfb2d6acc at panic.rs:359:14 [opt]
    frame #12: 0x000000010000c16a gdb_demo`std::rt::lang_start_internal::hba06c43da98f3f80 at rt.rs:171:5 [opt]
    frame #13: 0x0000000100001547 gdb_demo`std::rt::lang_start::h78b8d8026e105e75(main=0x0000000100002330, argc=1, argv=0x00007ff7bfefeaa8, sigpipe='\0') at rt.rs:205:5
    frame #14: 0x0000000100002508 gdb_demo`main + 24
    frame #15: 0x00007ff808bd3530 dyld`start + 3056

这告诉你:main 调用了 handlehandle 调用了 parse,现在停在 parse 的第 87 行。

关键信息:调用栈是从下往上看的,最下面是程序入口,最上面是当前位置。

切换栈帧 frame

查看第 1 层的详细信息:

代码语言:javascript
复制
(lldb) frame select 1

切换到第 1 层之后,你就可以查看那个层级的变量了。这在追查「参数传递过程」时特别有用。

代码语言:javascript
复制

(lldb) frame select 1
frame #1: 0x000000010000165e gdb_demo`core::ops::function::FnOnce::call_once::h6ddedf0346e35ea0((null)=0x0000000100002330, (null)=<unavailable>) at function.rs:250:5
   247  
   248      /// Performs the call operation.
   249      #[unstable(feature = "fn_traits", issue = "29625")]
-> 250      extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
   251  }
   252  
   253  mod impls {

五、打印变量 p / po / v

现在到了最激动人心的部分:看变量值

基本打印 p

代码语言:javascript
复制
(lldb) p some_variable
代码语言:javascript
复制

(lldb) n
Process 32258 stopped
* thread #1, name = 'main', queue = 'com.apple.main-thread', stop reason = step over
    frame #0: 0x00000001000023d3 gdb_demo`gdb_demo::main::h5f24b280e42565ff at main.rs:3:26
   1    fn main() {
   2        let numbers = vec![1, 2, 3, 4, 5];
-> 3        let sum = sum_vector(&numbers);
   4        println!("Sum: {}", sum);
   5    
   6        let mut v = Vec::new();
   7        v.push(42);
Target 0: (gdb_demo) stopped.
(lldb) p numbers
(alloc::vec::Vec<int, alloc::alloc::Global>) size=5 {
  [0] = 1
  [1] = 2
  [2] = 3
  [3] = 4
  [4] = 5
}

对于简单类型(整数、布尔值),直接就能看到值。

打印对象 po

po 是 "print object" 的意思,对于复杂类型会调用它的 DebugDisplay 实现:

代码语言:javascript
复制
(lldb) po my_struct

查看局部变量 v

代码语言:javascript
复制
(lldb) v

列出当前作用域所有局部变量,非常实用。

代码语言:javascript
复制

(lldb) v
(alloc::vec::Vec<int, alloc::alloc::Global>) numbers = size=5 {
  [0] = 1
  [1] = 2
  [2] = 3
  [3] = 4
  [4] = 5
}

六、命令速查表

命令

全称

作用

示例

b

break

设置断点

b main / b file.rs:10

br list

breakpoint list

查看所有断点

br list

br del

breakpoint delete

删除断点

br del 1

r

run

运行程序

r

c

continue

继续运行

c

n

next

单步执行(不进函数)

n

s

step

单步执行(进入函数)

s

finish

-

执行完当前函数

finish

bt

backtrace

查看调用栈

bt

frame select

-

切换栈帧

frame select 1

p

print

打印变量

p x

po

print object

打印对象(调用 Debug)

po my_struct

v

variable

查看所有局部变量

v

q

quit

退出 LLDB

q

建议保存这张表,调试的时候随时查阅。


七、Q&A 环节

Q1:LLDB 和 GDB 有什么区别?选哪个?

A:macOS 上 LLDB 是原生支持的,调试体验更好。Linux 上两者都可以。命令大同小异,学会一个,另一个很快就能上手。Rust 官方对 LLDB 的支持更完善一些。

Q2:为什么我的变量打印出来是乱码?

A:可能是编译时没开调试信息。确保用 cargo build 编译,不要加 --release。Release 模式会优化掉很多调试信息。

Q3:调试时程序运行很慢怎么办?

A:这是正常的。调试模式下程序性能会下降,因为要加载调试符号、响应断点等。如果只是看最终结果,可以用 release 模式运行;如果要调试,就耐心一点。

Q4:能在 IDE 里用这些命令吗?

A:当然可以。VS Code 的 CodeLLDB 插件、IntelliJ Rust 都集成了 LLDB。图形界面更直观,但命令行更灵活,建议两种都学会。

Q5:我的断点打不上,提示找不到符号怎么办?

A:检查函数名是否正确。Rust 的函数名可能被 mangling,用 lldbimage lookup 命令搜索:

代码语言:javascript
复制
(lldb) image lookup -r -n your_function

写在最后

掌握 LLDB 不是一蹴而就的事,但只要记住核心流程:

  1. 1. b 打断点
  2. 2. r 运行
  3. 3. n / s 单步执行
  4. 4. p / po 查看变量
  5. 5. bt 查调用栈

这五个命令覆盖了 80% 的调试场景。剩下的技巧,在实际使用中慢慢积累就好。

调试本质上是一种「逆向推理」——从错误现象出发,一步步追溯原因。LLDB 就是你的放大镜和记录仪。用好它,那些凌晨两点的崩溃,可能就变成下午两点的「小意思」了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust火箭工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么要学 LLDB?
  • 一、启动调试:让程序停下来
  • 二、断点命令 b / br
    • 设置断点
    • 查看所有断点
    • 删除断点
    • 禁用/启用断点
  • 三、运行控制:让程序听你指挥
    • 运行 r
    • 继续运行 c
    • 单步执行 n 和 s
    • 跳出函数 finish
  • 四、查看调用栈 bt 和 frame
    • 查看调用栈 bt
    • 切换栈帧 frame
  • 五、打印变量 p / po / v
    • 基本打印 p
    • 打印对象 po
    • 查看局部变量 v
  • 六、命令速查表
  • 七、Q&A 环节
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档