https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90746

            Bug ID: 90746
           Summary: __sanitizer_cov_trace_pc should not be tail called
           Product: gcc
           Version: 10.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: sanitizer
          Assignee: unassigned at gcc dot gnu.org
          Reporter: dvyukov at google dot com
                CC: dodji at gcc dot gnu.org, dvyukov at gcc dot gnu.org,
                    jakub at gcc dot gnu.org, kcc at gcc dot gnu.org, marxin at 
gcc dot gnu.org
  Target Milestone: ---

-fsanitize-coverage=trace-pc emits calls to __sanitizer_cov_trace_pc into every
basic block. This function is meant to use __builtin_return_address to get
caller PC and trace them. However, with -O2 __sanitizer_cov_trace_pc calls are
tailcall-optimized, instead of callq __sanitizer_cov_trace_pc we get jmpq
__sanitizer_cov_trace_pc, as the result __builtin_return_address returns
caller-caller PC instead of caller PC. This leads to 2 problems:
1. The executed basic block is not traced.
2. When we generate coverage reports we used to assume that we will trace only
PCs that point to callq __sanitizer_cov_trace_pc. It makes it easy to (1)
pre-symbolize all possible PCs and (2) calculate coverage percent as the ratio
of covered call sites to all call sites. But these assumptions break when the
callback is tail called.

Here is a repro:

// test.c
#include <stdlib.h>
#include <stdio.h>

__attribute__((noinline)) void foo()
{
        if (!rand()) {
                printf("%s\n", __func__);
                return;
        }
}

int main()
{
        foo();
}

// cov.c
#include <stdio.h>

void __sanitizer_cov_trace_pc()
{
        printf("0x%lx\n", __builtin_return_address(0) - 5);
}

Then run:
gcc test.c -O2 -g -fsanitize-coverage=trace-pc -c && gcc cov.c test.o &&
./a.out | addr2line -fae a.out

0x00000000004004a4
main
test.c:14
0x00000000004005c4
foo
test.c:6
0x00000000004004ab
main
test.c:14

vs:
gcc test.c -O2 -fno-optimize-sibling-calls -g -fsanitize-coverage=trace-pc -c
&& gcc cov.c test.o && ./a.out | addr2line -fae a.out

0x00000000004004a4
main
test.c:14
0x00000000004005c4
foo
test.c:6
0x00000000004005d2
foo
test.c:6

In the first invocation the second basic block of foo is not traced.

I've found this piece of code which looks relevant:

// tree-tailcall.c
static void
find_tail_calls (basic_block bb, struct tailcall **ret)
...
  /* We found the call, check whether it is suitable.  */
  tail_recursion = false;
  func = gimple_call_fndecl (call);
  if (func
      && !fndecl_built_in_p (func)
      && recursive_call_p (current_function_decl, func))
    {
        ...
        tail_recursion = true;
    }

But I am not sure what's the right way to change it. __sanitizer_cov_trace_pc
is kinda builtin, but not exactly.

"noreturn" attribute should prevent tail calls, but we can't mark
__sanitizer_cov_trace_pc as noreturn. Is there a special bit for just tail
calls?

Reply via email to