llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.34k stars 12.13k forks source link

Incorrect debug information of pointer variables compiled with '-g -O3' #95512

Open edumoot opened 5 months ago

edumoot commented 5 months ago

We get a wrong value -21845- while dereferencing the pointer "ptr_global" in lldb environment. It should be pointing to "local_var" on line 20, instead of other address, so the correct one should be 3 .

The pointer variable "ptr_local_var" has the same problem.

(lldb) b case.c:20
Breakpoint 1: where = case.out`main + 6 [inlined] func_1 at case.c:20:28, address = 0x0000000000001146
(lldb) r
Process 6657 launched: '/home/ad/Downloads/lldb/reproduce_bug/case.out' (x86_64)
Process 6657 stopped
* thread #1, name = 'case.out', stop reason = breakpoint 1.1
    frame #0: 0x0000555555555146 case.out`main [inlined] func_1 at case.c:20:28
   17       }
   18   
   19       ptr_global = ptr_local_var;
-> 20       *ptr_to_global_pointer = &local_var;
   21   
   22       return *global_pointer;
   23   }
(lldb) fr v
(int) local_var = 3
(int *) ptr_local_var = 0x00007fffffffd894
(int *) ptr_global = 0x00007fffffffd894
(lldb) p *ptr_local_var
(int) 21845
(lldb) p *ptr_global
(int) 21845
(lldb) fr v -L
0x00007837c0001a40: (int) local_var = 3
scalar: (int *) ptr_local_var = 0x00007fffffffd894
scalar: (int *) ptr_global = 0x00007fffffffd894

cat case.c

#include "stdio.h"

static int global_var = 7;
static int *global_pointer = (void*)0;

static int func_1(void)
{
    int *ptr_global = &global_var;
    int local_var = 3;
    int *ptr_local_var = &local_var;
    int **ptr_to_global_pointer = &global_pointer;

    for (global_var = 9; (global_var != 27); global_var += 3)
    {
        if (*ptr_global)
            break;
    }

    ptr_global = ptr_local_var;
    *ptr_to_global_pointer = &local_var;

    return *global_pointer;
}

int main(void)
{
    printf("%d", func_1());
    return 0;
}

We can reproduce it in LLVM 18.1.2, LLVM17.0.6, and LLVM16.0.3, with:

clang case.c -g -O3 -o case.out
lldb case.out
llvmbot commented 5 months ago

@llvm/issue-subscribers-debuginfo

Author: Yachao Zhu (edumoot)

We get a wrong value -21845- while dereferencing the pointer "ptr_global" in lldb environment. It should be pointing to "local_var" on line 20, instead of other address, so the correct one should be 3 . The pointer variable "ptr_local_var" has the same problem. ```lldb (lldb) b case.c:20 Breakpoint 1: where = case.out`main + 6 [inlined] func_1 at case.c:20:28, address = 0x0000000000001146 (lldb) r Process 6657 launched: '/home/ad/Downloads/lldb/reproduce_bug/case.out' (x86_64) Process 6657 stopped * thread #1, name = 'case.out', stop reason = breakpoint 1.1 frame #0: 0x0000555555555146 case.out`main [inlined] func_1 at case.c:20:28 17 } 18 19 ptr_global = ptr_local_var; -> 20 *ptr_to_global_pointer = &local_var; 21 22 return *global_pointer; 23 } (lldb) fr v (int) local_var = 3 (int *) ptr_local_var = 0x00007fffffffd894 (int *) ptr_global = 0x00007fffffffd894 (lldb) p *ptr_local_var (int) 21845 (lldb) p *ptr_global (int) 21845 (lldb) fr v -L 0x00007837c0001a40: (int) local_var = 3 scalar: (int *) ptr_local_var = 0x00007fffffffd894 scalar: (int *) ptr_global = 0x00007fffffffd894 ``` `cat case.c` ``` #include "stdio.h" static int global_var = 7; static int *global_pointer = (void*)0; static int func_1(void) { int *ptr_global = &global_var; int local_var = 3; int *ptr_local_var = &local_var; int **ptr_to_global_pointer = &global_pointer; for (global_var = 9; (global_var != 27); global_var += 3) { if (*ptr_global) break; } ptr_global = ptr_local_var; *ptr_to_global_pointer = &local_var; return *global_pointer; } int main(void) { printf("%d", func_1()); return 0; } ``` We can reproduce it in LLVM 18.1.2, LLVM17.0.6, and LLVM16.0.3, with: ``` clang case.c -g -O3 -o case.out lldb case.out ```
edumoot commented 5 months ago

GDB produces similar things:

Breakpoint 1, func_1 () at case.c:20
20      *ptr_to_global_pointer = &local_var;
(gdb) info locals
local_var = 3
ptr_local_var = 0x7fffffffdaf4
ptr_global = 0x7fffffffdaf4
ptr_to_global_pointer = <optimised out>
(gdb) p *ptr_local_var
$1 = 21845
(gdb) p *ptr_global
$2 = 21845
(gdb) 
OCHyams commented 5 months ago

