diff options
-rw-r--r-- | compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep.cc | 94 | ||||
-rw-r--r-- | compiler-rt/test/asan/TestCases/Linux/coverage-caller-callee.cc | 74 |
2 files changed, 168 insertions, 0 deletions
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep.cc b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep.cc index 77302c95e61..27c1fee0aa8 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_coverage_libcdep.cc @@ -38,6 +38,7 @@ #include "sanitizer_mutex.h" #include "sanitizer_procmaps.h" #include "sanitizer_stacktrace.h" +#include "sanitizer_symbolizer.h" #include "sanitizer_flags.h" atomic_uint32_t dump_once_guard; // Ensure that CovDump runs only once. @@ -63,6 +64,9 @@ class CoverageData { void AfterFork(int child_pid); void Extend(uptr npcs); void Add(uptr pc); + void IndirCall(uptr caller, uptr callee, uptr callee_cache[], + uptr cache_size); + void DumpCallerCalleePairs(); uptr *data(); uptr size(); @@ -85,6 +89,14 @@ class CoverageData { uptr pc_array_mapped_size; // Descriptor of the file mapped pc array. int pc_fd; + + // Caller-Callee (cc) array, size and current index. + static const uptr kCcArrayMaxSize = FIRST_32_SECOND_64(1 << 18, 1 << 24); + uptr **cc_array; + atomic_uintptr_t cc_array_index; + atomic_uintptr_t cc_array_size; + + StaticSpinMutex mu; void DirectOpen(); @@ -118,6 +130,11 @@ void CoverageData::Init() { atomic_store(&pc_array_size, kPcArrayMaxSize, memory_order_relaxed); atomic_store(&pc_array_index, 0, memory_order_relaxed); } + + cc_array = reinterpret_cast<uptr **>(MmapNoReserveOrDie( + sizeof(uptr *) * kCcArrayMaxSize, "CovInit::cc_array")); + atomic_store(&cc_array_size, kCcArrayMaxSize, memory_order_relaxed); + atomic_store(&cc_array_index, 0, memory_order_relaxed); } void CoverageData::ReInit() { @@ -186,6 +203,38 @@ void CoverageData::Add(uptr pc) { pc_array[idx] = pc; } +// Registers a pair caller=>callee. +// When a given caller is seen for the first time, the callee_cache is added +// to the global array cc_array, callee_cache[0] is set to caller and +// callee_cache[1] is set to cache_size. +// Then we are trying to add callee to callee_cache [2,cache_size) if it is +// not there yet. +// If the cache is full we drop the callee (may want to fix this later). +void CoverageData::IndirCall(uptr caller, uptr callee, uptr callee_cache[], + uptr cache_size) { + if (!cc_array) return; + atomic_uintptr_t *atomic_callee_cache = + reinterpret_cast<atomic_uintptr_t *>(callee_cache); + uptr zero = 0; + if (atomic_compare_exchange_strong(&atomic_callee_cache[0], &zero, caller, + memory_order_seq_cst)) { + uptr idx = atomic_fetch_add(&cc_array_index, 1, memory_order_relaxed); + CHECK_LT(idx * sizeof(uptr), + atomic_load(&cc_array_size, memory_order_acquire)); + callee_cache[1] = cache_size; + cc_array[idx] = callee_cache; + } + CHECK_EQ(atomic_load(&atomic_callee_cache[0], memory_order_relaxed), caller); + for (uptr i = 2; i < cache_size; i++) { + uptr was = 0; + if (atomic_compare_exchange_strong(&atomic_callee_cache[i], &was, callee, + memory_order_seq_cst)) + return; + if (was == callee) // Already have this callee. + return; + } +} + uptr *CoverageData::data() { return pc_array; } @@ -268,6 +317,45 @@ static int CovOpenFile(bool packed, const char* name) { return fd; } +// This function dumps the caller=>callee pairs into a file as a sequence of +// lines like "module_name offset". +void CoverageData::DumpCallerCalleePairs() { + uptr max_idx = atomic_load(&cc_array_index, memory_order_relaxed); + if (!max_idx) return; + auto sym = Symbolizer::GetOrInit(); + if (!sym) + return; + InternalScopedString out(4096 * 16); + uptr total = 0; + for (uptr i = 0; i < max_idx; i++) { + uptr *cc_cache = cc_array[i]; + CHECK(cc_cache); + uptr caller = cc_cache[0]; + uptr n_callees = cc_cache[1]; + const char *caller_module_name = "<unknown>"; + uptr caller_module_address = 0; + sym->GetModuleNameAndOffsetForPC(caller, &caller_module_name, + &caller_module_address); + for (uptr j = 2; j < n_callees; j++) { + uptr callee = cc_cache[j]; + if (!callee) break; + total++; + const char *callee_module_name = "<unknown>"; + uptr callee_module_address = 0; + sym->GetModuleNameAndOffsetForPC(callee, &callee_module_name, + &callee_module_address); + out.append("%s 0x%zx\n%s 0x%zx\n", caller_module_name, + caller_module_address, callee_module_name, + callee_module_address); + } + } + int fd = CovOpenFile(false, "caller-callee"); + if (fd < 0) return; + internal_write(fd, out.data(), out.length()); + internal_close(fd); + VReport(1, " CovDump: %zd caller-callee pairs written\n", total); +} + // Dump the coverage on disk. static void CovDump() { if (!common_flags()->coverage || common_flags()->coverage_direct) return; @@ -324,6 +412,7 @@ static void CovDump() { } if (cov_fd >= 0) internal_close(cov_fd); + coverage_data.DumpCallerCalleePairs(); #endif // !SANITIZER_WINDOWS } @@ -359,6 +448,11 @@ extern "C" { SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov() { coverage_data.Add(StackTrace::GetPreviousInstructionPc(GET_CALLER_PC())); } +SANITIZER_INTERFACE_ATTRIBUTE void +__sanitizer_cov_indir_call16(uptr callee, uptr callee_cache16[]) { + coverage_data.IndirCall(StackTrace::GetPreviousInstructionPc(GET_CALLER_PC()), + callee, callee_cache16, 16); +} SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_dump() { CovDump(); } SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_init() { coverage_data.Init(); diff --git a/compiler-rt/test/asan/TestCases/Linux/coverage-caller-callee.cc b/compiler-rt/test/asan/TestCases/Linux/coverage-caller-callee.cc new file mode 100644 index 00000000000..c2395f19ebb --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Linux/coverage-caller-callee.cc @@ -0,0 +1,74 @@ +// Test caller-callee coverage with large number of threads +// and various numbers of callers and callees. + +// RUN: %clangxx_asan -mllvm -asan-coverage=4 %s -o %t +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 10 1 2>&1 | FileCheck %s --check-prefix=CHECK-10-1 +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 9 2 2>&1 | FileCheck %s --check-prefix=CHECK-9-2 +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 7 3 2>&1 | FileCheck %s --check-prefix=CHECK-7-3 +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 17 1 2>&1 | FileCheck %s --check-prefix=CHECK-17-1 +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 15 2 2>&1 | FileCheck %s --check-prefix=CHECK-15-2 +// RUN: ASAN_OPTIONS=coverage=1:verbosity=1 %run %t 18 3 2>&1 | FileCheck %s --check-prefix=CHECK-18-3 +// RUN: rm -f caller-callee*.sancov +// +// REQUIRES: asan-64-bits +// +// CHECK-10-1: CovDump: 10 caller-callee pairs written +// CHECK-9-2: CovDump: 18 caller-callee pairs written +// CHECK-7-3: CovDump: 21 caller-callee pairs written +// CHECK-17-1: CovDump: 14 caller-callee pairs written +// CHECK-15-2: CovDump: 28 caller-callee pairs written +// CHECK-18-3: CovDump: 42 caller-callee pairs written + +#include <stdio.h> +#include <stdlib.h> +#include <pthread.h> +int P = 0; +struct Foo {virtual void f() {if (P) printf("Foo::f()\n");}}; +struct Foo1 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo2 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo3 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo4 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo5 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo6 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo7 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo8 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo9 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo10 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo11 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo12 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo13 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo14 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo15 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo16 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo17 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo18 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; +struct Foo19 : Foo {virtual void f() {if (P) printf("%d\n", __LINE__);}}; + +Foo *foo[20] = { + new Foo, new Foo1, new Foo2, new Foo3, new Foo4, new Foo5, new Foo6, + new Foo7, new Foo8, new Foo9, new Foo10, new Foo11, new Foo12, new Foo13, + new Foo14, new Foo15, new Foo16, new Foo17, new Foo18, new Foo19, +}; + +int n_functions = 10; +int n_callers = 2; + +void *Thread(void *arg) { + if (n_callers >= 1) for (int i = 0; i < 2000; i++) foo[i % n_functions]->f(); + if (n_callers >= 2) for (int i = 0; i < 2000; i++) foo[i % n_functions]->f(); + if (n_callers >= 3) for (int i = 0; i < 2000; i++) foo[i % n_functions]->f(); + return arg; +} + +int main(int argc, char **argv) { + if (argc >= 2) + n_functions = atoi(argv[1]); + if (argc >= 3) + n_callers = atoi(argv[2]); + const int kNumThreads = 16; + pthread_t t[kNumThreads]; + for (int i = 0; i < kNumThreads; i++) + pthread_create(&t[i], 0, Thread, 0); + for (int i = 0; i < kNumThreads; i++) + pthread_join(t[i], 0); +} |