Rust Program + Some GDB

Project Creation

Start by creating a new project and test it.

$ cargo new rust-proj
$ cd rust-proj
$ cargo build
$ cargo run

Spicing Up the Program

To get a good program we want some variables to inspect as well as a loop to try out stepping. Since this will eventually be a program executed on a remote machine, a line of code to reveal the machine running the code is useful.

Here’s code for a function with a loop:

A lot of what’s happening here is tricking rustc so that the function (and its name) won’t dissapear during compilation. Another concern is that if the a call like fact(5) is made in the code, that will probably get pre-calculated and a constant with the resulting numbers takes its place in the code.

#[inline(never)]
fn fact(n: i32) -> i32
{
    let mut res: i32 = 1;
    let mut i: i32 = 1;
    while i <= n
    {
        res *= i;
        i   += 1;
    }
    res
}

In order to generate a random int, start by adding this dependency to your Cargo.toml:

rand = {version = "0.8.5", features = ["std", "std_rng"]}

Here’s the complete the main file:

use std::process::Command;

use rand::Rng;


fn main() 
{
    let mut n = 5;
    let mut s = fact(n);
    println!("fact({}) is {}!", n, s);

    n = rand::thread_rng().gen_range(0..12);
    s = fact(n);
    println!("fact({}) is {}!", n, s);

    let output = if cfg!(target_os = "windows")
    {
        Command::new("cmd")
            .args(["/C", "echo hello"])
            .output()
            .expect("failed to execute process")
    } 
    else
    {
        Command::new("sh")
            .arg("-c")
            .arg("uname -m")
            .output()
            .expect("failed to execute process")
    };
    
    println!("uname -m was {}", String::from_utf8_lossy(&output.stdout));
}


#[inline(never)]
fn fact(n: i32) -> i32
{
    let mut res: i32 = 1;
    let mut i: i32 = 1;
    while i <= n
    {
        res *= i;
        i   += 1;
    }
    res
}

In case you won’t be cross compiling you could replace the call to uname with something like ip a | grep "inet "; Anything printable with which you can quickly tell that you’re actually doing remote execution.

TL;DR

The remainder of this chapter is hand-holding through baby’s first gdb session. If you don’t think you need this experience, just skip to the next chapter.

Trying out gdb

For the final setup we’ll use lldb but for now let’s stick with normal gdb. Install it for your system, if you haven’t already.

Tip

Scroll down to the list of numbers. There you can click the numbers and have the corresponding annotated line get highlighted.

Here’s a a gdb session with much of the fluff cut out, marked by <...>.

sasja@ed800:~/rust-proj$ ls
Cargo.lock  Cargo.toml  src  target

1sasja@ed800:~/rust-proj$ gdb
GNU gdb (GDB) 15.1
<...>

2(gdb) file target/debug/rust-proj
Reading symbols from target/debug/rust-proj...
<...>

3(gdb) l
1   fn main() {
2       println!("Hello, world!");
3   }

4(gdb) b 2
Breakpoint 1 at 0x77c4: file src/main.rs, line 2.

5(gdb) run
Starting program: 
    /home/sasja/rust-proj/target/debug/rust-proj
<...>
6Enable debuginfod for this session? (y or [n])
<...>

Breakpoint 1, rust_proj::main () at src/main.rs:2
2       println!("Hello, world!");

7(gdb) c
Continuing.
Hello, world!
[Inferior 1 (process 117903) exited normally]

8(gdb) exit
sasja@ed800:~/rust-proj$
1
Start gdb, without any arguments.
2
Point to your binary.
3
l is for list. Gives you all the code, making it possible for you to know what the actual line numbers are.
4
b for breakpoint, sets a breakpoint on line 2, given the numbering shown above.
5
Start the program.
6
Just press ENTER to make this question go away.
7
The execution has reached a breakpoint. Using c will make the program resume execution.
8
Kill gdb.

Testing It Out

Compile the program and load it into gdb as before. To see that it’s all there you can run.

(gdb) set listsize 50
(gdb) list

If you just run list main you will get an empty response, or perhaps assembly code. This is because main is the word for the very first code that gets run, even before your program starts. In this case it’s a set of instructions added by rustc.

What you think of as main is actually rust_proj::main. To test this, run list rust_proj::fact.

Use the list below to get a grip on what’s possible to do.

List of gdb Example Commands

(gdb) set listsize 50
Show 50 lines at a time when using list.
(gdb) b 10
Set a breakpoint on line 10.
(gdb) i b
List information on all the current breakpoints.
(gdb) d 1-3
Delete breakpoints 1 to 3.
(gdb) dis 1-3 5
Temporarily disable breakpoints 1 to 3.
(gdb) d b
Delete all breakpoints.
(gdb) en 2 4
Enable the disabled breakpoints 2 and 4.
(gdb) c
Continue until next breakpoint.
(gdb) p i
Print the current value of the variable i.
(gdb) p my_struct.field
Print the value of the field field in variable my_struct.
(gdb) backtrace
View the current call stack.
(gdb) set var i = 5
Change the value of a variable.
(gdb) s
Step into function.
(gdb) n
Next line of code. Doesn’t go into function.
(gdb) fin
Steps out of a function.

Cross Compiling and Remote Debugging \(\rightarrow\)