Debugging a code produced by rustc with GDB

I have a lack of details while debugging a code produced by rustc with GDB. Help me to understand how to get more details while debugging.

Let's start.

$ rust-gdb ./hello_world

(gdb) directory /usr/local/src/rustc-1.4.0/src
Source directories searched: /usr/local/src/rustc-1.4.0/src:/usr/local/lib/rustlib/etc:$cdir:$cwd

(gdb) list
1	fn main() {
2		println!("Hello World!");
3	}

(gdb) b 2
Breakpoint 1 at 0x538c: file main.rs, line 2.

(gdb) run
Breakpoint 1, hello_world::main () at main.rs:2
2		println!("Hello World!");

(gdb) s
hello_world::fmt::Arguments<'a>::new_v1 (pieces=&[&str](len: 1) = {...}, 
    args=&[core::fmt::ArgumentV1](len: 0)) at ../src/libcore/fmt/mod.rs:226
226	                  args: &'a [ArgumentV1<'a>]) -> Arguments<'a> {

There are no problems in previous log. It's just for understanding initial conditions. Note, there is successful step into the library code at "../src/libcore/fmt/mod.rs:226" which hints me that linked standard library was built with debug information.

(gdb) s
Hello World!
hello_world::main () at main.rs:3
3	}

First question. Why I doesn't stepped into all the code about printing (I'm talking about fn _print() from "/usr/local/src/rustc-1.4.0/src/libstd/io/stdio.rs:578") and instead came out to the line 3 of main.rs?

Seems like debug information available only for part of standard library. What I have to do to be able to step through the all standard library code?

(gdb) s
0x0000555555561965 in sys_common::unwind::try::try_fn::h4848098439110500489 ()

Second question. Why this code have no source information (like "../src/libcore/fmt/mod.rs:226" in the previous log)? How to make it available?

fn try_fn() located at "/usr/local/src/rustc-1.4.0/src/libstd/sys/common/unwind/mod.rs:162" but this information is missed.

... (skipped some similar lines)

(gdb) s
Single stepping until exit from function main,
which has no line number information.
__libc_start_main (main=0x555555559400 <main>, argc=1, argv=0x7fffffffdf68, 
    init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, 
    stack_end=0x7fffffffdf58) at libc-start.c:321
321	libc-start.c: No such file or directory.

Third question. How to find out which library source and it's version I have to install to be able to step into "libc-start.c:321"?

It looks like Arguments::new_v1 was inlined - note that the root of the path is hello_world, not core. Since you built your binary with debug info, it'll make it for new_v1 as well. I don't think the release builds of Rust are built with debug info included, which is why try_fn doesn't have a source line included.

The step command in GDB will skip over calls to functions that don't have associated debug info which is why the rest of the printing stuff is missed. Running stepi to step to the next instruction will jump into io::stdio::_print::h4554c712dfb1df6dF8g. If you build Rust from source, I think passing --enable-debug to ./configure will set it up to build with debuginfo enabled. I wouldn't be surprised if the macro expansion of println! confuses the debug info output as well.

You'll need to install debug info for glibc to step into __libc_start_main. The way to do this depends on what Linux distro you're using - it's in the glibc-debuginfo package on RHEL I think.

2 Likes

Thank you @sfackler!

I've built Rust with --enable-debug and plugged it to my system with multirust.
I've been able to step into the standard library source code with GDB after that.

How to don't step into macro code while debugging and how to see that macro code?

It's not convenient to blindly skip lines of in place expanded (while compilation) macro code. I've used "step" command (not "stepi") in the next example and falled into hidden expanded macro code instead to step over it.

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

(gdb) b 2
Breakpoint 1 at 0x7481: file main.rs, line 2.

(gdb) run
Starting program: /home/nick/pro/edu/hello_world/target/debug/hello_world 
Breakpoint 1, hello_world::main () at main.rs:3
3	}

It was an intro.

The problem:

(gdb) s
2	<std macros>: No such file or directory.

(gdb) s
hello_world::fmt::ArgumentV1<'a>::new<&str> (x=0x5555557f9390 <ref2627>, 
    f=0x55555555b570 <hello_world::fmt::&'a T.Display::fmt>) at src/libcore/fmt/mod.rs:192
192	                formatter: mem::transmute(f),

(gdb) s
193	                value: mem::transmute(x)

(gdb) s
189	                      f: fn(&T, &mut Formatter) -> Result) -> ArgumentV1<'b> {

and so on...

Such behavior is due to macro expansion in place of macro ("println!()"). There is no source code of expanded macro because it's intermediate and lost after compilation.

Original code:

fn main() {
    println!("Hello, {}!", "World");
}

Expanded code (rustc -Z unstable-options --pretty=expanded main.rs > main-expanded.rs):

#![feature(no_std, prelude_import)]
#![no_std]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
fn main() {
    ::std::io::_print(::std::fmt::Arguments::new_v1({
                                                        static __STATIC_FMTSTR:
                                                               &'static [&'static str]
                                                               =
                                                            &["Hello, ",
                                                              "!\n"];
                                                        __STATIC_FMTSTR
                                                    },
                                                    &match (&"World",) {
                                                         (__arg0,) =>
                                                         [::std::fmt::ArgumentV1::new(__arg0,
                                                                                      ::std::fmt::Display::fmt)],
                                                     }));
}

First problem: No such file or directory.

Second problem. While stepping inside expanded macros there is no source code link to IDE and I'm blinded.

Solution. It may be handy to save expanded macro code into separate files in the "target/src" directory if special option used while compilation. Macros from original source code should be converted to calls to separate expansions. In this case we may choose to step into or over it. Such option will help us to debug with more control and details.

This is not a complete solution, but be an okay workaround: If you are using RustDT, and want to skip a macro, instead of using F6, use Ctrl+R (Run to Cursor). So if you have this code:

    println!("Small example: {} {}", string, str_slice);
    func();

And the debug instruction pointer is just before println, and you wanna skip the whole println macro instructions, place the editor cursor before "func()", and then press Ctrl-R.

2 Likes

Good workaround but just workaround. I'll use it though. Thank you @bruno_medeiros!

Off topic:
How to highlight Rust code blocks on this forum?

Using markdown's fence syntax:

```rust
let x = 5;
```

gives

let x = 5;
1 Like

It's interesting that I can successfully step into macro_rules! source code. It's what I need. But it works not for all macros. It works for macros in such code (first line):

for _ in 0..10 {
    sum += 1;
}

I can step in and see source code for partial_ord_impl, zero_one_impl, forward_ref_binop, add_impl macros.

But it doesn't work for println! macro. Why? println! have it's source code at macros.rs as mentioned above macros has too.