This feels like the kind of thing we'd need DW_OP_implicit_pointer to fix. That said, this particular example looks tricky because AFAICT the pointer variables do point to the stack slot of local_var correctly (i.e., the implicit pointer locations are correct in DWARF), it's just that a particular value hasn't been written to that address.

The variable local_var still gets a stack slot, just nothing is written to it - DSE removes the store (presumably because it would be UB to dereference ptr_global outside the function?).

edumoot commented 5 months ago

This feels like the kind of thing we'd need DW_OP_implicit_pointer to fix. That said, this particular example looks tricky because AFAICT the pointer variables do point to the stack slot of local_var correctly (i.e., the implicit pointer locations are correct in DWARF), it's just that a particular value hasn't been written to that address.

The variable local_var still gets a stack slot, just nothing is written to it - DSE removes the store (presumably because it would be UB to dereference ptr_global outside the function?).

The following is what we get on line 9 by insertting a marker to force the debugger stop. "21845" is stored at the address "0x00007fffffffd894". It seems that clang compiler returns "local_var" directly without initializing its value to the address .

* thread #1, name = 'case_r.out', stop reason = breakpoint 1.1
    frame #0: 0x0000555555555141 case_r.out`main [inlined] func_1 at .modifiedcase.c:9:9
   6    static int func_1(void)
   7    {
   8        int *ptr_global = &global_var;
-> 9        int local_var = 3;
   10       int *ptr_local_var = &local_var;
   11       int **ptr_to_global_pointer = &global_pointer;
   12   
(lldb) fr v
(int) local_var = 21845
(int *) ptr_local_var = <variable not available>

(int *) ptr_global = <variable not available>
(lldb) fr v -L
0x00007fffffffd8a4: (int) local_var = 21845
: (int *) ptr_local_var = <variable not available>

: (int *) ptr_global = <variable not available>
edumoot commented 5 months ago

"DSE removes the store instruction" @OCHyams also effects static global variables. After dereferencing global_pointer, we get 21845, although printf function works well.

    frame #0: 0x000055555555514d case.out`main at case.c:28:5
   25   
   26   int main(void)
   27   {
-> 28       printf("%d", func_1());
   29       return 0;
   30   }
(lldb) ta v
Global variables for /home/ad/Downloads/lldb/reproduce_bug/case.c in /home/ad/Downloads/lldb/reproduce_bug/case.out:
(int *) global_pointer = 0x00007fffffffd8a4
(lldb) p *global_pointer
(int) 21845
OCHyams commented 5 months ago

although printf function works well.

The value 3 is never stored to memory - func_1 is inlined and then the constant literal 3 is passed to printf directly. I imagine the compiler is eliding the store to the stack slot because the local value isn't used, and that it keeps the stack slot because its address is taken (I don't think saving the local's address itself is an issue). I think that to dereference that address of the local saved in the global variable is UB. To illustrate that I've modified main to dereference global_pointer after the call and ASAN gets upset: https://godbolt.org/z/Gv9WjWa7E

That is to say, I still think the variable locations for the pointers are correct. The pointer value is the address of the local_var - local_var is just never initialised because the only situation in which local_var's address would be read from is undefined behaviour, therefore the compiler can elide the store even though the stack slot still exists. I am inclined to say that this isn't a bug so much as a feature request (and I think it would be quite hard to fix, but I might be being pessimistic here).

Might be worth someone else having a quick look too to make sure I'm not barking up the wrong tree here (cc @jmorse).

edumoot commented 5 months ago

@OCHyams Yes, I agree. The variable location for the pointers are correct, indicating to 0x00007fffffffd8a4, the address of local_var, and the variable of local_var is not be initialized. Is this UB triggered by optimizations?

jmorse commented 5 months ago

Tricky -- almost all of this program can be optimised away by LLVM, but it has the added complication of having stores to global variables, which LLVM is reluctant to remove because they're global. That then leads to variables of the original program not being deleted because they need to support the stores to the global, but all the meaning has been optimised away. Hence: being able to view some uninitialized memory here.

IMO it's interesting that we can see these slices of program state preserved by the globals, but if the optimiser is allowed to optimise away the redundant calculations, we're stuck with presenting slightly misleading information. I don't believe there's anything in DWARF that allows the value/location of a global variable to be altered for a range of instructions?

dwblaikie commented 5 months ago

Tricky -- almost all of this program can be optimised away by LLVM, but it has the added complication of having stores to global variables, which LLVM is reluctant to remove because they're global. That then leads to variables of the original program not being deleted because they need to support the stores to the global, but all the meaning has been optimised away. Hence: being able to view some uninitialized memory here.

IMO it's interesting that we can see these slices of program state preserved by the globals, but if the optimiser is allowed to optimise away the redundant calculations, we're stuck with presenting slightly misleading information. I don't believe there's anything in DWARF that allows the value/location of a global variable to be altered for a range of instructions?

Technically there's no reason you couldn't use a loclist for a global, I think? It might be hard to do, but I guess at least for a static/file-scoped global, you might be able to do it - use the memory location as the default location, and explicit location list regions for the optimized-to-local/constant/etc portions.

But I'd say it's a fair bit of work and not something we're likely to do any time soon so far as I can guess.