- 
                Notifications
    You must be signed in to change notification settings 
- Fork 15k
Description
cc @jmorse
When Dead Store Elimination (DSEPass) eliminates stores into an object, this is tracked in DWARF debuginfo such that the debugger can still show the logical state of the object; but when the debugger looks at a pointer to the object (in particular, when a pointer to a local variable is passed to an inlined subroutine), when the debugger follows this pointer, it will potentially see uninitialized memory.
This might be related to #38112 (comment) , which is also about DSE causing issues with debugging.
Reproducer:
jannh@horn:~/test$ cat debug-dse-pointer-test.c
struct s {
  int a;
  int b;
};
int blah();
int bar(struct s*);
__attribute__((always_inline)) inline int baz(struct s *s) {
  blah();
  s->a = s->a + 1;
  s->b = s->b - 1;
  return bar(s);
}
__attribute__((noinline)) int foo(int a, int b) {
  struct s s = {.a = a, .b = b};
  return baz(&s);
}
int main(void) {
  foo(1, 2);
  return 0;
}
jannh@horn:~/test$ cat debug-dse-pointer-test2.c
struct s {
  int a;
  int b;
};
int blah(){return 0;}
int bar(struct s *s){return 0;}
jannh@horn:~/test$ /usr/local/google/home/jannh/git/foreign/llvmp-build-debug/bin/clang-12 -g -O2 -o debug-dse-pointer-test debug-dse-pointer-test.c debug-dse-pointer-test2.c
jannh@horn:~/test$ gdb ./debug-dse-pointer-test
[...]
(gdb) break baz
Breakpoint 1 at 0x401117: file debug-dse-pointer-test.c, line 10.
(gdb) run
[...]
Breakpoint 1, baz (s=0x7fffffffd6f0) at debug-dse-pointer-test.c:10
10        blah();
(gdb) print *s
$1 = {a = 4198720, b = 0}
(gdb) bt
#0  baz (s=0x7fffffffd6f0) at debug-dse-pointer-test.c:10
#1  foo (a=a@entry=1, b=b@entry=2) at debug-dse-pointer-test.c:18
#2  0x0000000000401150 in main () at debug-dse-pointer-test.c:22
(gdb) frame 1
#1  foo (a=a@entry=1, b=b@entry=2) at debug-dse-pointer-test.c:18
18        return baz(&s);
(gdb) print s
$2 = {a = 1, b = 2}
(gdb)
Note that the outer function foo can correctly display the struct contents, wrong data only shows up when following the pointer argument in the inlined callee.
Relevant DWARF:
0x00000059:   DW_TAG_subprogram
                DW_AT_low_pc    (0x0000000000401110)
                DW_AT_high_pc   (0x0000000000401138)
                DW_AT_frame_base        (DW_OP_reg7 RSP)
                DW_AT_call_all_calls    (true)
                DW_AT_name      ("foo")
                DW_AT_decl_file ("/usr/local/google/home/jannh/test/debug-dse-pointer-test.c")
                DW_AT_decl_line (16)
                DW_AT_prototyped        (true)
                DW_AT_type      (0x00000038 "int")
                DW_AT_external  (true)
[...]
0x0000007a:     DW_TAG_variable
                  DW_AT_location        (indexed (0x2) loclist = 0x0000003e:
                     [0x0000000000401117, 0x0000000000401120): DW_OP_reg6 RBP, DW_OP_piece 0x4, DW_OP_reg3 RBX, DW_OP_piece 0x4
                     [0x0000000000401120, 0x0000000000401123): DW_OP_piece 0x4, DW_OP_reg3 RBX, DW_OP_piece 0x4
                     [0x0000000000401123, 0x0000000000401125): DW_OP_breg7 RSP+0, DW_OP_piece 0x4, DW_OP_reg3 RBX, DW_OP_piece 0x4
                     [0x0000000000401125, 0x0000000000401129): DW_OP_breg7 RSP+0, DW_OP_piece 0x4
                     [0x0000000000401129, 0x0000000000401138): DW_OP_breg7 RSP+0)
                  DW_AT_name    ("s")
                  DW_AT_decl_file       ("/usr/local/google/home/jannh/test/debug-dse-pointer-test.c")
                  DW_AT_decl_line       (17)
                  DW_AT_type    (0x00000041 "s")
0x00000083:     DW_TAG_inlined_subroutine
                  DW_AT_abstract_origin (0x00000027 "baz")
                  DW_AT_low_pc  (0x0000000000401117)
                  DW_AT_high_pc (0x0000000000401131)
                  DW_AT_call_file       ("/usr/local/google/home/jannh/test/debug-dse-pointer-test.c")
                  DW_AT_call_line       (18)
                  DW_AT_call_column     (10)
0x00000090:       DW_TAG_formal_parameter
                    DW_AT_location      (DW_OP_reg7 RSP)
                    DW_AT_abstract_origin       (0x0000002f "s")
