Index: lib/CMakeLists.txt =================================================================== --- lib/CMakeLists.txt +++ lib/CMakeLists.txt @@ -15,6 +15,7 @@ if(COMPILER_RT_HAS_SANITIZER_COMMON) add_subdirectory(sanitizer_common) + add_subdirectory(stats) add_subdirectory(lsan) add_subdirectory(ubsan) endif() Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -522,6 +522,19 @@ void clear() { size_ = 0; } bool empty() const { return size() == 0; } + const T *begin() const { + return data(); + } + T *begin() { + return data(); + } + const T *end() const { + return data() + size(); + } + T *end() { + return data() + size(); + } + private: void Resize(uptr new_capacity) { CHECK_GT(new_capacity, 0); Index: lib/sanitizer_common/sanitizer_flags.h =================================================================== --- lib/sanitizer_common/sanitizer_flags.h +++ lib/sanitizer_common/sanitizer_flags.h @@ -46,6 +46,8 @@ common_flags_dont_use.CopyFrom(cf); } +void SubstituteForFlagValue(const char *s, char *out, uptr out_size); + class FlagParser; void RegisterCommonFlags(FlagParser *parser, CommonFlags *cf = &common_flags_dont_use); Index: lib/sanitizer_common/sanitizer_flags.cc =================================================================== --- lib/sanitizer_common/sanitizer_flags.cc +++ lib/sanitizer_common/sanitizer_flags.cc @@ -45,16 +45,42 @@ internal_memcpy(this, &other, sizeof(*this)); } -// Copy the string from "s" to "out", replacing "%b" with the binary basename. -static void SubstituteBinaryName(const char *s, char *out, uptr out_size) { +// Copy the string from "s" to "out", making the following substitutions: +// %b = binary basename +// %p = pid +void SubstituteForFlagValue(const char *s, char *out, uptr out_size) { char *out_end = out + out_size; while (*s && out < out_end - 1) { - if (s[0] != '%' || s[1] != 'b') { *out++ = *s++; continue; } - const char *base = GetProcessName(); - CHECK(base); - while (*base && out < out_end - 1) - *out++ = *base++; - s += 2; // skip "%b" + if (s[0] != '%') { + *out++ = *s++; + continue; + } + switch (s[1]) { + case 'b': { + const char *base = GetProcessName(); + CHECK(base); + while (*base && out < out_end - 1) + *out++ = *base++; + s += 2; // skip "%b" + break; + } + case 'p': { + int pid = internal_getpid(); + char buf[32]; + char *buf_pos = buf + 32; + do { + *--buf_pos = (pid % 10) + '0'; + pid /= 10; + } while (pid); + while (buf_pos < buf + 32 && out < out_end - 1) + *out++ = *buf_pos++; + s += 2; // skip "%p" + break; + } + default: + *out++ = *s++; + break; + } } *out = '\0'; } @@ -69,7 +95,7 @@ bool Parse(const char *value) final { if (internal_strchr(value, '%')) { char *buf = (char *)MmapOrDie(kMaxPathLength, "FlagHandlerInclude"); - SubstituteBinaryName(value, buf, kMaxPathLength); + SubstituteForFlagValue(value, buf, kMaxPathLength); bool res = parser_->ParseFile(buf, ignore_missing_); UnmapOrDie(buf, kMaxPathLength); return res; Index: lib/sanitizer_common/sanitizer_internal_defs.h =================================================================== --- lib/sanitizer_common/sanitizer_internal_defs.h +++ lib/sanitizer_common/sanitizer_internal_defs.h @@ -107,10 +107,8 @@ #else typedef u32 operator_new_size_type; #endif -} // namespace __sanitizer -using namespace __sanitizer; // NOLINT // ----------- ATTENTION ------------- // This header should NOT include any other headers to avoid portability issues. @@ -188,14 +186,12 @@ typedef thread_return_t (THREAD_CALLING_CONV *thread_callback_t)(void* arg); // NOTE: Functions below must be defined in each run-time. -namespace __sanitizer { void NORETURN Die(); // FIXME: No, this shouldn't be in the sanitizer interface. SANITIZER_INTERFACE_ATTRIBUTE void NORETURN CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2); -} // namespace __sanitizer // Check macro #define RAW_CHECK_MSG(expr, msg) do { \ @@ -287,6 +283,9 @@ #if !defined(_MSC_VER) || defined(__clang__) # define GET_CALLER_PC() (uptr)__builtin_return_address(0) # define GET_CURRENT_FRAME() (uptr)__builtin_frame_address(0) +inline void Trap() { + __builtin_trap(); +} #else extern "C" void* _ReturnAddress(void); # pragma intrinsic(_ReturnAddress) @@ -295,6 +294,12 @@ // FIXME: This macro is still used when printing error reports though it's not // clear if the BP value is needed in the ASan reports on Windows. # define GET_CURRENT_FRAME() (uptr)0xDEADBEEF + +extern "C" void __ud2(void); +# pragma intrinsic(__ud2) +inline void Trap() { + __ud2(); +} #endif #define HANDLE_EINTR(res, f) \ @@ -313,4 +318,8 @@ (void)enable_fp; \ } while (0) +} // namespace __sanitizer + +using namespace __sanitizer; // NOLINT + #endif // SANITIZER_DEFS_H Index: lib/sanitizer_common/sanitizer_symbolizer.h =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer.h +++ lib/sanitizer_common/sanitizer_symbolizer.h @@ -113,6 +113,8 @@ void AddHooks(StartSymbolizationHook start_hook, EndSymbolizationHook end_hook); + LoadedModule *FindModuleForAddress(uptr address); + private: // GetModuleNameAndOffsetForPC has to return a string to the caller. // Since the corresponding module might get unloaded later, we should create @@ -139,7 +141,6 @@ bool FindModuleNameAndOffsetForAddress(uptr address, const char **module_name, uptr *module_offset); - LoadedModule *FindModuleForAddress(uptr address); LoadedModule modules_[kMaxNumberOfModules]; uptr n_modules_; // If stale, need to reload the modules before looking up addresses. Index: lib/stats/CMakeLists.txt =================================================================== --- /dev/null +++ lib/stats/CMakeLists.txt @@ -0,0 +1,27 @@ +include_directories(..) + +add_custom_target(stats) + +if(APPLE) + set(STATS_LIB_FLAVOR SHARED) +else() + set(STATS_LIB_FLAVOR STATIC) +endif() + +add_compiler_rt_runtime(clang_rt.stats + ${STATS_LIB_FLAVOR} + ARCHS ${SANITIZER_COMMON_SUPPORTED_ARCH} + OS ${SANITIZER_COMMON_SUPPORTED_OS} + SOURCES stats.cc + OBJECT_LIBS RTSanitizerCommon + RTSanitizerCommonLibc + CFLAGS ${SANITIZER_COMMON_CFLAGS} + PARENT_TARGET stats) + +add_compiler_rt_runtime(clang_rt.stats_client + STATIC + ARCHS ${SANITIZER_COMMON_SUPPORTED_ARCH} + OS ${SANITIZER_COMMON_SUPPORTED_OS} + SOURCES stats_client.cc + CFLAGS ${SANITIZER_COMMON_CFLAGS} + PARENT_TARGET stats) Index: lib/stats/stats.h =================================================================== --- /dev/null +++ lib/stats/stats.h @@ -0,0 +1,43 @@ +//===-- stats.h -------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Data definitions for sanitizer statistics gathering. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_STATS_STATS_H +#define SANITIZER_STATS_STATS_H + +#include "sanitizer_common/sanitizer_internal_defs.h" + +namespace __sanitizer { + +// Number of bits in data that are used for the sanitizer kind. Needs to match +// llvm::kSanitizerStatKindBits in +// llvm/include/llvm/Transforms/Utils/SanitizerStats.h +enum { kKindBits = 3 }; + +struct StatInfo { + uptr addr; + uptr data; +}; + +struct StatModule { + StatModule *next; + u32 size; + StatInfo infos[1]; +}; + +inline uptr CountFromData(uptr data) { + return data & ((1ull << (sizeof(uptr) * 8 - kKindBits)) - 1); +} + +} + +#endif Index: lib/stats/stats.cc =================================================================== --- /dev/null +++ lib/stats/stats.cc @@ -0,0 +1,132 @@ +//===-- stats.cc ----------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Sanitizer statistics gathering. Manages statistics for a process and is +// responsible for writing the report file. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#if SANITIZER_POSIX +#include "sanitizer_common/sanitizer_posix.h" +#endif +#include "sanitizer_common/sanitizer_symbolizer.h" +#include "stats/stats.h" +#if SANITIZER_POSIX +#include +#endif + +using namespace __sanitizer; + +namespace { + +InternalMmapVectorNoCtor modules; + +fd_t stats_fd; + +void WriteLE(fd_t fd, uptr val) { + char chars[sizeof(uptr)]; + for (unsigned i = 0; i != sizeof(uptr); ++i) { + chars[i] = val >> (i * 8); + } + WriteToFile(fd, chars, sizeof(uptr)); +} + +void OpenStatsFile(const char *path_env) { + InternalScopedBuffer path(kMaxPathLength); + SubstituteForFlagValue(path_env, path.data(), kMaxPathLength); + + error_t err; + stats_fd = OpenFile(path.data(), WrOnly, &err); + if (stats_fd == kInvalidFd) { + Report("stats: failed to open %s for writing (reason: %d)\n", path.data(), + err); + return; + } + char sizeof_uptr = sizeof(uptr); + WriteToFile(stats_fd, &sizeof_uptr, 1); +} + +void WriteModuleReport(StatModule **smodp) { + CHECK(smodp); + const char *path_env = GetEnv("SANITIZER_STATS_PATH"); + if (!path_env || stats_fd == kInvalidFd) + return; + if (!stats_fd) + OpenStatsFile(path_env); + LoadedModule *mod = Symbolizer::GetOrInit()->FindModuleForAddress( + reinterpret_cast(smodp)); + WriteToFile(stats_fd, mod->full_name(), + internal_strlen(mod->full_name()) + 1); + for (StatModule *smod = *smodp; smod; smod = smod->next) { + for (u32 i = 0; i != smod->size; ++i) { + StatInfo *s = &smod->infos[i]; + if (!s->addr) + continue; + WriteLE(stats_fd, s->addr - mod->base_address()); + WriteLE(stats_fd, s->data); + } + } + WriteLE(stats_fd, 0); + WriteLE(stats_fd, 0); +} + +} // namespace + +extern "C" +SANITIZER_INTERFACE_ATTRIBUTE +unsigned __sanitizer_stats_register(StatModule **mod) { + modules.push_back(mod); + return modules.size() - 1; +} + +extern "C" +SANITIZER_INTERFACE_ATTRIBUTE +void __sanitizer_stats_unregister(unsigned index) { + WriteModuleReport(modules[index]); + modules[index] = 0; +} + +namespace { + +void WriteFullReport() { + for (StatModule **mod : modules) { + if (!mod) + continue; + WriteModuleReport(mod); + } + if (stats_fd != 0 && stats_fd != kInvalidFd) { + CloseFile(stats_fd); + stats_fd = kInvalidFd; + } +} + +#if SANITIZER_POSIX +void USR2Handler(int sig) { + WriteFullReport(); +} +#endif + +struct WriteReportOnExitOrSignal { + WriteReportOnExitOrSignal() { +#if SANITIZER_POSIX + struct sigaction sigact; + internal_memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = USR2Handler; + internal_sigaction(SIGUSR2, &sigact, nullptr); +#endif + } + + ~WriteReportOnExitOrSignal() { + WriteFullReport(); + } +} wr; + +} // namespace Index: lib/stats/stats_client.cc =================================================================== --- /dev/null +++ lib/stats/stats_client.cc @@ -0,0 +1,83 @@ +//===-- stats_client.cc ---------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Sanitizer statistics gathering. Manages statistics for a module (executable +// or DSO) and registers statistics with the process. +// +// This is linked into each individual modle and cannot directly use functions +// declared in sanitizer_common. +// +//===----------------------------------------------------------------------===// + +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include + +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "stats/stats.h" + +using namespace __sanitizer; + +namespace { + +void *LookupSymbolFromMain(const char *name) { +#ifdef _WIN32 + return reinterpret_cast(GetProcAddress(GetModuleHandle(0), name)); +#else + return dlsym(RTLD_DEFAULT, name); +#endif +} + +StatModule *list; + +struct RegisterSanStats { + unsigned module_id; + + RegisterSanStats() { + typedef unsigned (*reg_func_t)(StatModule **); + reg_func_t reg_func = reinterpret_cast( + LookupSymbolFromMain("__sanitizer_stats_register")); + if (reg_func) + module_id = reg_func(&list); + } + + ~RegisterSanStats() { + typedef void (*unreg_func_t)(unsigned); + unreg_func_t unreg_func = reinterpret_cast( + LookupSymbolFromMain("__sanitizer_stats_unregister")); + if (unreg_func) + unreg_func(module_id); + } +} reg; + +} + +extern "C" void __sanitizer_stat_init(StatModule *mod) { + mod->next = list; + list = mod; +} + +extern "C" void __sanitizer_stat_report(StatInfo *s) { + s->addr = GET_CALLER_PC(); +#if defined(_WIN64) && !defined(__clang__) + uptr old_data = InterlockedIncrement64(reinterpret_cast(&s->data)); +#elif defined(_WIN32) && !defined(__clang__) + uptr old_data = InterlockedIncrement(&s->data); +#else + uptr old_data = __sync_fetch_and_add(&s->data, 1); +#endif + + // Overflow check. + if (CountFromData(old_data + 1) == 0) + Trap(); +} Index: test/cfi/CMakeLists.txt =================================================================== --- test/cfi/CMakeLists.txt +++ test/cfi/CMakeLists.txt @@ -8,6 +8,8 @@ list(APPEND CFI_TEST_DEPS opt ubsan + stats + sanstats ) if(COMPILER_RT_HAS_CFI) list(APPEND CFI_TEST_DEPS cfi) Index: test/cfi/cross-dso/stats.cpp =================================================================== --- /dev/null +++ test/cfi/cross-dso/stats.cpp @@ -0,0 +1,59 @@ +// RUN: %clangxx_cfi_dso -DSHARED_LIB -fPIC -g -fsanitize-stats -shared -o %t.so %s +// RUN: %clangxx_cfi_dso -g -fsanitize-stats -o %t %s %t.so +// RUN: env SANITIZER_STATS_PATH=%t.stats %t +// RUN: sanstats %t.stats | FileCheck %s + +struct ABase {}; + +struct A : ABase { + virtual void vf() {} + void nvf() {} +}; + +extern "C" void vcall(A *a); +extern "C" void nvcall(A *a); + +#ifdef SHARED_LIB + +extern "C" __attribute__((noinline)) void vcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] vcall cfi-vcall 37 + a->vf(); +} + +extern "C" __attribute__((noinline)) void nvcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] nvcall cfi-nvcall 51 + a->nvf(); +} + +#else + +extern "C" __attribute__((noinline)) A *dcast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] dcast cfi-derived-cast 24 + return (A *)(ABase *)a; +} + +extern "C" __attribute__((noinline)) A *ucast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] ucast cfi-unrelated-cast 81 + return (A *)(char *)a; +} + +extern "C" __attribute__((noinline)) void unreachable(A *a) { + // CHECK-NOT: unreachable + a->vf(); +} + +int main() { + A a; + for (unsigned i = 0; i != 37; ++i) + vcall(&a); + for (unsigned i = 0; i != 51; ++i) + nvcall(&a); + for (unsigned i = 0; i != 24; ++i) + dcast(&a); + for (unsigned i = 0; i != 81; ++i) + ucast(&a); + for (unsigned i = 0; i != 0; ++i) + unreachable(&a); +} + +#endif Index: test/cfi/stats.cpp =================================================================== --- /dev/null +++ test/cfi/stats.cpp @@ -0,0 +1,49 @@ +// RUN: %clangxx_cfi -g -fsanitize-stats -o %t %s +// RUN: env SANITIZER_STATS_PATH=%t.stats %t +// RUN: sanstats %t.stats | FileCheck %s + +struct ABase {}; + +struct A : ABase { + virtual void vf() {} + void nvf() {} +}; + +extern "C" __attribute__((noinline)) void vcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] vcall cfi-vcall 37 + a->vf(); +} + +extern "C" __attribute__((noinline)) void nvcall(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] nvcall cfi-nvcall 51 + a->nvf(); +} + +extern "C" __attribute__((noinline)) A *dcast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] dcast cfi-derived-cast 24 + return (A *)(ABase *)a; +} + +extern "C" __attribute__((noinline)) A *ucast(A *a) { + // CHECK: stats.cpp:[[@LINE+1]] ucast cfi-unrelated-cast 81 + return (A *)(char *)a; +} + +extern "C" __attribute__((noinline)) void unreachable(A *a) { + // CHECK-NOT: unreachable + a->vf(); +} + +int main() { + A a; + for (unsigned i = 0; i != 37; ++i) + vcall(&a); + for (unsigned i = 0; i != 51; ++i) + nvcall(&a); + for (unsigned i = 0; i != 24; ++i) + dcast(&a); + for (unsigned i = 0; i != 81; ++i) + ucast(&a); + for (unsigned i = 0; i != 0; ++i) + unreachable(&a); +}