Skip to content

Unavailable variables under the Visual Studio Debugger at the function end #119090

Open
@xTachyon

Description

@xTachyon

Rust version:

cargo 1.76.0-nightly (1aa9df1a5 2023-12-12)
release: 1.76.0-nightly
commit-hash: 1aa9df1a5be205cce621f0bc0ea6062a5e22a98c
commit-date: 2023-12-12
host: x86_64-pc-windows-msvc
libgit2: 1.7.1 (sys:0.18.1 vendored)
libcurl: 8.5.0-DEV (sys:0.4.70+curl-8.5.0 vendored ssl:Schannel)
os: Windows 10.0.19045 (Windows 10 Pro) [64-bit]

Rust code:

pub fn main() {
    let x = 10;
    let y = x + 10;
}

How it looks now:
image
The y variable never appears in the window in this case.

I slightly modified the LLVM output in order to be easily tested with clang (clang main.ll -g -gcodeview).

(Semi) Original LLVM IR

; ModuleID = 'example.1d7810305fd8544f-cgu.0'
source_filename = "example.1d7810305fd8544f-cgu.0"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc"

@alloc_f58f411d2051cb7654f7b8a078d6577a = private unnamed_addr constant <{ [69 x i8] }> <{ [69 x i8] c"/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse/example.rs" }>, align 1
@alloc_2aef29fe3608c713caacd9ceb2662f82 = private unnamed_addr constant <{ ptr, [16 x i8] }> <{ ptr @alloc_f58f411d2051cb7654f7b8a078d6577a, [16 x i8] c"E\00\00\00\00\00\00\00\03\00\00\00\0D\00\00\00" }>, align 8
@str.0 = internal unnamed_addr constant [28 x i8] c"attempt to add with overflow"

; example::main
; Function Attrs: uwtable
define void @main() unnamed_addr #0 !dbg !6 {
start:
  %y.dbg.spill = alloca i32, align 4
  %x.dbg.spill = alloca i32, align 4
  store i32 10, ptr %x.dbg.spill, align 4, !dbg !20
  call void @llvm.dbg.declare(metadata ptr %x.dbg.spill, metadata !12, metadata !DIExpression()), !dbg !20
  %0 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 10), !dbg !21
  %_3.0 = extractvalue { i32, i1 } %0, 0, !dbg !21
  %_3.1 = extractvalue { i32, i1 } %0, 1, !dbg !21
  %1 = call i1 @llvm.expect.i1(i1 %_3.1, i1 false), !dbg !21
  br i1 %1, label %panic, label %bb1, !dbg !21

bb1:                                              ; preds = %start
  store i32 %_3.0, ptr %y.dbg.spill, align 4, !dbg !21
  call void @llvm.dbg.declare(metadata ptr %y.dbg.spill, metadata !17, metadata !DIExpression()), !dbg !22
  ret void, !dbg !23

panic:                                            ; preds = %start
; call core_panicking_panic
  call void @core_panicking_panic(ptr align 1 @str.0, i64 28, ptr align 8 @alloc_2aef29fe3608c713caacd9ceb2662f82) #4, !dbg !21
  unreachable, !dbg !21
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare { i32, i1 } @llvm.sadd.with.overflow.i32(i32, i32) #1

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none)
declare i1 @llvm.expect.i1(i1, i1) #2

; core_panicking_panic
; Function Attrs: cold noinline noreturn uwtable
define void @core_panicking_panic(ptr align 1, i64, ptr align 8) unnamed_addr #3 {
start:
  ret void;
}

