Rust LLDB 调试入门指北

本文介绍工具 LLDB 的初步使用

Rust 会使用 DWARF 格式在 binary 中嵌入调试信息,所以可以使用一些通用的调试工具,比如 GDB 和 LLDB。Rust 提供了 rust-gdbrust-lldb 两个命令用于调试,其相比原生的 gdblldb 添加了一些方便调试的脚本

下面来初步的了解 rust-lldb 的使用,rustup 会安装 rust-lldb,但不会安装 lldb,需要自行安装

LLDB 的命令结构如下

<noun> <verb> [-options [option-value]] [argument [argument...]]  

本文所使用的代码如下:

fn binary_search(nums: Vec<i32>, target: i32) -> i32 {  
    let mut size = nums.len();

    if size == 0 {
        return -1;
    }

    let mut base = 0usize;

    while size > 1 {
        let half = size / 2;
        let mid = base + half;

        if nums[mid] <= target {
            base = mid;
        }
        size = size - half;
    }

    if nums[base] == target {
        return base as i32;
    }
    return -1;
}


fn main() {  
    assert_eq!(binary_search(vec![1, 4, 7, 10, 16, 19], 10), 3);
}

cargo build 默认会以 debug 模式进行构建,所以含有用于调试的 symbol,需要注意的是 cargo install 会以 release 模式构建,需要 cargo install --debug

通过 rust-lldb 命令加载可执行文件(或者在 REPL 中通过 file 载入),进入 LLDB 的 REPL。比如

rust-lldb target/debug/<bin-name> first_arg

启动时会显示

(lldb) settings set -- target.run-args  "first_arg"

字样,参数会存在 target.run-args 中,可以在 REPL 中重置命令行参数

(lldb) settings set target.run-args "arg1"
(lldb) settings show target.run-args
target.run-args (array of strings) =  
  [0]: "arg1"
(lldb) settings append target.run-args "arg2"                                                                                          
(lldb) settings show target.run-args
target.run-args (array of strings) =  
  [0]: "arg1"
  [1]: "arg2"

gui 命令可以进入 GUI,可以借助 voltron 获得更改的体验

通过 b 命令来设置断点。b 命令是对于 GDB 中 break 命令的模拟,并不是 LLDB 中的 breakpoint 的 alias。breakpoint set -n <func name> 设置支持补全,breakpoint set -f <文件路径>--line 15 在指定的行设置断点

(lldb) b binary_search                                                                                                                
Breakpoint 1: where = b`b::binary_search::hcb6e4fa332e0db67 + 13 at main.rs:2, address = 0x00000000000049bd  
(lldb) b
Current breakpoints:  
1: name = 'binary_search', locations = 1  
  1.1: where = b`b::binary_search::hcb6e4fa332e0db67 + 13 at main.rs:2, address = b[0x00000000000049bd], unresolved, hit count = 0    

2: name = 'binary_s', locations = 0 (pending)

