从零介绍使用 LLDB debug RUST 程序的基本方法。
Rust 会使用 DWARF 格式在 binary 中嵌入调试信息,所以可以使用一些通用的调试工具,比如 GDB 和 LLDB。
DWARF 维基百科 地址:https://en.wikipedia.org/wiki/DWARF
Rust 提供了 rust-gdb
和 rust-lldb
两个命令用于调试,其相比原生的 gdb
和 lldb
添加了一些方便调试的脚本。
rustup
会安装 rust-lldb
,但不会安装 lldb
,需要自行安装。
例子程序
演示 debug 使用的是二分查找法:
/// binary search
///
/// # introduction
///
/// * params:
/// * nums: input search array
/// * target: search target
/// * return:
/// * None: not found
/// * Some: some index of target in array
///
pub fn binary_search<T>(nums: Vec<T>, target: T) -> Option<usize> where T: Ord{
let mut l = 0;
let mut r = nums.len();
if r <= 0 {
return None;
}
while r > 0 && l < r {
let mid = (l + r) / 2;
if target == nums[mid] {
return Some(mid);
} else if target > nums[mid] {
l = mid + 1;
} else {
r = mid;
}
}
None
}
use std::env;
fn main() {
println!("args: {:?}", env::args());
assert_eq!(
Some(4),
search::binary_search(vec![2, 3, 5, 8, 10, 18, 100], 10)
);
}
rust-lldb 命令行使用
使用 rust-lldb debug 二进制文件,那么二进制文件中必须包含调试相关的信息。cargo build
默认会以 debug 模式进行构建,所以含有用于调试的 symbol,需要注意的是 cargo install
会以 release 模式构建,需要 cargo install --debug
。
进入 LLDB REPL
通过 rust-lldb
命令加载可执行文件(或者在 REPL 中通过 file
载入),进入 LLDB 的 REPL。比如:
rust-lldb target/debug/<bin-name>
二进制文件的启动参数
使用
settings set — target.run-args "myarg"
给二进制文件添加一个启动参数 myarg
。
通过:
settings show target.run-args
可查看二进制文件的启动参数
例子代码会首先打印启动参数,所以设置完启动参数后,运行时,会将该参数打印出来:
⚠️ lldb repl 中直接输入 settings 命令可查看相关的子命令,包括清理调试参数,从文件读取参数,替换参数等。
断点
通过 b
命令设置断点, 对应 GDB 中break
命令的模拟, 并不是 lldb 中的 breakpoint 的别名(alias)。
常用场景:
- 方法开始设置断点:
breakpoint set -n <func name>
设置支持补全 - 文件某一行设置断点:
breakpoint set -f <文件路径> --line 15
在指定的行设置断点 - 查看已有的 breakpoint:
b
- 删除断点:
breakpoint delete [<断点编号>]
如果没有设置断点编号,那么默认删除所有断点 - 使断点失效但是不删除断点记录:
breakpoint disable [<断点编号>]
如果没有设置断点编号,那么默认 disable 所有断点 - 激活断点:
breakpoint enable [<断点编号>]
如果没有设置断点编号,那么默认 enable 所有断点
断点列表,例子如下:
(lldb) breakpoint list
Current breakpoints:
1: name = 'binary_search', locations = 0 (pending)
2: file = 'search.rs', line = 19, exact_match = 0, locations = 1
2.1: where = algorithms`algorithms::search::binary_search::ha09558f76f86b6d5 + 78 at search.rs:19:4, address = algorithms[0x00000001000023ce], unresolved, hit count = 0
4: name = 'binary_search', locations = 1 Options: disabled
4.1: where = algorithms`algorithms::search::binary_search::ha09558f76f86b6d5 + 14 at search.rs:14:16, address = algorithms[0x000000010000238e], unresolved, hit count = 0
例子中
- 断点 1 使用了-M 参数设置断点,断点找不到,处于 pending 状态;
- 断点 2 设置了
search.rs
文件的 19 行为断点(line = 19
),且设置成功了; - 断点 3 被删除了,所以不在断点列表;
- 断点 4 被 disable 了(
Options: disabled
),是基于函数名设置的断点(name = 'binary_search'
)
逻辑断点的 ID 为整数,并且其所在位置的父断点为 ID(两个之间以
.
连接,例如上例中的2.1
)。
例如:
断点命令
使用如下命令设置断点触发时执行的命令:
breakpoint command add <cmd-options> <breakpt-id>
例如:breakpoint command add 2.1
。
在<cmd-options>
中 可以添加--command
用来执行命令,或者--script
执行 python 脚本。
通过help breakpoint command add
查看更多帮助信息。
在断点被触发时,会自动运行设置的命令, 如果没有指定断点 id, 那么默认将 lldb 的命令加到最后一个断点。
例如:
条件断点
在设置断点时,配置-c
即-condition
,配置条件断点。
例如在 例子代码中,r >
3 时,自动打印堆栈信息:
(lldb) breakpoint set -c "r > 3" -f search.rs -l 19 -C bt
(lldb) breakpoint list
Current breakpoints:
1: file = 'search.rs', line = 16, exact_match = 0, locations = 1, resolved = 1, hit count = 1
1.1: where = algorithms`algorithms::search::binary_search::ha09558f76f86b6d5 + 56 at search.rs:16:7, address = 0x00000001000023b8, resolved, hit count = 1
2: file = 'search.rs', line = 19, exact_match = 0, locations = 1
Breakpoint commands:
bt
Condition: r > 3
2.1: where = algorithms`algorithms::search::binary_search::ha09558f76f86b6d5 + 78 at search.rs:19:4, address = 0x00000001000023ce, unresolved, hit count = 0
//运行二进制文件
(lldb) r
Process 59956 launched: '/Users/guangfuhe/Projects/rust/algorithms/target/debug/algorithms' (x86_64)
args: Args { inner: ["/Users/guangfuhe/Projects/rust/algorithms/target/debug/algorithms"] }
Process 59956 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00000001000023b8 algorithms`algorithms::search::binary_search::ha09558f76f86b6d5(nums=vec![2, 3, 5, 8, 10, 18, 100], target=10) at search.rs:16:7
13 pub fn binary_search<T>(nums: Vec<T>, target: T) -> Option<usize> where T: Ord{
14 let mut l = 0;
15 let mut r = nums.len();
-> 16 if r <= 0 {
17 return None;
18 }
19 while r > 0 && l < r {
Target 0: (algorithms) stopped.
(lldb) c
Process 59956 resuming
//到了断点自动触发的命令
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x00000001000023ce algorithms`algorithms::search::binary_search::ha09558f76f86b6d5(nums=vec![2, 3, 5, 8, 10, 18, 100], target=10) at search.rs:19:4
frame #1: 0x0000000100002928 algorithms`algorithms::main::h451514f7ba18c029 at main.rs:9:8
frame #2: 0x00000001000037d2 algorithms`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::ha58704225b4f5f6e at rt.rs:67:33
frame #3: 0x000000010000b108 algorithms`std::panicking::try::do_call::hb5f9c52a170af65b [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h7021adb3f4199cb2 at rt.rs:52:12 [opt]
frame #4: 0x000000010000b0fc algorithms`std::panicking::try::do_call::hb5f9c52a170af65b at panicking.rs:292 [opt]
frame #5: 0x000000010000c6ff algorithms`__rust_maybe_catch_panic at lib.rs:78:7 [opt]
frame #6: 0x000000010000b9be algorithms`std::rt::lang_start_internal::h032b1be013493933 [inlined] std::panicking::try::h9686b6ceb1ebe04f at panicking.rs:270:12 [opt]
frame #7: 0x000000010000b98b algorithms`std::rt::lang_start_internal::h032b1be013493933 [inlined] std::panic::catch_unwind::hf68219c633474123 at panic.rs:394 [opt]
frame #8: 0x000000010000b98b algorithms`std::rt::lang_start_internal::h032b1be013493933 at rt.rs:51 [opt]
frame #9: 0x00000001000037b2 algorithms`std::rt::lang_start::h9af99707a1bf11eb(main=&0x1000027f0, argc=1, argv=&0x7ffeefbff968) at rt.rs:67:4
frame #10: 0x0000000100002ab2 algorithms`main + 34
frame #11: 0x00007fff6668f7fd libdyld.dylib`start + 1
frame #12: 0x00007fff6668f7fd libdyld.dylib`start + 1
Process 59956 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x00000001000023ce algorithms`algorithms::search::binary_search::ha09558f76f86b6d5(nums=vec![2, 3, 5, 8, 10, 18, 100], target=10) at search.rs:19:4
16 if r <= 0 {
17 return None;
18 }
-> 19 while r > 0 && l < r {
20 let mid = (l + r) / 2;
21 if target == nums[mid] {
22 return Some(mid);
Target 0: (algorithms) stopped.
查看变量值
使用 **frame variable**
命令查看栈变量和对应的值
例如:
(lldb) breakpoint set -n binary_search
Breakpoint 1: where = algorithms`algorithms::search::binary_search::ha09558f76f86b6d5 + 14 at search.rs:14:16, address = 0x000000010000238e
(lldb) r
Process 47767 launched: '/Users/guangfuhe/Projects/rust/algorithms/target/debug/algorithms' (x86_64)
args: Args { inner: ["/Users/guangfuhe/Projects/rust/algorithms/target/debug/algorithms"] }
Process 47767 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010000238e algorithms`algorithms::search::binary_search::ha09558f76f86b6d5(nums=vec![2, 3, 5, 8, 10, 18, 100], target=10) at search.rs:14:16
11 /// * Some: some index of target in array
12 ///
13 pub fn binary_search<T>(nums: Vec<T>, target: T) -> Option<usize> where T: Ord{
-> 14 let mut l = 0;
15 let mut r = nums.len();
16 if r <= 0 {
17 return None;
Target 0: (algorithms) stopped.
(lldb) frame variable
(alloc::vec::Vec<int>) nums = vec![2, 3, 5, 8, 10, 18, 100]
(int) target = 10
(lldb)
**watch**
** 命令观察变量 变化: **watch set var r**
(其中 r 是源代码中的变量)。此处需要在调试时运行二进制文件,结合断点完成变量观察设置。**
例如:
单步调试
r
开始运行二进制程序
c
continue,继续执行程序,直到结束或下一个断点
- 选择线程:
thread select <id>
,在执行 r 命令的时候,会输出每个线程的线程 id。 - step in:
thread step-in
单步执行,进入子函数,对应 gdb 的step
命令或者s
命令。 - step over:
thread step-over
越过子函数,但子函数会执行。对应 gdb 的next
或者n
- step out :
thread step-out
当单步执行到子函数内时,用 step out 就可以执行完子函数余下部分,并返回到上一层函数。 对应 gdb 的finish
或者f
。
表达式求值
使用expr
对栈变量
例如:
(lldb) frame variable
(alloc::vec::Vec<int>) nums = vec![2, 3, 5, 8, 10, 18, 100]
(int) target = 10
(unsigned long) l = 0
(unsigned long) r = 7
(lldb) expr r>1
(bool) $0 = true
(lldb) expr r+1
(unsigned long) $1 = 8
自定义 lldb alias
设置断点:
- 全命令:
breakpoint set --file main.rs --line 18
, 其中--file
可以简写为-f
,--line
简写为-l
。 - 自定义 alias:
command alias bfl breakpoint set -f %1 -l %2
- 使用 alias 同样设置上面的断点:
bfl main.rs 18
取消 alias:unalias <alias-name>
, 例如unalias bfl
rust-lldb GUI 使用
进入 GUI: 在 lldb repl 中输入 gui
。
全局快捷键 | 说明 |
---|---|
tab | 选择 view |
h | 每个 view 不同的帮助信息 |
, | 上一页 |
. | 下一页 |
⬆️ | 选择上一个 |
⬇️ | 选择下一个 |
⬅️ | unexpand / 选择父组件 |
➡️ | expand |
💡在进入 GUI 前,可以先二进制程序运行起来,进入到 GUI 就能看到源代码。
参考