attributes #0 = { uwtable "target-cpu"="x86-64" }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #2 = { nocallback nofree nosync nounwind willreturn memory(none) }
attributes #3 = { cold noinline noreturn uwtable "target-cpu"="x86-64" }
attributes #4 = { noreturn }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!llvm.dbg.cu = !{!4}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 2, !"CodeView", i32 1}
!2 = !{i32 2, !"Debug Info Version", i32 3}
!3 = !{!"rustc version 1.76.0-nightly (6a6287132 2023-12-17)"}
!4 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !5, producer: "clang LLVM (rustc version 1.76.0-nightly (6a6287132 2023-12-17))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false)
!5 = !DIFile(filename: "main.rs", directory: "/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse")
!6 = distinct !DISubprogram(name: "main", linkageName: "main", scope: !8, file: !7, line: 1, type: !9, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !4, templateParams: !19, retainedNodes: !11)
!7 = !DIFile(filename: "main.rs", directory: "/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse", checksumkind: CSK_SHA256, checksum: "b560e17d45bb87e6939526cf009c50f61f7ef727de0fede23b15cee6d2ab18d6")
!8 = !DINamespace(name: "example", scope: null)
!9 = !DISubroutineType(types: !10)
!10 = !{null}
!11 = !{!12, !17}
!12 = !DILocalVariable(name: "x", scope: !13, file: !7, line: 2, type: !14, align: 4)
!13 = distinct !DILexicalBlock(scope: !6, file: !7, line: 2)
!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "i32", file: !15, baseType: !16)
!15 = !DIFile(filename: "<unknown>", directory: "")
!16 = !DIBasicType(name: "__int32", size: 32, encoding: DW_ATE_signed)
!17 = !DILocalVariable(name: "y", scope: !18, file: !7, line: 3, type: !14, align: 4)
!18 = distinct !DILexicalBlock(scope: !13, file: !7, line: 3)
!19 = !{}
!20 = !DILocation(line: 2, scope: !13)
!21 = !DILocation(line: 3, scope: !13)
!22 = !DILocation(line: 3, scope: !18)
!23 = !DILocation(line: 4, scope: !6)

I played with it a little and tried a similar code in C++ and see what clang generates, and it seems like the difference is the addition of DILexicalBlock in the Rust version. Removing them seems to make the debugging work as expected.

Diff with `DILexicalBlock` removed

diff --git a/exhibit_a.ll b/exhibit_b.ll
index 87d5ab8..942fab4 100755
--- a/exhibit_a.ll
+++ b/exhibit_b.ll
@@ -70,15 +70,13 @@ attributes #4 = { noreturn }
 !9 = !DISubroutineType(types: !10)
 !10 = !{null}
 !11 = !{!12, !17}
-!12 = !DILocalVariable(name: "x", scope: !13, file: !7, line: 2, type: !14, align: 4)
-!13 = distinct !DILexicalBlock(scope: !6, file: !7, line: 2)
+!12 = !DILocalVariable(name: "x", scope: !6, file: !7, line: 2, type: !14, align: 4)
 !14 = !DIDerivedType(tag: DW_TAG_typedef, name: "i32", file: !15, baseType: !16)
 !15 = !DIFile(filename: "<unknown>", directory: "")
 !16 = !DIBasicType(name: "__int32", size: 32, encoding: DW_ATE_signed)
-!17 = !DILocalVariable(name: "y", scope: !18, file: !7, line: 3, type: !14, align: 4)
-!18 = distinct !DILexicalBlock(scope: !13, file: !7, line: 3)
+!17 = !DILocalVariable(name: "y", scope: !6, file: !7, line: 3, type: !14, align: 4)
 !19 = !{}
-!20 = !DILocation(line: 2, scope: !13)
-!21 = !DILocation(line: 3, scope: !13)
-!22 = !DILocation(line: 3, scope: !18)
+!20 = !DILocation(line: 2, scope: !6)
+!21 = !DILocation(line: 3, scope: !6)
+!22 = !DILocation(line: 3, scope: !6)
 !23 = !DILocation(line: 4, scope: !6)
\ No newline at end of file

With the above changes:
image

I'm sure that DILexicalBlock makes a lot of sense, and clang seems to be generating them too when entering a new scope, but right now it seems like they do more harm than good. I'm not sure what the good solution for this would be, but I've been hit by this problem more times than I can remember debugging through rust code.

Can this be fixed somehow?

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-debuginfoArea: Debugging information in compiled programs (DWARF, PDB, etc.)C-bugCategory: This is a bug.E-needs-investigationCall for partcipation: This issues needs some investigation to determine current statusWG-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