Disassembly:
$ objdump --disassemble=foo -Mintel ./debug-dse-pointer-test
[...]
0000000000401110 <foo>:
  401110:       55                      push   rbp
  401111:       53                      push   rbx
  401112:       50                      push   rax
  401113:       89 f3                   mov    ebx,esi
  401115:       89 fd                   mov    ebp,edi
  401117:       31 c0                   xor    eax,eax
  401119:       e8 42 00 00 00          call   401160 <blah>
  40111e:       ff c5                   inc    ebp
  401120:       89 2c 24                mov    DWORD PTR [rsp],ebp
  401123:       ff cb                   dec    ebx
  401125:       89 5c 24 04             mov    DWORD PTR [rsp+0x4],ebx
  401129:       48 89 e7                mov    rdi,rsp
  40112c:       e8 3f 00 00 00          call   401170 <bar>
  401131:       48 83 c4 08             add    rsp,0x8
  401135:       5b                      pop    rbx
  401136:       5d                      pop    rbp
  401137:       c3                      ret
Tested with:
clang version 22.0.0git (https://github.com/llvm/llvm-project a9b8dfe7b5f224e2d442352979cf2e0c1c0b539b)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/google/home/jannh/git/foreign/llvmp-build-debug/bin
Build config: +unoptimized, +assertions
When building with  -mllvm --print-after-all, you can see that the initial two store instructions in foo disappear during DSEPass:
[...]
; *** IR Dump After MemCpyOptPass on foo ***
; Function Attrs: noinline nounwind uwtable
define dso_local i32 @foo(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !8 {
entry:
  %s = alloca %struct.s, align 4, !DIAssignID !20
    #dbg_assign(i1 poison, !15, !DIExpression(), !20, ptr %s, !DIExpression(), !21)
    #dbg_value(i32 %a, !13, !DIExpression(), !21)
    #dbg_value(i32 %b, !14, !DIExpression(), !21)
  call void @llvm.lifetime.start.p0(ptr nonnull %s) #4, !dbg !22
  store i32 %a, ptr %s, align 4, !dbg !23, !tbaa !24, !DIAssignID !29
    #dbg_assign(i32 %a, !15, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !29, ptr %s, !DIExpression(), !21)
  %b2 = getelementptr inbounds nuw i8, ptr %s, i64 4, !dbg !30
  store i32 %b, ptr %b2, align 4, !dbg !23, !tbaa !31, !DIAssignID !32
    #dbg_assign(i32 %b, !15, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !32, ptr %b2, !DIExpression(), !21)
    #dbg_value(ptr %s, !33, !DIExpression(), !39)
  %call.i = tail call i32 (...) @blah() #4, !dbg !41
  %add.i = add nsw i32 %a, 1, !dbg !42
  store i32 %add.i, ptr %s, align 4, !dbg !43, !tbaa !24, !DIAssignID !44
    #dbg_assign(i32 %add.i, !15, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !44, ptr %s, !DIExpression(), !21)
  %sub.i = add nsw i32 %b, -1, !dbg !45
  store i32 %sub.i, ptr %b2, align 4, !dbg !46, !tbaa !31, !DIAssignID !47
    #dbg_assign(i32 %sub.i, !15, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !47, ptr %b2, !DIExpression(), !21)
  %call3.i = call i32 @bar(ptr noundef nonnull %s) #4, !dbg !48
  call void @llvm.lifetime.end.p0(ptr nonnull %s) #4, !dbg !49
  ret i32 %call3.i, !dbg !50
}
; *** IR Dump After DSEPass on foo ***
; Function Attrs: noinline nounwind uwtable
define dso_local i32 @foo(i32 noundef %a, i32 noundef %b) local_unnamed_addr #0 !dbg !8 {
entry:
  %s = alloca %struct.s, align 4, !DIAssignID !20
    #dbg_assign(i1 poison, !15, !DIExpression(), !20, ptr %s, !DIExpression(), !21)
    #dbg_value(i32 %a, !13, !DIExpression(), !21)
    #dbg_value(i32 %b, !14, !DIExpression(), !21)
  call void @llvm.lifetime.start.p0(ptr nonnull %s) #4, !dbg !22
    #dbg_assign(i32 %a, !15, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !23, ptr %s, !DIExpression(), !21)
  %b2 = getelementptr inbounds nuw i8, ptr %s, i64 4, !dbg !24
    #dbg_assign(i32 %b, !15, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !25, ptr %b2, !DIExpression(), !21)
    #dbg_value(ptr %s, !26, !DIExpression(), !32)
  %call.i = tail call i32 (...) @blah() #4, !dbg !34
  %add.i = add nsw i32 %a, 1, !dbg !35
  store i32 %add.i, ptr %s, align 4, !dbg !36, !tbaa !37, !DIAssignID !42
    #dbg_assign(i32 %add.i, !15, !DIExpression(DW_OP_LLVM_fragment, 0, 32), !42, ptr %s, !DIExpression(), !21)
  %sub.i = add nsw i32 %b, -1, !dbg !43
  store i32 %sub.i, ptr %b2, align 4, !dbg !44, !tbaa !45, !DIAssignID !46
    #dbg_assign(i32 %sub.i, !15, !DIExpression(DW_OP_LLVM_fragment, 32, 32), !46, ptr %b2, !DIExpression(), !21)
  %call3.i = call i32 @bar(ptr noundef nonnull %s) #4, !dbg !47
  call void @llvm.lifetime.end.p0(ptr nonnull %s) #4, !dbg !48
  ret i32 %call3.i, !dbg !49
}
[...]