Skip to content

-fsanitize-coverage produces incorrect debug info at function entry points #54873

@khuey

Description

@khuey

Consider the following function:

int square(int i) {
    return i * i;
}

Compiling with

Debian clang version 15.0.0-++20220411071831+e995526e661f-1~exp1~20220411071915.217
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin

The assembly looks like:

0000000000001130 <square>:
    1130:       55                      push   %rbp
    1131:       48 89 e5                mov    %rsp,%rbp
    1134:       89 7d fc                mov    %edi,-0x4(%rbp)
    1137:       8b 45 fc                mov    -0x4(%rbp),%eax
    113a:       0f af 45 fc             imul   -0x4(%rbp),%eax
    113e:       5d                      pop    %rbp
    113f:       c3                      retq   

The DWARF output from dwarfdump -i is:

<snip>
< 1><0x00000023>    DW_TAG_subprogram
                      DW_AT_low_pc                (indirect address, index 0x0): 0x00001130
                      DW_AT_high_pc               <offset-from-lowpc>16
                      DW_AT_frame_base            len 0x0001: 56: DW_OP_reg6
                      DW_AT_name                  (indirect string, index 0x3): square
                      DW_AT_decl_file             0x00000000
                      DW_AT_decl_line             0x00000001
                      DW_AT_prototyped            yes
                      DW_AT_type                  0x00000064<.debug_info+0x00000064>
                      DW_AT_external              yes
< 2><0x00000032>      DW_TAG_formal_parameter
                        DW_AT_location              len 0x0002: 917c: DW_OP_fbreg -4
                        DW_AT_name                  (indirect string, index 0x6): i
                        DW_AT_decl_file             0x00000000
                        DW_AT_decl_line             0x00000001
                        DW_AT_type                  0x00000064<.debug_info+0x00000064>

<snip>

And from dwarfdump -l

.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000c):

            NS new statement, BB new basic block, ET end of text sequence
            PE prologue end, EB epilogue begin
            IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x00001130  [   1, 0] NS uri: "/build/coverage-test.c"
0x00001137  [   2,10] NS PE
<snip>

Note that the debug_info declares the parameter is on the stack at -0x4(%rbp), not in the register specified in the ABI (this is normal for debug builds). The debug_line section specifies the prologue end is at 0x1137 (the PE marker) so that's where a debugger will place a breakpoint at function entry. Because that's after the value of %edi at function entry has been moved into the stack slot, everything will work as expected.

Now add -fsanitize-coverage=trace-pc-guard to the compiler arguments:

The assembly looks like:

000000000002d8c0 <square>:
   2d8c0:       55                      push   %rbp
   2d8c1:       48 89 e5                mov    %rsp,%rbp
   2d8c4:       48 83 ec 10             sub    $0x10,%rsp
   2d8c8:       89 7d f8                mov    %edi,-0x8(%rbp)
   2d8cb:       48 8d 3d be 72 01 00    lea    0x172be(%rip),%rdi        # 44b90 <__TMC_END__>
   2d8d2:       e8 19 02 ff ff          callq  1daf0 <__sanitizer_cov_trace_pc_guard>
   2d8d7:       8b 7d f8                mov    -0x8(%rbp),%edi
   2d8da:       89 7d fc                mov    %edi,-0x4(%rbp)
   2d8dd:       8b 45 fc                mov    -0x4(%rbp),%eax
   2d8e0:       0f af 45 fc             imul   -0x4(%rbp),%eax
   2d8e4:       48 83 c4 10             add    $0x10,%rsp
   2d8e8:       5d                      pop    %rbp
   2d8e9:       c3                      retq   
   2d8ea:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

Note that the compiler has inserted a call to __sanitizer_cov_trace_pc_guard, and a second stack slot for the original value of %edi at -0x8(%rbp).

The DWARF output from dwarfdump -i is:

<snip>
< 1><0x0000002b>    DW_TAG_subprogram
                      DW_AT_low_pc                (indirect address, index 0x0): 0x0002d8c0
                      DW_AT_high_pc               <offset-from-lowpc>42
                      DW_AT_frame_base            len 0x0001: 56: DW_OP_reg6
                      DW_AT_name                  (indirect string, index 0x3): square
                      DW_AT_decl_file             0x00000000
                      DW_AT_decl_line             0x00000001
                      DW_AT_prototyped            yes
                      DW_AT_type                  0x0000006c<.debug_info+0x0000006c>
                      DW_AT_external              yes
< 2><0x0000003a>      DW_TAG_formal_parameter
                        DW_AT_location              len 0x0002: 917c: DW_OP_fbreg -4
                        DW_AT_name                  (indirect string, index 0x6): i
                        DW_AT_decl_file             0x00000000
                        DW_AT_decl_line             0x00000001
                        DW_AT_type                  0x0000006c<.debug_info+0x0000006c>
<snip>

i.e. essentially unchanged, note that the DWARF still claims the variable lives at -0x4(%rbp).

The output from dwarfdump -l, however, is:

.debug_line: line number info for a single cu
Source lines (from CU-DIE at .debug_info offset 0x0000000c):

            NS new statement, BB new basic block, ET end of text sequence
            PE prologue end, EB epilogue begin
            IS=val ISA number, DI=val discriminator value
<pc>        [lno,col] NS BB ET PE EB IS= DI= uri: "filepath"
0x0002d8c0  [   1, 0] NS uri: "/build/coverage-test.c"
0x0002d8cb  [   1, 0] NS PE
0x0002d8dd  [   2,10] NS
<snip>

The prologue end marker is now placed at 0x2d8cb, which is after the value of %rdi has been moved to the newly added stack slot -0x8(%rbp). But the DWARF still claims that value of i is in -0x4(%rbp), which is not initialized until 0x2d8dd. So a debugger will break at 0x2d8cb, read the value of i from -0x4(%rbp), and get a garbage value because that slot is not yet initialized.

This affects displaying the values of variables on function entry, including setting conditional breakpoints on them

(This issue was originally reported to me by @hsivonen in a Rust binary produced by cargo-fuzz.)

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions