Skip to content

1.71: Bad DW_TAG_lexical_block; gdb breaks on method before self is initialized #130003

Closed
@apodolsk

Description

@apodolsk

I tried this code:

struct Foo {
    a: usize,
}

impl Foo {
    #[inline(never)]
    fn get_a(&self) -> Option<usize> {
        Some(self.a)
    }

    #[inline(never)]
    fn var_return(&self) -> usize {
        let r = self.get_a().unwrap();
        r
    }

    #[inline(never)]
    fn var_return_opt_unwrap(&self) -> Option<usize> {
        let r = self.get_a().unwrap();
        Some(r)
    }

    #[inline(never)]
    fn var_return_opt_match(&self) -> Option<usize> {
        let r = match self.get_a() {
            None => return None,
            Some(a) => a,
        };
        Some(r)
    }

    #[inline(never)]
    fn var_return_opt_try(&self) -> Option<usize> {
        let r = self.get_a()?;
        Some(r)
    }
}

fn main() {
    let f1 = Foo{ a: 1 };
    let f2 = Foo{ a: 1 };
    f1.var_return();
    f1.var_return_opt_unwrap();
    f1.var_return_opt_match();
    f2.var_return_opt_try();
}

Setting a breakpoint on var_return_opt_try in rust-gdb results in gdb stopping before self's stack slot is initialized, and gdb dereferences it to print wrong values based on self:

