From 91c52d9552fa35c8fd9ac1deb02a9d85f8db5b42 Mon Sep 17 00:00:00 2001 From: Kostya Serebryany Date: Thu, 14 Nov 2013 13:28:17 +0000 Subject: [PATCH] [asan] Poor man's coverage that works with ASan (compiler-rt part) llvm-svn: 194702 --- .../include/sanitizer/common_interface_defs.h | 4 + compiler-rt/lib/asan/asan_flags.h | 3 + compiler-rt/lib/asan/asan_rtl.cc | 5 + .../lit_tests/TestCases/Linux/coverage.cc | 45 ++++++++ .../lib/sanitizer_common/CMakeLists.txt | 1 + .../lib/sanitizer_common/sanitizer_common.cc | 11 ++ .../lib/sanitizer_common/sanitizer_common.h | 5 + .../sanitizer_common/sanitizer_coverage.cc | 109 ++++++++++++++++++ .../sanitizer_internal_defs.h | 3 + compiler-rt/lib/tsan/rtl/tsan_symbolize.cc | 12 -- 10 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 compiler-rt/lib/asan/lit_tests/TestCases/Linux/coverage.cc create mode 100644 compiler-rt/lib/sanitizer_common/sanitizer_coverage.cc diff --git a/compiler-rt/include/sanitizer/common_interface_defs.h b/compiler-rt/include/sanitizer/common_interface_defs.h index 741958f834d5..b9c0d7311be2 100644 --- a/compiler-rt/include/sanitizer/common_interface_defs.h +++ b/compiler-rt/include/sanitizer/common_interface_defs.h @@ -47,6 +47,10 @@ extern "C" { void __sanitizer_unaligned_store32(void *p, uint32_t x); void __sanitizer_unaligned_store64(void *p, uint64_t x); + // Record and dump coverage info. + void __sanitizer_cov(void *pc); + void __sanitizer_cov_dump(); + #ifdef __cplusplus } // extern "C" #endif diff --git a/compiler-rt/lib/asan/asan_flags.h b/compiler-rt/lib/asan/asan_flags.h index 86eb3b6c180c..89662f28b29c 100644 --- a/compiler-rt/lib/asan/asan_flags.h +++ b/compiler-rt/lib/asan/asan_flags.h @@ -83,6 +83,9 @@ struct Flags { bool print_legend; // If set, prints ASan exit stats even after program terminates successfully. bool atexit; + // If set, coverage information will be dumped at shutdown time if the + // appropriate instrumentation was enabled. + bool coverage; // By default, disable core dumper on 64-bit - it makes little sense // to dump 16T+ core. bool disable_core; diff --git a/compiler-rt/lib/asan/asan_rtl.cc b/compiler-rt/lib/asan/asan_rtl.cc index bd1ace747f08..11f05954d31e 100644 --- a/compiler-rt/lib/asan/asan_rtl.cc +++ b/compiler-rt/lib/asan/asan_rtl.cc @@ -120,6 +120,7 @@ static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->print_stats, "print_stats"); ParseFlag(str, &f->print_legend, "print_legend"); ParseFlag(str, &f->atexit, "atexit"); + ParseFlag(str, &f->coverage, "coverage"); ParseFlag(str, &f->disable_core, "disable_core"); ParseFlag(str, &f->allow_reexec, "allow_reexec"); ParseFlag(str, &f->print_full_thread_history, "print_full_thread_history"); @@ -161,6 +162,7 @@ void InitializeFlags(Flags *f, const char *env) { f->print_stats = false; f->print_legend = true; f->atexit = false; + f->coverage = false; f->disable_core = (SANITIZER_WORDSIZE == 64); f->allow_reexec = true; f->print_full_thread_history = true; @@ -541,6 +543,9 @@ void __asan_init() { if (flags()->atexit) Atexit(asan_atexit); + if (flags()->coverage) + Atexit(__sanitizer_cov_dump); + // interceptors InitTlsSize(); diff --git a/compiler-rt/lib/asan/lit_tests/TestCases/Linux/coverage.cc b/compiler-rt/lib/asan/lit_tests/TestCases/Linux/coverage.cc new file mode 100644 index 000000000000..4373e9b13c68 --- /dev/null +++ b/compiler-rt/lib/asan/lit_tests/TestCases/Linux/coverage.cc @@ -0,0 +1,45 @@ +// RUN: %clangxx_asan -mllvm -asan-coverage=1 -DSHARED %s -shared -o %t.so -fPIC +// RUN: %clangxx_asan -mllvm -asan-coverage=1 %s -o %t -Wl,-R. %t.so +// RUN: export ASAN_OPTIONS=coverage=1:verbosity=1 +// RUN: %t 2>&1 | FileCheck %s --check-prefix=CHECK-main +// RUN: %t foo 2>&1 | FileCheck %s --check-prefix=CHECK-foo +// RUN: %t bar 2>&1 | FileCheck %s --check-prefix=CHECK-bar +// RUN: %t foo bar 2>&1 | FileCheck %s --check-prefix=CHECK-foo-bar + +#include +#include +#include + +#ifdef SHARED +void bar() { printf("bar\n"); } +#else +__attribute__((noinline)) +void foo() { printf("foo\n"); } +extern void bar(); + +int main(int argc, char **argv) { + fprintf(stderr, "PID: %d\n", getpid()); + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "foo")) + foo(); + if (!strcmp(argv[i], "bar")) + bar(); + } +} +#endif + +// CHECK-main: PID: [[PID:[0-9]+]] +// CHECK-main: [[PID]].sancov: 1 PCs written +// CHECK-main-NOT: .so.[[PID]] +// +// CHECK-foo: PID: [[PID:[0-9]+]] +// CHECK-foo: [[PID]].sancov: 2 PCs written +// CHECK-foo-NOT: .so.[[PID]] +// +// CHECK-bar: PID: [[PID:[0-9]+]] +// CHECK-bar: [[PID]].sancov: 1 PCs written +// CHECK-bar: .so.[[PID]].sancov: 1 PCs written +// +// CHECK-foo-bar: PID: [[PID:[0-9]+]] +// CHECK-foo-bar: [[PID]].sancov: 2 PCs written +// CHECK-foo-bar: so.[[PID]].sancov: 1 PCs written diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt index 4ec371958e6d..84c1e67dc806 100644 --- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -4,6 +4,7 @@ set(SANITIZER_SOURCES sanitizer_allocator.cc sanitizer_common.cc + sanitizer_coverage.cc sanitizer_flags.cc sanitizer_libc.cc sanitizer_libignore.cc diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.cc b/compiler-rt/lib/sanitizer_common/sanitizer_common.cc index 3af4d4fb6d10..7e870ff65455 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.cc @@ -226,6 +226,17 @@ bool LoadedModule::containsAddress(uptr address) const { return false; } +char *StripModuleName(const char *module) { + if (module == 0) + return 0; + const char *short_module_name = internal_strrchr(module, '/'); + if (short_module_name) + short_module_name += 1; + else + short_module_name = module; + return internal_strdup(short_module_name); +} + } // namespace __sanitizer using namespace __sanitizer; // NOLINT diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h index daf9892d90cc..cf8a12d65a09 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h @@ -180,6 +180,9 @@ void SleepForMillis(int millis); u64 NanoTime(); int Atexit(void (*function)(void)); void SortArray(uptr *array, uptr size); +// Strip the directories from the module name, return a new string allocated +// with internal_strdup. +char *StripModuleName(const char *module); // Exit void NORETURN Abort(); @@ -359,6 +362,8 @@ class InternalMmapVector { return capacity_; } + void clear() { size_ = 0; } + private: void Resize(uptr new_capacity) { CHECK_GT(new_capacity, 0); diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_coverage.cc b/compiler-rt/lib/sanitizer_common/sanitizer_coverage.cc new file mode 100644 index 000000000000..c5338e9c6892 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_coverage.cc @@ -0,0 +1,109 @@ +//===-- sanitizer_coverage.cc ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Sanitizer Coverage. +// This file implements run-time support for a poor man's coverage tool. +// +// Compiler instrumentation: +// For every function F the compiler injects the following code: +// if (*Guard) { +// __sanitizer_cov(&F); +// *Guard = 1; +// } +// It's fine to call __sanitizer_cov more than once for a given function. +// +// Run-time: +// - __sanitizer_cov(pc): record that we've executed a given PC. +// - __sanitizer_cov_dump: dump the coverage data to disk. +// For every module of the current process that has coverage data +// this will create a file module_name.PID.sancov. The file format is simple: +// it's just a sorted sequence of 4-byte offsets in the module. +// +// Eventually, this coverage implementation should be obsoleted by a more +// powerful general purpose Clang/LLVM coverage instrumentation. +// Consider this implementation as prototype. +// +// FIXME: support (or at least test with) dlclose. +//===----------------------------------------------------------------------===// + +#include "sanitizer_allocator_internal.h" +#include "sanitizer_common.h" +#include "sanitizer_libc.h" +#include "sanitizer_mutex.h" +#include "sanitizer_procmaps.h" +#include "sanitizer_flags.h" + +struct CovData { + BlockingMutex mu; + InternalMmapVector v; +}; + +static uptr cov_data_placeholder[sizeof(CovData) / sizeof(uptr)]; +COMPILER_CHECK(sizeof(cov_data_placeholder) >= sizeof(CovData)); +static CovData *cov_data = reinterpret_cast(cov_data_placeholder); + +namespace __sanitizer { + +// Simply add the pc into the vector under lock. If the function is called more +// than once for a given PC it will be inserted multiple times, which is fine. +static void CovAdd(uptr pc) { + BlockingMutexLock lock(&cov_data->mu); + cov_data->v.push_back(pc); +} + +static inline bool CompareLess(const uptr &a, const uptr &b) { + return a < b; +} + +// Dump the coverage on disk. +void CovDump() { + BlockingMutexLock lock(&cov_data->mu); + InternalMmapVector &v = cov_data->v; + InternalSort(&v, v.size(), CompareLess); + InternalMmapVector offsets(v.size()); + const uptr *vb = v.data(); + const uptr *ve = vb + v.size(); + MemoryMappingLayout proc_maps(/*cache_enabled*/false); + uptr mb, me, off, prot; + InternalScopedBuffer module(4096); + InternalScopedBuffer path(4096 * 2); + for (int i = 0; + proc_maps.Next(&mb, &me, &off, module.data(), module.size(), &prot); + i++) { + if ((prot & MemoryMappingLayout::kProtectionExecute) == 0) + continue; + if (vb >= ve) break; + if (mb <= *vb && *vb < me) { + offsets.clear(); + const uptr *old_vb = vb; + CHECK_LE(off, *vb); + for (; vb < ve && *vb < me; vb++) { + uptr diff = *vb - (i ? mb : 0) + off; + CHECK_LE(diff, 0xffffffffU); + offsets.push_back(static_cast(diff)); + } + char *module_name = StripModuleName(module.data()); + internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", + module_name, internal_getpid()); + InternalFree(module_name); + uptr fd = OpenFile(path.data(), true); + internal_write(fd, offsets.data(), offsets.size() * sizeof(u32)); + internal_close(fd); + if (common_flags()->verbosity) + Report(" CovDump: %s: %zd PCs written\n", path.data(), vb - old_vb); + } + } +} + +} // namespace __sanitizer + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov(uptr pc) { CovAdd(pc); } +SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_dump() { CovDump(); } +} // extern "C" diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h index 189614c367b2..0f8367ff4b6f 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h @@ -110,6 +110,9 @@ extern "C" { // the error message. This function can be overridden by the client. SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void __sanitizer_report_error_summary(const char *error_summary); + + SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov(__sanitizer::uptr pc); + SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_dump(); } // extern "C" diff --git a/compiler-rt/lib/tsan/rtl/tsan_symbolize.cc b/compiler-rt/lib/tsan/rtl/tsan_symbolize.cc index 24c747227ed2..75acf1d8438b 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_symbolize.cc +++ b/compiler-rt/lib/tsan/rtl/tsan_symbolize.cc @@ -42,18 +42,6 @@ ReportStack *NewReportStackEntry(uptr addr) { return ent; } -// Strip module path to make output shorter. -static char *StripModuleName(const char *module) { - if (module == 0) - return 0; - const char *short_module_name = internal_strrchr(module, '/'); - if (short_module_name) - short_module_name += 1; - else - short_module_name = module; - return internal_strdup(short_module_name); -} - static ReportStack *NewReportStackEntry(const AddressInfo &info) { ReportStack *ent = NewReportStackEntry(info.address); ent->module = StripModuleName(info.module);