3: name = 'binary_search', locations = 1  
  3.1: where = b`b::binary_search::hcb6e4fa332e0db67 + 13 at main.rs:2, address = b[0x00000000000049bd], unresolved, hit count = 0    
(lldb) breakpoint delete 2        
1 breakpoints deleted; 0 breakpoint locations disabled.  

通过 r 命令来运行程序

(lldb) r
There is a running process, kill it and restart?: [Y/n] y  
Process 12494 launched: '/root/T/b/target/debug/b' (x86_64)  
Process 12494 stopped  
* thread #1, name = 'b', stop reason = breakpoint 1.1
    frame #0: 0x00005555555589bd b`b::binary_search::hcb6e4fa332e0db67(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:2        
   1    fn binary_search(nums: Vec<i32>, target: i32) -> i32 {
-> 2        let mut size = nums.len();
   3
   4        if size == 0 {
   5            return -1;
   6        }
   7

通过 frame variable 可以看到当前栈中的变量

(lldb) frame variable
(alloc::vec::Vec<int>) nums = vec![1, 4, 7, 10, 16, 19]
(int) target = 10

通过 n 进行单步调试

(lldb) n
Process 20123 stopped  
* thread #1, name = 'b', stop reason = step over
    frame #0: 0x00005555555589df b`b::binary_search::hcb6e4fa332e0db67(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:4        
   1    fn binary_search(nums: Vec<i32>, target: i32) -> i32 {
   2        let mut size = nums.len();
   3
-> 4        if size == 0 {
   5            return -1;
   6        }
   7
(lldb) frame variable
(alloc::vec::Vec<int>) nums = vec![1, 4, 7, 10, 16, 19]
(int) target = 10
(unsigned long) size = 6

这都是模拟的 GDB 的命令族。LLDB 原生的则是

(lldb) thead select id
(lldb) thread step-in    // The same as gdb's "step" or "s"
(lldb) thread step-over  // The same as gdb's "next" or "n"
(lldb) thread step-out   // The same as gdb's "finish" or "f"

在断点处可以设置命令,比如直接打印堆栈

(lldb) breakpoint command add 1
Enter your debugger command(s). Type 'DONE' to end.  
> bt
> DONE

也可以直接通过 --command 参数设置

一个小例子,在 half == 3 条件成立的时候打印堆栈信息

(lldb) list main.rs:10
   10
   11       while size > 1 {
   12           let half = size / 2;
   13           let mid = base + half;
   14
   15           if nums[mid] <= target {
   16               base = mid;
   17           }
   18           size = size - half;
   19       }
   20
(lldb) breakpoint set -c "half == 3" -f main.rs -l 13 -C bt
Breakpoint 1: where = b`b::binary_search::hfce4ad2766a2fe8f + 166 at main.rs:13, address = 0x0000000000004a56  
(lldb) r
Process 31253 launched: '/root/T/b/target/debug/b' (x86_64)  
(lldb)  bt
* thread #1, name = 'b', stop reason = breakpoint 1.1
  * frame #0: 0x0000555555558a56 b`b::binary_search::hfce4ad2766a2fe8f(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:13                                                              
    frame #1: 0x0000555555558b90 b`b::main::he42792ea9d7cc77a at main.rs:29
    frame #2: 0x00005555555582c0 b`std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h36f6beb6917ae1e7 at rt.rs:74                                                                          
    frame #3: 0x0000555555561743 b`std::panicking::try::do_call::h4f8262c35e4e88a2 [inlined] std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::h10f59d0290367560 at rt.rs:59       
    frame #4: 0x0000555555561737 b`std::panicking::try::do_call::h4f8262c35e4e88a2 at panicking.rs:307                                                                                       
    frame #5: 0x000055555556ebfa b`__rust_maybe_catch_panic at lib.rs:102
    frame #6: 0x0000555555562246 b`std::rt::lang_start_internal::ha81f57a8465b4dcb [inlined] std::panicking::try::h8abdfbcaa376c6de at panicking.rs:286                                      
    frame #7: 0x000055555556220b b`std::rt::lang_start_internal::ha81f57a8465b4dcb [inlined] std::panic::catch_unwind::hcc1db352380c01f1 at panic.rs:398                                     
    frame #8: 0x000055555556220b b`std::rt::lang_start_internal::ha81f57a8465b4dcb at rt.rs:58
    frame #9: 0x0000555555558299 b`std::rt::lang_start::h1bce8d1d740917a0(main=&0x555555558b30, argc=1, argv=&0x7fffffffe3c8) at rt.rs:74                                                    
    frame #10: 0x0000555555558f1a b`main + 42
    frame #11: 0x00007ffff7dc9223 libc.so.6`__libc_start_main + 243
    frame #12: 0x000055555555817e b`_start + 46

Process 31253 stopped  
* thread #1, name = 'b', stop reason = breakpoint 1.1
    frame #0: 0x0000555555558a56 b`b::binary_search::hfce4ad2766a2fe8f(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:13                                                              
   10
   11       while size > 1 {
   12           let half = size / 2;
-> 13           let mid = base + half;
   14
   15           if nums[mid] <= target {
   16               base = mid;

可以通过 watch 来监控变量的变化

(lldb) list main.rs:10
   10
   11       while size > 1 {
   12           let half = size / 2;
   13           let mid = base + half;
   14
   15           if nums[mid] <= target {
   16               base = mid;
   17           }
   18           size = size - half;
   19       }
   20
(lldb) breakpoint set -f main.rs -l 11
Breakpoint 1: where = b`b::binary_search::hfce4ad2766a2fe8f + 88 at main.rs:11, address = 0x0000000000004a08  
(lldb) r
Process 1997 launched: '/root/T/b/target/debug/b' (x86_64)  
Process 1997 stopped  
* thread #1, name = 'b', stop reason = breakpoint 1.1
    frame #0: 0x0000555555558a08 b`b::binary_search::hfce4ad2766a2fe8f(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:11                                                              
   8
   9        let mut base = 0usize;
   10
-> 11       while size > 1 {
   12           let half = size / 2;
   13           let mid = base + half;
   14
(lldb) watch set var base
Watchpoint created: Watchpoint 1: addr = 0x7fffffffdec0 size = 8 state = enabled type = w  
    declare @ '/root/T/b/src/main.rs:9'
    watchpoint spec = 'base'
    new value: 0
(lldb) c
Process 1997 resuming

Watchpoint 1 hit:  
old value: 0  
new value: 3  
Process 1997 stopped  
* thread #1, name = 'b', stop reason = watchpoint 1
    frame #0: 0x0000555555558aa8 b`b::binary_search::hfce4ad2766a2fe8f(nums=vec![1, 4, 7, 10, 16, 19], target=10) at main.rs:18                                                              
   15           if nums[mid] <= target {
   16               base = mid;
   17           }
-> 18           size = size - half;
   19       }
   20
   21       if nums[base] == target {

expr 命令可以对表达式求值

lldb) frame variable  
(unsigned long) size = 24
(unsigned long) align = 4
(lldb) expr size == 24
(bool) $0 = true

Reference

LLDB Tutorial