~/code/scrap/rust/debuginfo: cargo clean; RUSTFLAGS=-Zmir-enable-passes=-SingleUseConsts cargo +nightly build         
     Removed 0 files                    
   Compiling debuginfo v0.1.0 (/home/vich/code/scrap/rust/debuginfo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
~/code/scrap/rust/debuginfo: rust-gdb target/debug/debuginfo -batch -ex "b debuginfo::Foo::var_return_opt_try" -ex "r" -ex "disas" -ex "p self" -ex "n" -ex "p self"
Breakpoint 1 at 0x33d68: file std/src/panicking.rs, line 862.
Breakpoint 2 at 0x13c24: file src/main.rs, line 34.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 2, debuginfo::Foo::var_return_opt_try (self=0x7fffffffe0f8) at src/main.rs:34
34	        let r = self.get_a()?;
Dump of assembler code for function _ZN9debuginfo3Foo18var_return_opt_try17hed204febe8a0db96E:
   0x0000555555567c20 <+0>:	sub    $0x38,%rsp
=> 0x0000555555567c24 <+4>:	mov    %rdi,0x28(%rsp)
   0x0000555555567c29 <+9>:	call   0x555555567ae0 <debuginfo::Foo::get_a>
   0x0000555555567c2e <+14>:	mov    %rax,%rdi
   0x0000555555567c31 <+17>:	mov    %rdx,%rsi
   0x0000555555567c34 <+20>:	call   0x555555567d20 <core::option::{impl#39}::branch<usize>>
   0x0000555555567c39 <+25>:	mov    %rax,0x18(%rsp)
   0x0000555555567c3e <+30>:	mov    %rdx,0x20(%rsp)
   0x0000555555567c43 <+35>:	cmpq   $0x0,0x18(%rsp)
   0x0000555555567c49 <+41>:	jne    0x555555567c65 <debuginfo::Foo::var_return_opt_try+69>
   0x0000555555567c4b <+43>:	mov    0x20(%rsp),%rax
   0x0000555555567c50 <+48>:	mov    %rax,0x30(%rsp)
   0x0000555555567c55 <+53>:	mov    %rax,0x10(%rsp)
   0x0000555555567c5a <+58>:	movq   $0x1,0x8(%rsp)
   0x0000555555567c63 <+67>:	jmp    0x555555567c74 <debuginfo::Foo::var_return_opt_try+84>
   0x0000555555567c65 <+69>:	call   0x555555567d00 <core::option::{impl#40}::from_residual<usize>>
   0x0000555555567c6a <+74>:	mov    %rax,0x8(%rsp)
   0x0000555555567c6f <+79>:	mov    %rdx,0x10(%rsp)
   0x0000555555567c74 <+84>:	mov    0x8(%rsp),%rax
   0x0000555555567c79 <+89>:	mov    0x10(%rsp),%rdx
   0x0000555555567c7e <+94>:	add    $0x38,%rsp
   0x0000555555567c82 <+98>:	ret
End of assembler dump.
$1 = (*mut debuginfo::Foo) 0x7fffffffe0f8
35	        Some(r)
$2 = (*mut debuginfo::Foo) 0x7fffffffe100
~/code/scrap/rust/debuginfo: rust-gdb --version
GNU gdb (GDB) 15.1

Note the incorrect different values of self as I step past initialization. The breakpoint should trigger just after that mov, not before it. The other functions in that program indeed do it correctly. Oddly, it seems like the ? operator is necessary for my repro, though maybe that has something to do with the hidden residual var.

(Note that f1 vs f2 is necessary to actually avoid accidental correct values of self due to stack reuse, so the other functions can't print wrong self as written. But I'm just looking at where the breakpoint hits in disassembly.)

Here's relevant llvm-dwarfdump from this binary, with a DW_TAG_lexical_block pointing at at the mov (0x...24) rather than the call:

0x0000049a:   DW_TAG_subprogram
                DW_AT_low_pc    (0x0000000000013c20)
                DW_AT_high_pc   (0x0000000000013c83)
                DW_AT_frame_base        (DW_OP_reg7 RSP)
                DW_AT_specification     (0x0000017b "_ZN9debuginfo3Foo18var_return_opt_try17hed204febe8a0db96E")

0x000004ad:     DW_TAG_formal_parameter
                  DW_AT_location        (DW_OP_fbreg +40)
                  DW_AT_name    ("self")
                  DW_AT_decl_file       ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
                  DW_AT_decl_line       (33)
                  DW_AT_type    (0x00000324 "debuginfo::Foo *")

0x000004bb:     DW_TAG_lexical_block
                  DW_AT_ranges  (0x00000060
                     [0x0000000000013c24, 0x0000000000013c29)
                     [0x0000000000013c65, 0x0000000000013c74))

0x000004c0:       DW_TAG_variable
                    DW_AT_location      (DW_OP_fbreg +7)
                    DW_AT_name  ("residual")
                    DW_AT_decl_file     ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
                    DW_AT_decl_line     (34)
                    DW_AT_type  (0x0000027d "core::option::Option<core::convert::Infallible>")

0x000004ce:       NULL

0x000004cf:     DW_TAG_lexical_block
                  DW_AT_low_pc  (0x0000000000013c55)
                  DW_AT_high_pc (0x0000000000013c63)

0x000004dc:       DW_TAG_variable
                    DW_AT_location      (DW_OP_fbreg +48)
                    DW_AT_name  ("r")
                    DW_AT_alignment     (1)
                    DW_AT_decl_file     ("/home/vich/code/scrap/rust/debuginfo/src/main.rs")
                    DW_AT_decl_line     (34)
                    DW_AT_type  (0x000001e6 "usize")

0x000004eb:       NULL

0x000004ec:     NULL

0x000004ed:   NULL

This looks pretty related to #128945 and #113819.

Like 113819, the repro hits on 1.71 but not 1.70. I'm filing separately because, unlike it, disabling SingleUseConsts doesn't seem to fix the breakpoint. I'm not 100% sure I'm disabling it right - you can see my cargo build command above.

Meta

rustc --version --verbose:

rustc 1.83.0-nightly (4ac7bcbaa 2024-09-04)
binary: rustc
commit-hash: 4ac7bcbaad8d6fd7a51bdf1b696cbc3ba4c796cf
commit-date: 2024-09-04
host: x86_64-unknown-linux-gnu
release: 1.83.0-nightly
LLVM version: 19.1.0
Backtrace

<backtrace>

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-LLVMArea: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues.A-debuginfoArea: Debugging information in compiled programs (DWARF, PDB, etc.)C-bugCategory: This is a bug.WG-debuggingWorking group: Bad Rust debugging experiences

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions