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?