| Issue |
161555
|
| Summary |
[DebugInfo] dereferencing pointer to local variable shows uninitialized data due to DSE
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
thejh
|
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 https://github.com/llvm/llvm-project/issues/38112#issuecomment-981001844 , 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
}
[...]
```
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs