Add debug to LLVM ir main function


I'm playing around with emitting LLVM ir code to learn and see how it loooks and all. Using the following simple code:

use std::vec::Vec;
fn f() -> u8 {
    3 + 7

fn main() {
    println!("{}", f());
    let x = 1 + 1;
    println!("{}", x);
    let _y = 2 - 1;
    let p = f();
    let a = [0, 1, 2];
    let mut vec = Vec::new();
    println!("{}", vec[0]);

and building it with

RUSTFLAGS="--emit llvm-ir -g" cargo build

it does produce a .ll file. When digging through the ir code I notice that the main function itself is not debugable:

 ; Function Attrs: nonlazybind
define i32 @main(i32 %0, i8** %1) unnamed_addr #10 {
  %2 = load volatile i8, i8* getelementptr inbounds ([34 x i8], [34 x i8]* @__rustc_debug_gdb_scripts_section__, i32 0, i32 0), align 1
  %3 = sext i32 %0 to i64
; call std::rt::lang_start
  %4 = call i64 @_ZN3std2rt10lang_start17hc764769363227138E(void ()* @_ZN8dump_ast4main17h162d53a07588ea53E, i64 %3, i8** %1)
  %5 = trunc i64 %4 to i32
  ret i32 %5

as can be seen, the main function does not point to a DISubprogram and have not a !dbg in the function. However, if digging in and looking at the calls made by the main function, those are all debugable. Ref:

; std::rt::lang_start
; Function Attrs: nonlazybind uwtable
define hidden i64 @_ZN3std2rt10lang_start17hc764769363227138E(void ()* nonnull %main, i64 %argc, i8** %argv) unnamed_addr #1 !dbg !88

define internal void @_ZN8dump_ast4main17h162d53a07588ea53E() unnamed_addr #1 personality i32 (i32, i32, i64, %"unwind::libunwind::_Unwind_Exception"*, %"unwind::libunwind::_Unwind_Context"*)* @rust_eh_personality !dbg !2178

(as my understanding, the hidden is the actual main while the internal is the eh personality).
So my question is why isn't the "main" function a separate DISubprogram so it can be debugable? Or is the some option that can produce a !dbg main function?

Thank you!

That main function is not the fn main from your program. It's just a shim that is invoked by the libc's startup code, and in turn calls the initialization routine of the Rust runtime library, which will finally call your fn main after it's done.

This shim is not debuggable because it is not created from source code. It is injected by the compiler.


Wow, little did I know! Thank you for this great explanation. This might be a dumb question, but why wouldn't it be debuggable just because the compiler injects the code? It's still part of the compiled code?

Debuginfo is used to map the generated LLVM IR and machine code back to the source code from where it originated. In this case, there is no source code it could be mapped to.

"Not debuggable" here just means you won't be able to inspect local variables and step through source lines, but debuggers like GDB can still inspect CPU registers and memory, as well as give you a backtrace, even without any debuginfo being present.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.