[asan] run-time part of the caller-callee coverage instrumentation

llvm-svn: 220975
This commit is contained in:
Kostya Serebryany 2014-10-31 17:19:11 +00:00
parent 11ab078d80
commit b6eae0c2bc
2 changed files with 168 additions and 0 deletions

View File

@ -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();

View File

@ -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);
}