diff options
author | Kostya Serebryany <kcc@google.com> | 2012-05-10 13:48:04 +0000 |
---|---|---|
committer | Kostya Serebryany <kcc@google.com> | 2012-05-10 13:48:04 +0000 |
commit | 7ac41484ea322e0ea5774df681660269f5dc321e (patch) | |
tree | 85fd49d59eebae20d3a82770431fd26478d2611b /lib/tsan/rtl | |
parent | f2b1df7cb8f53f51bcb105e0d2930d99325bb681 (diff) |
[tsan] First commit of ThreadSanitizer (TSan) run-time library.
Algorithm description: http://code.google.com/p/thread-sanitizer/wiki/ThreadSanitizerAlgorithm
Status:
The tool is known to work on large real-life applications, but still has quite a few rough edges.
Nothing is guaranteed yet.
The tool works on x86_64 Linux.
Support for 64-bit MacOS 10.7+ is planned for late 2012.
Support for 32-bit OSes is doable, but problematic and not yet planed.
Further commits coming:
- tests
- makefiles
- documentation
- clang driver patch
The code was previously developed at http://code.google.com/p/data-race-test/source/browse/trunk/v2/
by Dmitry Vyukov and Kostya Serebryany with contributions from
Timur Iskhodzhanov, Alexander Potapenko, Alexey Samsonov and Evgeniy Stepanov.
git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@156542 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/tsan/rtl')
45 files changed, 7978 insertions, 0 deletions
diff --git a/lib/tsan/rtl/tsan_allocator.cc b/lib/tsan/rtl/tsan_allocator.cc new file mode 100644 index 000000000..4b21d1ee2 --- /dev/null +++ b/lib/tsan/rtl/tsan_allocator.cc @@ -0,0 +1,47 @@ +//===-- tsan_allocator-------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_allocator.h" + +// Provisional implementation. +extern "C" void *__libc_malloc(__tsan::uptr size); +extern "C" void __libc_free(void *ptr); + +namespace __tsan { + +u64 kBlockMagic = 0x6A6CB03ABCEBC041ull; + +void AllocInit() { +} + +void *Alloc(uptr sz) { + void *p = __libc_malloc(sz + sizeof(u64)); + ((u64*)p)[0] = kBlockMagic; + return (char*)p + sizeof(u64); +} + +void Free(void *p) { + CHECK_NE(p, (char*)0); + p = (char*)p - sizeof(u64); + CHECK_EQ(((u64*)p)[0], kBlockMagic); + ((u64*)p)[0] = 0; + __libc_free(p); +} + +void *AllocBlock(void *p) { + CHECK_NE(p, (void*)0); + u64 *pp = (u64*)((uptr)p & ~0x7); + for (; pp[0] != kBlockMagic; pp--) {} + return pp + 1; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_allocator.h b/lib/tsan/rtl/tsan_allocator.h new file mode 100644 index 000000000..7018bced2 --- /dev/null +++ b/lib/tsan/rtl/tsan_allocator.h @@ -0,0 +1,29 @@ +//===-- tsan_allocator.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_ALLOCATOR_H +#define TSAN_ALLOCATOR_H + +#include "tsan_defs.h" + +namespace __tsan { + +void AllocInit(); +void *Alloc(uptr sz); +void Free(void *p); // Does not accept NULL. +// Given the pointer p into a valid allocated block, +// returns a pointer to the beginning of the block. +void *AllocBlock(void *p); + +} // namespace __tsan + +#endif // TSAN_ALLOCATOR_H diff --git a/lib/tsan/rtl/tsan_atomic.h b/lib/tsan/rtl/tsan_atomic.h new file mode 100644 index 000000000..6fcd9f924 --- /dev/null +++ b/lib/tsan/rtl/tsan_atomic.h @@ -0,0 +1,140 @@ +//===-- tsan_rtl.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Atomic operations. For now implies IA-32/Intel64. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_ATOMIC_H +#define TSAN_ATOMIC_H + +#include "tsan_defs.h" + +namespace __tsan { + +const int kCacheLineSize = 64; + +enum memory_order { + memory_order_relaxed = 1 << 0, + memory_order_consume = 1 << 1, + memory_order_acquire = 1 << 2, + memory_order_release = 1 << 3, + memory_order_acq_rel = 1 << 4, + memory_order_seq_cst = 1 << 5, +}; + +struct atomic_uint32_t { + typedef u32 Type; + volatile Type val_dont_use; +}; + +struct atomic_uint64_t { + typedef u64 Type; + volatile Type val_dont_use; +}; + +struct atomic_uintptr_t { + typedef uptr Type; + volatile Type val_dont_use; +}; + +INLINE void atomic_signal_fence(memory_order) { + __asm__ __volatile__("" ::: "memory"); +} + +INLINE void atomic_thread_fence(memory_order) { + __asm__ __volatile__("mfence" ::: "memory"); +} + +INLINE void proc_yield(int cnt) { + __asm__ __volatile__("" ::: "memory"); + for (int i = 0; i < cnt; i++) + __asm__ __volatile__("pause"); + __asm__ __volatile__("" ::: "memory"); +} + +template<typename T> +INLINE typename T::Type atomic_load( + const volatile T *a, memory_order mo) { + DCHECK(mo & (memory_order_relaxed | memory_order_consume + | memory_order_acquire | memory_order_seq_cst)); + DCHECK(!((uptr)a % sizeof(*a))); + typename T::Type v; + if (mo == memory_order_relaxed) { + v = a->val_dont_use; + } else { + atomic_signal_fence(memory_order_seq_cst); + v = a->val_dont_use; + atomic_signal_fence(memory_order_seq_cst); + } + return v; +} + +template<typename T> +INLINE void atomic_store(volatile T *a, typename T::Type v, memory_order mo) { + DCHECK(mo & (memory_order_relaxed | memory_order_release + | memory_order_seq_cst)); + DCHECK(!((uptr)a % sizeof(*a))); + if (mo == memory_order_relaxed) { + a->val_dont_use = v; + } else { + atomic_signal_fence(memory_order_seq_cst); + a->val_dont_use = v; + atomic_signal_fence(memory_order_seq_cst); + } + if (mo == memory_order_seq_cst) + atomic_thread_fence(memory_order_seq_cst); +} + +template<typename T> +INLINE typename T::Type atomic_fetch_add(volatile T *a, + typename T::Type v, memory_order mo) { + (void)mo; + DCHECK(!((uptr)a % sizeof(*a))); + return __sync_fetch_and_add(&a->val_dont_use, v); +} + +template<typename T> +INLINE typename T::Type atomic_fetch_sub(volatile T *a, + typename T::Type v, memory_order mo) { + (void)mo; + DCHECK(!((uptr)a % sizeof(*a))); + return __sync_fetch_and_add(&a->val_dont_use, -v); +} + +INLINE uptr atomic_exchange(volatile atomic_uintptr_t *a, uptr v, + memory_order mo) { + __asm__ __volatile__("xchg %1, %0" : "+r"(v), "+m"(*a) : : "memory", "cc"); + return v; +} + +template<typename T> +INLINE bool atomic_compare_exchange_strong(volatile T *a, + typename T::Type *cmp, + typename T::Type xchg, + memory_order mo) { + typedef typename T::Type Type; + Type cmpv = *cmp; + Type prev = __sync_val_compare_and_swap(&a->val_dont_use, cmpv, xchg); + if (prev == cmpv) + return true; + *cmp = prev; + return false; +} + +INLINE bool atomic_compare_exchange_weak(volatile atomic_uintptr_t *a, + uptr *cmp, uptr xchg, + memory_order mo) { + return atomic_compare_exchange_strong(a, cmp, xchg, mo); +} + +} // namespace __tsan + +#endif // TSAN_ATOMIC_H diff --git a/lib/tsan/rtl/tsan_clock.cc b/lib/tsan/rtl/tsan_clock.cc new file mode 100644 index 000000000..2b0163271 --- /dev/null +++ b/lib/tsan/rtl/tsan_clock.cc @@ -0,0 +1,99 @@ +//===-- tsan_clock.cc -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_clock.h" +#include "tsan_rtl.h" + +// It's possible to optimize clock operations for some important cases +// so that they are O(1). The cases include singletons, once's, local mutexes. +// First, SyncClock must be re-implemented to allow indexing by tid. +// It must not necessarily be a full vector clock, though. For example it may +// be a multi-level table. +// Then, each slot in SyncClock must contain a dirty bit (it's united with +// the clock value, so no space increase). The acquire algorithm looks +// as follows: +// void acquire(thr, tid, thr_clock, sync_clock) { +// if (!sync_clock[tid].dirty) +// return; // No new info to acquire. +// // This handles constant reads of singleton pointers and +// // stop-flags. +// acquire_impl(thr_clock, sync_clock); // As usual, O(N). +// sync_clock[tid].dirty = false; +// sync_clock.dirty_count--; +// } +// The release operation looks as follows: +// void release(thr, tid, thr_clock, sync_clock) { +// // thr->sync_cache is a simple fixed-size hash-based cache that holds +// // several previous sync_clock's. +// if (thr->sync_cache[sync_clock] >= thr->last_acquire_epoch) { +// // The thread did no acquire operations since last release on this clock. +// // So update only the thread's slot (other slots can't possibly change). +// sync_clock[tid].clock = thr->epoch; +// if (sync_clock.dirty_count == sync_clock.cnt +// || (sync_clock.dirty_count == sync_clock.cnt - 1 +// && sync_clock[tid].dirty == false)) +// // All dirty flags are set, bail out. +// return; +// set all dirty bits, but preserve the thread's bit. // O(N) +// update sync_clock.dirty_count; +// return; +// } +// release_impl(thr_clock, sync_clock); // As usual, O(N). +// set all dirty bits, but preserve the thread's bit. +// // The previous step is combined with release_impl(), so that +// // we scan the arrays only once. +// update sync_clock.dirty_count; +// } + +namespace __tsan { + +ThreadClock::ThreadClock() { + nclk_ = 0; + for (uptr i = 0; i < (uptr)kMaxTid; i++) + clk_[i] = 0; +} + +void ThreadClock::acquire(const SyncClock *src) { + DCHECK(nclk_ <= kMaxTid); + DCHECK(src->clk_.Size() <= kMaxTid); + + const uptr nclk = src->clk_.Size(); + if (nclk == 0) + return; + nclk_ = max(nclk_, nclk); + for (uptr i = 0; i < nclk; i++) { + if (clk_[i] < src->clk_[i]) + clk_[i] = src->clk_[i]; + } +} + +void ThreadClock::release(SyncClock *dst) const { + DCHECK(nclk_ <= kMaxTid); + DCHECK(dst->clk_.Size() <= kMaxTid); + + if (dst->clk_.Size() < nclk_) + dst->clk_.Resize(nclk_); + for (uptr i = 0; i < nclk_; i++) { + if (dst->clk_[i] < clk_[i]) + dst->clk_[i] = clk_[i]; + } +} + +void ThreadClock::acq_rel(SyncClock *dst) { + acquire(dst); + release(dst); +} + +SyncClock::SyncClock() + : clk_(MBlockClock) { +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_clock.h b/lib/tsan/rtl/tsan_clock.h new file mode 100644 index 000000000..c4f819447 --- /dev/null +++ b/lib/tsan/rtl/tsan_clock.h @@ -0,0 +1,79 @@ +//===-- tsan_clock.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_CLOCK_H +#define TSAN_CLOCK_H + +#include "tsan_defs.h" +#include "tsan_vector.h" + +namespace __tsan { + +// The clock that lives in sync variables (mutexes, atomics, etc). +class SyncClock { + public: + SyncClock(); + + uptr size() const { + return clk_.Size(); + } + + void Reset() { + clk_.Reset(); + } + + private: + Vector<u64> clk_; + friend struct ThreadClock; +}; + +// The clock that lives in threads. +struct ThreadClock { + public: + ThreadClock(); + + u64 get(int tid) const { + DCHECK(tid < kMaxTid); + return clk_[tid]; + } + + void set(int tid, u64 v) { + DCHECK(tid < kMaxTid); + DCHECK(v >= clk_[tid]); + clk_[tid] = v; + if ((int)nclk_ <= tid) + nclk_ = tid + 1; + } + + void tick(int tid) { + DCHECK(tid < kMaxTid); + clk_[tid]++; + if ((int)nclk_ <= tid) + nclk_ = tid + 1; + } + + uptr size() const { + return nclk_; + } + + void acquire(const SyncClock *src); + void release(SyncClock *dst) const; + void acq_rel(SyncClock *dst); + + private: + uptr nclk_; + u64 clk_[kMaxTid]; +}; + +} // namespace __tsan + +#endif // TSAN_CLOCK_H diff --git a/lib/tsan/rtl/tsan_compiler.h b/lib/tsan/rtl/tsan_compiler.h new file mode 100644 index 000000000..6aab097d4 --- /dev/null +++ b/lib/tsan/rtl/tsan_compiler.h @@ -0,0 +1,30 @@ +//===-- tsan_rtl.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Compiler-specific definitions. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_COMPILER_H +#define TSAN_COMPILER_H + +#define INLINE static inline +#define NOINLINE __attribute__((noinline)) +#define ALWAYS_INLINE __attribute__((always_inline)) +#define NORETURN __attribute__((noreturn)) +#define WEAK __attribute__((weak)) +#define ALIGN(n) __attribute__((aligned(n))) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define THREADLOCAL __thread +#define FORMAT(f, a) __attribute__((format(printf, f, a))) +#define USED __attribute__((used)) + +#endif // TSAN_COMPILER_H diff --git a/lib/tsan/rtl/tsan_defs.h b/lib/tsan/rtl/tsan_defs.h new file mode 100644 index 000000000..d2088d5f3 --- /dev/null +++ b/lib/tsan/rtl/tsan_defs.h @@ -0,0 +1,194 @@ +//===-- tsan_defs.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_DEFS_H +#define TSAN_DEFS_H + +#include "tsan_compiler.h" +#include "tsan_stat.h" + +#ifndef TSAN_DEBUG +#define TSAN_DEBUG 0 +#endif // TSAN_DEBUG + +namespace __tsan { + +typedef unsigned u32; // NOLINT +typedef unsigned long long u64; // NOLINT +typedef signed long long s64; // NOLINT +typedef unsigned long uptr; // NOLINT + +const uptr kPageSize = 4096; +const int kTidBits = 16; +const int kMaxTid = 1 << kTidBits; +const int kClkBits = 40; + +#ifdef TSAN_SHADOW_COUNT +# if TSAN_SHADOW_COUNT == 2 \ + || TSAN_SHADOW_COUNT == 4 || TSAN_SHADOW_COUNT == 8 +const unsigned kShadowCnt = TSAN_SHADOW_COUNT; +# else +# error "TSAN_SHADOW_COUNT must be one of 2,4,8" +# endif +#else +// Count of shadow values in a shadow cell. +const unsigned kShadowCnt = 8; +#endif + +// That many user bytes are mapped onto a single shadow cell. +const unsigned kShadowCell = 8; + +// Size of a single shadow value (u64). +const unsigned kShadowSize = 8; + +#if defined(TSAN_COLLECT_STATS) && TSAN_COLLECT_STATS +const bool kCollectStats = true; +#else +const bool kCollectStats = false; +#endif + +#define CHECK_IMPL(c1, op, c2) \ + do { \ + __tsan::u64 v1 = (u64)(c1); \ + __tsan::u64 v2 = (u64)(c2); \ + if (!(v1 op v2)) \ + __tsan::CheckFailed(__FILE__, __LINE__, \ + "(" #c1 ") " #op " (" #c2 ")", v1, v2); \ + } while (false) \ +/**/ + +#define CHECK(a) CHECK_IMPL((a), !=, 0) +#define CHECK_EQ(a, b) CHECK_IMPL((a), ==, (b)) +#define CHECK_NE(a, b) CHECK_IMPL((a), !=, (b)) +#define CHECK_LT(a, b) CHECK_IMPL((a), <, (b)) +#define CHECK_LE(a, b) CHECK_IMPL((a), <=, (b)) +#define CHECK_GT(a, b) CHECK_IMPL((a), >, (b)) +#define CHECK_GE(a, b) CHECK_IMPL((a), >=, (b)) + +#if TSAN_DEBUG +#define DCHECK(a) CHECK(a) +#define DCHECK_EQ(a, b) CHECK_EQ(a, b) +#define DCHECK_NE(a, b) CHECK_NE(a, b) +#define DCHECK_LT(a, b) CHECK_LT(a, b) +#define DCHECK_LE(a, b) CHECK_LE(a, b) +#define DCHECK_GT(a, b) CHECK_GT(a, b) +#define DCHECK_GE(a, b) CHECK_GE(a, b) +#else +#define DCHECK(a) +#define DCHECK_EQ(a, b) +#define DCHECK_NE(a, b) +#define DCHECK_LT(a, b) +#define DCHECK_LE(a, b) +#define DCHECK_GT(a, b) +#define DCHECK_GE(a, b) +#endif + +void CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2); + +// The following "build consistency" machinery ensures that all source files +// are built in the same configuration. Inconsistent builds lead to +// hard to debug crashes. +#if TSAN_DEBUG +void build_consistency_debug(); +#else +void build_consistency_release(); +#endif + +#if TSAN_COLLECT_STATS +void build_consistency_stats(); +#else +void build_consistency_nostats(); +#endif + +#if TSAN_SHADOW_COUNT == 1 +void build_consistency_shadow1(); +#elif TSAN_SHADOW_COUNT == 2 +void build_consistency_shadow2(); +#elif TSAN_SHADOW_COUNT == 4 +void build_consistency_shadow4(); +#else +void build_consistency_shadow8(); +#endif + +static inline void USED build_consistency() { +#if TSAN_DEBUG + void(*volatile cfg)() = &build_consistency_debug; +#else + void(*volatile cfg)() = &build_consistency_release; +#endif +#if TSAN_COLLECT_STATS + void(*volatile stats)() = &build_consistency_stats; +#else + void(*volatile stats)() = &build_consistency_nostats; +#endif +#if TSAN_SHADOW_COUNT == 1 + void(*volatile shadow)() = &build_consistency_shadow1; +#elif TSAN_SHADOW_COUNT == 2 + void(*volatile shadow)() = &build_consistency_shadow2; +#elif TSAN_SHADOW_COUNT == 4 + void(*volatile shadow)() = &build_consistency_shadow4; +#else + void(*volatile shadow)() = &build_consistency_shadow8; +#endif + (void)cfg; + (void)stats; + (void)shadow; +} + +template<typename T> +T min(T a, T b) { + return a < b ? a : b; +} + +template<typename T> +T max(T a, T b) { + return a > b ? a : b; +} + +template<typename T> +T RoundUp(T p, int align) { + DCHECK_EQ(align & (align - 1), 0); + return (T)(((u64)p + align - 1) & ~(align - 1)); +} + +void internal_memset(void *ptr, int c, uptr size); +void internal_memcpy(void *dst, const void *src, uptr size); +int internal_memcmp(const void *s1, const void *s2, uptr size); +int internal_strcmp(const char *s1, const char *s2); +int internal_strncmp(const char *s1, const char *s2, uptr size); +void internal_strcpy(char *s1, const char *s2); +uptr internal_strlen(const char *s); +char* internal_strdup(const char *s); +const char *internal_strstr(const char *where, const char *what); +const char *internal_strchr(const char *where, char what); + +struct MD5Hash { + u64 hash[2]; + bool operator==(const MD5Hash &other) const { + return hash[0] == other.hash[0] && hash[1] == other.hash[1]; + } +}; + +MD5Hash md5_hash(const void *data, uptr size); + +struct ThreadState; +struct ThreadContext; +struct Context; +struct ReportStack; +class ReportDesc; +class RegionAlloc; +class StackTrace; + +} // namespace __tsan + +#endif // TSAN_DEFS_H diff --git a/lib/tsan/rtl/tsan_flags.cc b/lib/tsan/rtl/tsan_flags.cc new file mode 100644 index 000000000..77abd5b40 --- /dev/null +++ b/lib/tsan/rtl/tsan_flags.cc @@ -0,0 +1,143 @@ +//===-- tsan_flags.cc -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_flags.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" + +namespace __tsan { + +static void Flag(const char *env, bool *flag, const char *name); +static void Flag(const char *env, int *flag, const char *name); +static void Flag(const char *env, const char **flag, const char *name); + +Flags *flags() { + return &CTX()->flags; +} + +// Can be overriden in frontend. +void WEAK OverrideFlags(Flags *f) { + (void)f; +} + +void InitializeFlags(Flags *f, const char *env) { + internal_memset(f, 0, sizeof(*f)); + + // Default values. + f->enable_annotations = true; + f->suppress_equal_stacks = true; + f->suppress_equal_addresses = true; + f->report_thread_leaks = true; + f->report_signal_unsafe = true; + f->force_seq_cst_atomics = false; + f->strip_path_prefix = internal_strdup(""); + f->suppressions = internal_strdup(""); + f->exitcode = 66; + f->log_fileno = 2; + f->atexit_sleep_ms = 1000; + f->verbosity = 0; + + // Let a frontend override. + OverrideFlags(f); + + // Override from command line. + Flag(env, &f->enable_annotations, "enable_annotations"); + Flag(env, &f->suppress_equal_stacks, "suppress_equal_stacks"); + Flag(env, &f->suppress_equal_addresses, "suppress_equal_addresses"); + Flag(env, &f->report_thread_leaks, "report_thread_leaks"); + Flag(env, &f->report_signal_unsafe, "report_signal_unsafe"); + Flag(env, &f->force_seq_cst_atomics, "force_seq_cst_atomics"); + Flag(env, &f->strip_path_prefix, "strip_path_prefix"); + Flag(env, &f->suppressions, "suppressions"); + Flag(env, &f->exitcode, "exitcode"); + Flag(env, &f->log_fileno, "log_fileno"); + Flag(env, &f->atexit_sleep_ms, "atexit_sleep_ms"); + Flag(env, &f->verbosity, "verbosity"); +} + +void FinalizeFlags(Flags *flags) { + internal_free((void*)flags->strip_path_prefix); + internal_free((void*)flags->suppressions); +} + +static const char *GetFlagValue(const char *env, const char *name, + const char **end) { + if (env == 0) + return *end = 0; + const char *pos = internal_strstr(env, name); + if (pos == 0) + return *end = 0; + pos += internal_strlen(name); + if (pos[0] != '=') + return *end = pos; + pos += 1; + if (pos[0] == '"') { + pos += 1; + *end = internal_strchr(pos, '"'); + } else if (pos[0] == '\'') { + pos += 1; + *end = internal_strchr(pos, '\''); + } else { + *end = internal_strchr(pos, ' '); + } + if (*end == 0) + *end = pos + internal_strlen(pos); + return pos; +} + +static void Flag(const char *env, bool *flag, const char *name) { + const char *end = 0; + const char *val = GetFlagValue(env, name, &end); + if (val == 0) + return; + int len = end - val; + if (len == 1 && val[0] == '0') + *flag = false; + else if (len == 1 && val[0] == '1') + *flag = true; +} + +static void Flag(const char *env, int *flag, const char *name) { + const char *end = 0; + const char *val = GetFlagValue(env, name, &end); + if (val == 0) + return; + bool minus = false; + if (val != end && val[0] == '-') { + minus = true; + val += 1; + } + int v = 0; + for (; val != end; val++) { + if (val[0] < '0' || val[0] > '9') + break; + v = v * 10 + val[0] - '0'; + } + if (minus) + v = -v; + *flag = v; +} + +static void Flag(const char *env, const char **flag, const char *name) { + const char *end = 0; + const char *val = GetFlagValue(env, name, &end); + if (val == 0) + return; + int len = end - val; + char *f = (char*)internal_alloc(MBlockFlag, len + 1); + internal_memcpy(f, val, len); + f[len] = 0; + *flag = f; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_flags.h b/lib/tsan/rtl/tsan_flags.h new file mode 100644 index 000000000..1bad405a6 --- /dev/null +++ b/lib/tsan/rtl/tsan_flags.h @@ -0,0 +1,56 @@ +//===-- tsan_flags.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_FLAGS_H +#define TSAN_FLAGS_H + +namespace __tsan { + +struct Flags { + // Enable dynamic annotations, otherwise they are no-ops. + bool enable_annotations; + // Supress a race report if we've already output another race report + // with the same stack. + bool suppress_equal_stacks; + // Supress a race report if we've already output another race report + // on the same address. + bool suppress_equal_addresses; + // Report thread leaks at exit? + bool report_thread_leaks; + // Report violations of async signal-safety + // (e.g. malloc() call from a signal handler). + bool report_signal_unsafe; + // If set, all atomics are effectively sequentially consistent (seq_cst), + // regardless of what user actually specified. + bool force_seq_cst_atomics; + // Strip that prefix from file paths in reports. + const char *strip_path_prefix; + // Suppressions filename. + const char *suppressions; + // Override exit status if something was reported. + int exitcode; + // Log fileno (1 - stdout, 2 - stderr). + int log_fileno; + // Sleep in main thread before exiting for that many ms + // (useful to catch "at exit" races). + int atexit_sleep_ms; + // Verbosity level (0 - silent, 1 - a bit of output, 2+ - more output). + int verbosity; +}; + +Flags *flags(); +void InitializeFlags(Flags *flags, const char *env); +void FinalizeFlags(Flags *flags); +} + +#endif // TSAN_FLAGS_H diff --git a/lib/tsan/rtl/tsan_interceptors.cc b/lib/tsan/rtl/tsan_interceptors.cc new file mode 100644 index 000000000..3d22181fb --- /dev/null +++ b/lib/tsan/rtl/tsan_interceptors.cc @@ -0,0 +1,1402 @@ +//===-- tsan_interceptors_linux.cc ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "tsan_rtl.h" +#include "tsan_interface.h" +#include "tsan_atomic.h" +#include "tsan_platform.h" +#include "tsan_mman.h" +#include "tsan_placement_new.h" + +using namespace __tsan; // NOLINT + +struct sigset_t { + u64 val[kSigCount / 8 / sizeof(u64)]; +}; + +struct ucontext_t { + u64 opaque[1024]; +}; + +extern "C" int pthread_attr_init(void *attr); +extern "C" int pthread_attr_destroy(void *attr); +extern "C" int pthread_attr_getdetachstate(void *attr, int *v); +extern "C" int pthread_attr_setstacksize(void *attr, uptr stacksize); +extern "C" int pthread_attr_getstacksize(void *attr, uptr *stacksize); +extern "C" int pthread_key_create(unsigned *key, void (*destructor)(void* v)); +extern "C" int pthread_setspecific(unsigned key, const void *v); +extern "C" int pthread_mutexattr_gettype(void *a, int *type); +extern "C" int pthread_yield(); +extern "C" int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset); +extern "C" int sigfillset(sigset_t *set); +extern "C" void *pthread_self(); +extern "C" int getcontext(ucontext_t *ucp); +extern "C" void _exit(int status); +extern "C" int __cxa_atexit(void (*func)(void *arg), void *arg, void *dso); +extern "C" int *__errno_location(); +extern "C" int usleep(unsigned usec); +const int PTHREAD_MUTEX_RECURSIVE = 1; +const int PTHREAD_MUTEX_RECURSIVE_NP = 1; +const int kPthreadAttrSize = 56; +const int EINVAL = 22; +const int EBUSY = 16; +const int EPOLL_CTL_ADD = 1; +void *const MAP_FAILED = (void*)-1; +const int PTHREAD_BARRIER_SERIAL_THREAD = -1; +const int MAP_FIXED = 0x10; +typedef long long_t; // NOLINT + +typedef void (*sighandler_t)(int sig); + +union pthread_attr_t { + char size[kPthreadAttrSize]; + void *align; +}; + +struct sigaction_t { + union { + sighandler_t sa_handler; + void (*sa_sigaction)(int sig, my_siginfo_t *siginfo, void *uctx); + }; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(); +}; + +const sighandler_t SIG_DFL = (sighandler_t)0; +const sighandler_t SIG_IGN = (sighandler_t)1; +const int SA_SIGINFO = 4; +const int SIG_SETMASK = 2; + +static sigaction_t sigactions[kSigCount]; + +static unsigned g_thread_finalize_key; + +static void process_pending_signals(ThreadState *thr); + +class ScopedInterceptor { + public: + ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc) + : thr_(thr) + , in_rtl_(thr->in_rtl) { + if (thr_->in_rtl == 0) { + Initialize(thr); + FuncEntry(thr, pc); + thr_->in_rtl++; + DPrintf("#%d: intercept %s()\n", thr_->tid, fname); + } else { + thr_->in_rtl++; + } + } + + ~ScopedInterceptor() { + thr_->in_rtl--; + if (thr_->in_rtl == 0) { + FuncExit(thr_); + process_pending_signals(thr_); + } + CHECK_EQ(in_rtl_, thr_->in_rtl); + } + + private: + ThreadState *const thr_; + const int in_rtl_; +}; + +#define SCOPED_INTERCEPTOR_RAW(func, ...) \ + ThreadState *thr = cur_thread(); \ + StatInc(thr, StatInterceptor); \ + StatInc(thr, StatInt_##func); \ + ScopedInterceptor si(thr, #func, \ + (__tsan::uptr)__builtin_return_address(0)); \ + const uptr pc = (uptr)&func; \ + (void)pc; \ +/**/ + +#define SCOPED_TSAN_INTERCEPTOR(func, ...) \ + SCOPED_INTERCEPTOR_RAW(func, __VA_ARGS__); \ + if (thr->in_rtl > 1) \ + return REAL(func)(__VA_ARGS__); \ +/**/ + +#define TSAN_INTERCEPTOR(ret, func, ...) INTERCEPTOR(ret, func, __VA_ARGS__) +#define TSAN_INTERCEPT(func) \ + if (!INTERCEPT_FUNCTION(func)) \ + Printf("ThreadSanitizer: failed to intercept '" #func "' function\n"); \ +/**/ + +class AtExitContext { + public: + AtExitContext() + : mtx_(MutexTypeAtExit, StatMtxAtExit) + , pos_() { + } + + typedef void(*atexit_t)(); + + int atexit(ThreadState *thr, uptr pc, atexit_t f) { + Lock l(&mtx_); + if (pos_ == kMaxAtExit) + return 1; + Release(thr, pc, (uptr)this); + stack_[pos_] = f; + pos_++; + return 0; + } + + void exit(ThreadState *thr, uptr pc) { + CHECK_EQ(thr->in_rtl, 0); + for (;;) { + atexit_t f = 0; + { + Lock l(&mtx_); + if (pos_) { + pos_--; + f = stack_[pos_]; + ScopedInRtl in_rtl; + Acquire(thr, pc, (uptr)this); + } + } + if (f == 0) + break; + DPrintf("#%d: executing atexit func %p\n", thr->tid, f); + CHECK_EQ(thr->in_rtl, 0); + f(); + } + } + + private: + static const int kMaxAtExit = 128; + Mutex mtx_; + atexit_t stack_[kMaxAtExit]; + int pos_; +}; + +static AtExitContext *atexit_ctx; + +static void finalize(void *arg) { + ThreadState * thr = cur_thread(); + uptr pc = 0; + atexit_ctx->exit(thr, pc); + { + ScopedInRtl in_rtl; + DestroyAndFree(atexit_ctx); + usleep(flags()->atexit_sleep_ms * 1000); + } + int status = Finalize(cur_thread()); + _exit(status); +} + +TSAN_INTERCEPTOR(int, atexit, void (*f)()) { + SCOPED_TSAN_INTERCEPTOR(atexit, f); + return atexit_ctx->atexit(thr, pc, f); + return 0; +} + +static uptr fd2addr(int fd) { + (void)fd; + static u64 addr; + return (uptr)&addr; +} + +static uptr epollfd2addr(int fd) { + (void)fd; + static u64 addr; + return (uptr)&addr; +} + +static uptr file2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +static uptr dir2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +TSAN_INTERCEPTOR(void*, malloc, uptr size) { + SCOPED_INTERCEPTOR_RAW(malloc, size); + return user_alloc(thr, pc, size); +} + +TSAN_INTERCEPTOR(void*, calloc, uptr size, uptr n) { + SCOPED_INTERCEPTOR_RAW(calloc, size, n); + void *p = user_alloc(thr, pc, n * size); + internal_memset(p, 0, n * size); + return p; +} + +TSAN_INTERCEPTOR(void*, realloc, void *p, uptr size) { + SCOPED_INTERCEPTOR_RAW(realloc, p, size); + return user_realloc(thr, pc, p, size); +} + +TSAN_INTERCEPTOR(void, free, void *p) { + if (p == 0) + return; + SCOPED_INTERCEPTOR_RAW(free, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(void, cfree, void *p) { + if (p == 0) + return; + SCOPED_INTERCEPTOR_RAW(cfree, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(uptr, strlen, const void *s) { + SCOPED_TSAN_INTERCEPTOR(strlen, s); + uptr len = REAL(strlen)(s); + MemoryAccessRange(thr, pc, (uptr)s, len + 1, false); + return len; +} + +TSAN_INTERCEPTOR(void*, memset, void *dst, int v, uptr size) { + SCOPED_TSAN_INTERCEPTOR(memset, dst, v, size); + MemoryAccessRange(thr, pc, (uptr)dst, size, true); + return REAL(memset)(dst, v, size); +} + +TSAN_INTERCEPTOR(void*, memcpy, void *dst, const void *src, uptr size) { + SCOPED_TSAN_INTERCEPTOR(memcpy, dst, src, size); + MemoryAccessRange(thr, pc, (uptr)dst, size, true); + MemoryAccessRange(thr, pc, (uptr)src, size, false); + return REAL(memcpy)(dst, src, size); +} + +TSAN_INTERCEPTOR(int, strcmp, const char *s1, const char *s2) { + SCOPED_TSAN_INTERCEPTOR(strcmp, s1, s2); + uptr len = 0; + for (; s1[len] && s2[len]; len++) { + if (s1[len] != s2[len]) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len + 1, false); + MemoryAccessRange(thr, pc, (uptr)s2, len + 1, false); + return s1[len] - s2[len]; +} + +TSAN_INTERCEPTOR(int, strncmp, const char *s1, const char *s2, uptr n) { + SCOPED_TSAN_INTERCEPTOR(strncmp, s1, s2, n); + uptr len = 0; + for (; s1[len] && s2[len] && len < n; len++) { + if (s1[len] != s2[len]) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len < n ? len + 1 : n, false); + MemoryAccessRange(thr, pc, (uptr)s2, len < n ? len + 1 : n, false); + return len == n ? 0 : s1[len] - s2[len]; +} + +TSAN_INTERCEPTOR(void*, memchr, void *s, int c, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memchr, s, c, n); + void *res = REAL(memchr)(s, c, n); + uptr len = res ? (char*)res - (char*)s + 1 : n; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(void*, memrchr, char *s, int c, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memrchr, s, c, n); + MemoryAccessRange(thr, pc, (uptr)s, n, false); + return REAL(memrchr)(s, c, n); +} + +TSAN_INTERCEPTOR(void*, memmove, void *dst, void *src, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memmove, dst, src, n); + MemoryAccessRange(thr, pc, (uptr)dst, n, true); + MemoryAccessRange(thr, pc, (uptr)src, n, false); + return REAL(memmove)(dst, src, n); +} + +TSAN_INTERCEPTOR(int, memcmp, const void *s1, const void *s2, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memcmp, s1, s2, n); + int res = 0; + uptr len = 0; + for (; len < n; len++) { + if ((res = ((unsigned char*)s1)[len] - ((unsigned char*)s2)[len])) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len < n ? len + 1 : n, false); + MemoryAccessRange(thr, pc, (uptr)s2, len < n ? len + 1 : n, false); + return res; +} + +TSAN_INTERCEPTOR(void*, strchr, void *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strchr, s, c); + void *res = REAL(strchr)(s, c); + uptr len = res ? (char*)res - (char*)s + 1 : REAL(strlen)(s) + 1; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(void*, strchrnul, void *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strchrnul, s, c); + void *res = REAL(strchrnul)(s, c); + uptr len = (char*)res - (char*)s + 1; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(void*, strrchr, void *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strrchr, s, c); + MemoryAccessRange(thr, pc, (uptr)s, REAL(strlen)(s) + 1, false); + return REAL(strrchr)(s, c); +} + +TSAN_INTERCEPTOR(void*, strcpy, void *dst, const void *src) { // NOLINT + SCOPED_TSAN_INTERCEPTOR(strcpy, dst, src); // NOLINT + uptr srclen = REAL(strlen)(src); + MemoryAccessRange(thr, pc, (uptr)dst, srclen + 1, true); + MemoryAccessRange(thr, pc, (uptr)src, srclen + 1, false); + return REAL(strcpy)(dst, src); // NOLINT +} + +TSAN_INTERCEPTOR(void*, strncpy, void *dst, void *src, uptr n) { + SCOPED_TSAN_INTERCEPTOR(strncpy, dst, src, n); + uptr srclen = REAL(strlen)(src); + MemoryAccessRange(thr, pc, (uptr)dst, n, true); + MemoryAccessRange(thr, pc, (uptr)src, min(srclen + 1, n), false); + return REAL(strncpy)(dst, src, n); +} + +TSAN_INTERCEPTOR(const char*, strstr, const char *s1, const char *s2) { + SCOPED_TSAN_INTERCEPTOR(strstr, s1, s2); + const char *res = REAL(strstr)(s1, s2); + uptr len1 = REAL(strlen)(s1); + uptr len2 = REAL(strlen)(s2); + MemoryAccessRange(thr, pc, (uptr)s1, len1 + 1, false); + MemoryAccessRange(thr, pc, (uptr)s2, len2 + 1, false); + return res; +} + +static bool fix_mmap_addr(void **addr, long_t sz, int flags) { + if (*addr) { + if (!IsAppMem((uptr)*addr) || !IsAppMem((uptr)*addr + sz - 1)) { + if (flags & MAP_FIXED) { + *__errno_location() = EINVAL; + return false; + } else { + *addr = 0; + } + } + } + return true; +} + +TSAN_INTERCEPTOR(void*, mmap, void *addr, long_t sz, int prot, + int flags, int fd, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(mmap, addr, sz, prot, flags, fd, off); + if (!fix_mmap_addr(&addr, sz, flags)) + return MAP_FAILED; + void *res = REAL(mmap)(addr, sz, prot, flags, fd, off); + if (res != MAP_FAILED) { + MemoryResetRange(thr, pc, (uptr)res, sz); + } + return res; +} + +TSAN_INTERCEPTOR(void*, mmap64, void *addr, long_t sz, int prot, + int flags, int fd, u64 off) { + SCOPED_TSAN_INTERCEPTOR(mmap64, addr, sz, prot, flags, fd, off); + if (!fix_mmap_addr(&addr, sz, flags)) + return MAP_FAILED; + void *res = REAL(mmap64)(addr, sz, prot, flags, fd, off); + if (res != MAP_FAILED) { + MemoryResetRange(thr, pc, (uptr)res, sz); + } + return res; +} + +TSAN_INTERCEPTOR(int, munmap, void *addr, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(munmap, addr, sz); + int res = REAL(munmap)(addr, sz); + return res; +} + +#ifdef __LP64__ + +// void *operator new(size_t) +TSAN_INTERCEPTOR(void*, _Znwm, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(_Znwm, sz); + return user_alloc(thr, pc, sz); +} + +// void *operator new(size_t, nothrow_t) +TSAN_INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(_ZnwmRKSt9nothrow_t, sz); + return user_alloc(thr, pc, sz); +} + +// void *operator new[](size_t) +TSAN_INTERCEPTOR(void*, _Znam, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(_Znam, sz); + return user_alloc(thr, pc, sz); +} + +// void *operator new[](size_t, nothrow_t) +TSAN_INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(_ZnamRKSt9nothrow_t, sz); + return user_alloc(thr, pc, sz); +} + +#else +#error "Not implemented" +#endif + +// void operator delete(void*) +TSAN_INTERCEPTOR(void, _ZdlPv, void *p) { + if (p == 0) + return; + SCOPED_TSAN_INTERCEPTOR(_ZdlPv, p); + user_free(thr, pc, p); +} + +// void operator delete(void*, nothrow_t) +TSAN_INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void *p) { + if (p == 0) + return; + SCOPED_TSAN_INTERCEPTOR(_ZdlPvRKSt9nothrow_t, p); + user_free(thr, pc, p); +} + +// void operator delete[](void*) +TSAN_INTERCEPTOR(void, _ZdaPv, void *p) { + if (p == 0) + return; + SCOPED_TSAN_INTERCEPTOR(_ZdaPv, p); + user_free(thr, pc, p); +} + +// void operator delete[](void*, nothrow_t) +TSAN_INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void *p) { + if (p == 0) + return; + SCOPED_TSAN_INTERCEPTOR(_ZdaPvRKSt9nothrow_t, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(void*, memalign, uptr align, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(memalign, align, sz); + return user_alloc_aligned(thr, pc, sz, align); +} + +TSAN_INTERCEPTOR(void*, valloc, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(valloc, sz); + return user_alloc_aligned(thr, pc, sz, kPageSize); +} + +TSAN_INTERCEPTOR(void*, pvalloc, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(pvalloc, sz); + sz = RoundUp(sz, kPageSize); + return user_alloc_aligned(thr, pc, sz, kPageSize); +} + +TSAN_INTERCEPTOR(int, posix_memalign, void **memptr, uptr align, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(posix_memalign, memptr, align, sz); + *memptr = user_alloc_aligned(thr, pc, sz, align); + return 0; +} + +// Used in thread-safe function static initialization. +TSAN_INTERCEPTOR(int, __cxa_guard_acquire, char *m) { + SCOPED_TSAN_INTERCEPTOR(__cxa_guard_acquire, m); + int res = REAL(__cxa_guard_acquire)(m); + if (res) { + // This thread does the init. + } else { + Acquire(thr, pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(void, __cxa_guard_release, char *m) { + SCOPED_TSAN_INTERCEPTOR(__cxa_guard_release, m); + Release(thr, pc, (uptr)m); + REAL(__cxa_guard_release)(m); +} + +static void thread_finalize(void *v) { + uptr iter = (uptr)v; + if (iter > 1) { + if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) { + Printf("ThreadSanitizer: failed to set thread key\n"); + Die(); + } + return; + } + { + ScopedInRtl in_rtl; + ThreadFinish(cur_thread()); + } +} + + +struct ThreadParam { + void* (*callback)(void *arg); + void *param; + atomic_uintptr_t tid; +}; + +extern "C" void *__tsan_thread_start_func(void *arg) { + ThreadParam *p = (ThreadParam*)arg; + void* (*callback)(void *arg) = p->callback; + void *param = p->param; + int tid = 0; + { + ThreadState *thr = cur_thread(); + ScopedInRtl in_rtl; + if (pthread_setspecific(g_thread_finalize_key, (void*)4)) { + Printf("ThreadSanitizer: failed to set thread key\n"); + Die(); + } + while ((tid = atomic_load(&p->tid, memory_order_acquire)) == 0) + pthread_yield(); + atomic_store(&p->tid, 0, memory_order_release); + ThreadStart(thr, tid); + CHECK_EQ(thr->in_rtl, 1); + } + void *res = callback(param); + // Prevent the callback from being tail called, + // it mixes up stack traces. + volatile int foo = 42; + foo++; + return res; +} + +TSAN_INTERCEPTOR(int, pthread_create, + void *th, void *attr, void *(*callback)(void*), void * param) { + SCOPED_TSAN_INTERCEPTOR(pthread_create, th, attr, callback, param); + pthread_attr_t myattr; + if (attr == 0) { + pthread_attr_init(&myattr); + attr = &myattr; + } + int detached = 0; + pthread_attr_getdetachstate(attr, &detached); + uptr stacksize = 0; + pthread_attr_getstacksize(attr, &stacksize); + // We place the huge ThreadState object into TLS, account for that. + const uptr minstacksize = GetTlsSize() + 128*1024; + if (stacksize < minstacksize) { + DPrintf("ThreadSanitizer: stacksize %lu->%lu\n", stacksize, minstacksize); + pthread_attr_setstacksize(attr, minstacksize); + } + ThreadParam p; + p.callback = callback; + p.param = param; + atomic_store(&p.tid, 0, memory_order_relaxed); + int res = REAL(pthread_create)(th, attr, __tsan_thread_start_func, &p); + if (res == 0) { + int tid = ThreadCreate(cur_thread(), pc, *(uptr*)th, detached); + CHECK_NE(tid, 0); + atomic_store(&p.tid, tid, memory_order_release); + while (atomic_load(&p.tid, memory_order_acquire) != 0) + pthread_yield(); + } + if (attr == &myattr) + pthread_attr_destroy(&myattr); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_join, void *th, void **ret) { + SCOPED_TSAN_INTERCEPTOR(pthread_join, th, ret); + int tid = ThreadTid(thr, pc, (uptr)th); + int res = REAL(pthread_join)(th, ret); + if (res == 0) { + ThreadJoin(cur_thread(), pc, tid); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_detach, void *th) { + SCOPED_TSAN_INTERCEPTOR(pthread_detach, th); + int tid = ThreadTid(thr, pc, (uptr)th); + int res = REAL(pthread_detach)(th); + if (res == 0) { + ThreadDetach(cur_thread(), pc, tid); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_init, void *m, void *a) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_init, m, a); + int res = REAL(pthread_mutex_init)(m, a); + if (res == 0) { + bool recursive = false; + if (a) { + int type = 0; + if (pthread_mutexattr_gettype(a, &type) == 0) + recursive = (type == PTHREAD_MUTEX_RECURSIVE + || type == PTHREAD_MUTEX_RECURSIVE_NP); + } + MutexCreate(cur_thread(), pc, (uptr)m, false, recursive); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_destroy, m); + int res = REAL(pthread_mutex_destroy)(m); + if (res == 0 || res == EBUSY) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_lock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_lock, m); + int res = REAL(pthread_mutex_lock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_trylock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_trylock, m); + int res = REAL(pthread_mutex_trylock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_timedlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_timedlock, m, abstime); + int res = REAL(pthread_mutex_timedlock)(m, abstime); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_unlock, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_mutex_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_init, void *m, int pshared) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_init, m, pshared); + int res = REAL(pthread_spin_init)(m, pshared); + if (res == 0) { + MutexCreate(cur_thread(), pc, (uptr)m, false, false); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_destroy, m); + int res = REAL(pthread_spin_destroy)(m); + if (res == 0) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_lock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_lock, m); + int res = REAL(pthread_spin_lock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_trylock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_trylock, m); + int res = REAL(pthread_spin_trylock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_unlock, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_spin_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_init, void *m, void *a) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_init, m, a); + int res = REAL(pthread_rwlock_init)(m, a); + if (res == 0) { + MutexCreate(cur_thread(), pc, (uptr)m, true, false); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_destroy, m); + int res = REAL(pthread_rwlock_destroy)(m); + if (res == 0) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_rdlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_rdlock, m); + int res = REAL(pthread_rwlock_rdlock)(m); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_tryrdlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_tryrdlock, m); + int res = REAL(pthread_rwlock_tryrdlock)(m); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_timedrdlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedrdlock, m, abstime); + int res = REAL(pthread_rwlock_timedrdlock)(m, abstime); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_wrlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_wrlock, m); + int res = REAL(pthread_rwlock_wrlock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_trywrlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_trywrlock, m); + int res = REAL(pthread_rwlock_trywrlock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_timedwrlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedwrlock, m, abstime); + int res = REAL(pthread_rwlock_timedwrlock)(m, abstime); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_unlock, m); + MutexReadOrWriteUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_rwlock_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_init, void *c, void *a) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, c, a); + int res = REAL(pthread_cond_init)(c, a); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_destroy, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, c); + int res = REAL(pthread_cond_destroy)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_signal, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_signal, c); + int res = REAL(pthread_cond_signal)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_broadcast, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_broadcast, c); + int res = REAL(pthread_cond_broadcast)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_wait, c, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_cond_wait)(c, m); + MutexLock(cur_thread(), pc, (uptr)m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_timedwait, c, m, abstime); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_cond_timedwait)(c, m, abstime); + MutexLock(cur_thread(), pc, (uptr)m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_init, void *b, void *a, unsigned count) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_init, b, a, count); + MemoryWrite1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_init)(b, a, count); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_destroy, void *b) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_destroy, b); + MemoryWrite1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_destroy)(b); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_wait, void *b) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_wait, b); + Release(cur_thread(), pc, (uptr)b); + MemoryRead1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_wait)(b); + MemoryRead1Byte(thr, pc, (uptr)b); + if (res == 0 || res == PTHREAD_BARRIER_SERIAL_THREAD) { + Acquire(cur_thread(), pc, (uptr)b); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_once, void *o, void (*f)()) { + SCOPED_TSAN_INTERCEPTOR(pthread_once, o, f); + if (o == 0 || f == 0) + return EINVAL; + atomic_uint32_t *a = static_cast<atomic_uint32_t*>(o); + u32 v = atomic_load(a, memory_order_acquire); + if (v == 0 && atomic_compare_exchange_strong(a, &v, 1, + memory_order_relaxed)) { + const int old_in_rtl = thr->in_rtl; + thr->in_rtl = 0; + (*f)(); + CHECK_EQ(thr->in_rtl, 0); + thr->in_rtl = old_in_rtl; + Release(cur_thread(), pc, (uptr)o); + atomic_store(a, 2, memory_order_release); + } else { + while (v != 2) { + pthread_yield(); + v = atomic_load(a, memory_order_acquire); + } + Acquire(cur_thread(), pc, (uptr)o); + } + return 0; +} + +TSAN_INTERCEPTOR(int, sem_init, void *s, int pshared, unsigned value) { + SCOPED_TSAN_INTERCEPTOR(sem_init, s, pshared, value); + int res = REAL(sem_init)(s, pshared, value); + return res; +} + +TSAN_INTERCEPTOR(int, sem_destroy, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_destroy, s); + int res = REAL(sem_destroy)(s); + return res; +} + +TSAN_INTERCEPTOR(int, sem_wait, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_wait, s); + int res = REAL(sem_wait)(s); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_trywait, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_trywait, s); + int res = REAL(sem_trywait)(s); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_timedwait, void *s, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(sem_timedwait, s, abstime); + int res = REAL(sem_timedwait)(s, abstime); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_post, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_post, s); + Release(cur_thread(), pc, (uptr)s); + int res = REAL(sem_post)(s); + return res; +} + +TSAN_INTERCEPTOR(int, sem_getvalue, void *s, int *sval) { + SCOPED_TSAN_INTERCEPTOR(sem_getvalue, s, sval); + int res = REAL(sem_getvalue)(s, sval); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(read, fd, buf, sz); + int res = REAL(read)(fd, buf, sz); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pread, fd, buf, sz, off); + int res = REAL(pread)(fd, buf, sz, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) { + SCOPED_TSAN_INTERCEPTOR(pread64, fd, buf, sz, off); + int res = REAL(pread64)(fd, buf, sz, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { + SCOPED_TSAN_INTERCEPTOR(readv, fd, vec, cnt); + int res = REAL(readv)(fd, vec, cnt); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, preadv64, int fd, void *vec, int cnt, u64 off) { + SCOPED_TSAN_INTERCEPTOR(preadv64, fd, vec, cnt, off); + int res = REAL(preadv64)(fd, vec, cnt, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, write, int fd, void *buf, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(write, fd, buf, sz); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(write)(fd, buf, sz); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwrite, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pwrite, fd, buf, sz, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwrite)(fd, buf, sz, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwrite64, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pwrite64, fd, buf, sz, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwrite64)(fd, buf, sz, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, writev, int fd, void *vec, int cnt) { + SCOPED_TSAN_INTERCEPTOR(writev, fd, vec, cnt); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(writev)(fd, vec, cnt); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwritev64, int fd, void *vec, int cnt, u64 off) { + SCOPED_TSAN_INTERCEPTOR(pwritev64, fd, vec, cnt, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwritev64)(fd, vec, cnt, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, send, int fd, void *buf, long_t len, int flags) { + SCOPED_TSAN_INTERCEPTOR(send, fd, buf, len, flags); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(send)(fd, buf, len, flags); + return res; +} + +TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { + SCOPED_TSAN_INTERCEPTOR(sendmsg, fd, msg, flags); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(sendmsg)(fd, msg, flags); + return res; +} + +TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { + SCOPED_TSAN_INTERCEPTOR(recv, fd, buf, len, flags); + int res = REAL(recv)(fd, buf, len, flags); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, recvmsg, int fd, void *msg, int flags) { + SCOPED_TSAN_INTERCEPTOR(recvmsg, fd, msg, flags); + int res = REAL(recvmsg)(fd, msg, flags); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(int, unlink, char *path) { + SCOPED_TSAN_INTERCEPTOR(unlink, path); + Release(cur_thread(), pc, file2addr(path)); + int res = REAL(unlink)(path); + return res; +} + +TSAN_INTERCEPTOR(void*, fopen, char *path, char *mode) { + SCOPED_TSAN_INTERCEPTOR(fopen, path, mode); + void *res = REAL(fopen)(path, mode); + Acquire(cur_thread(), pc, file2addr(path)); + return res; +} + +TSAN_INTERCEPTOR(uptr, fread, void *ptr, uptr size, uptr nmemb, void *f) { + SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + return REAL(fread)(ptr, size, nmemb, f); +} + +TSAN_INTERCEPTOR(uptr, fwrite, const void *p, uptr size, uptr nmemb, void *f) { + SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + return REAL(fwrite)(p, size, nmemb, f); +} + +TSAN_INTERCEPTOR(int, puts, const char *s) { + SCOPED_TSAN_INTERCEPTOR(puts, s); + MemoryAccessRange(thr, pc, (uptr)s, REAL(strlen)(s), false); + return REAL(puts)(s); +} + +TSAN_INTERCEPTOR(int, rmdir, char *path) { + SCOPED_TSAN_INTERCEPTOR(rmdir, path); + Release(cur_thread(), pc, dir2addr(path)); + int res = REAL(rmdir)(path); + return res; +} + +TSAN_INTERCEPTOR(void*, opendir, char *path) { + SCOPED_TSAN_INTERCEPTOR(opendir, path); + void *res = REAL(opendir)(path); + Acquire(cur_thread(), pc, dir2addr(path)); + return res; +} + +TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) { + SCOPED_TSAN_INTERCEPTOR(epoll_ctl, epfd, op, fd, ev); + if (op == EPOLL_CTL_ADD) { + Release(cur_thread(), pc, epollfd2addr(epfd)); + } + int res = REAL(epoll_ctl)(epfd, op, fd, ev); + return res; +} + +TSAN_INTERCEPTOR(int, epoll_wait, int epfd, void *ev, int cnt, int timeout) { + SCOPED_TSAN_INTERCEPTOR(epoll_wait, epfd, ev, cnt, timeout); + int res = REAL(epoll_wait)(epfd, ev, cnt, timeout); + if (res > 0) { + Acquire(cur_thread(), pc, epollfd2addr(epfd)); + } + return res; +} + +static void rtl_sighandler(int sig) { + ThreadState *thr = cur_thread(); + SignalDesc *signal = &thr->pending_signals[sig]; + if (signal->armed == false) { + signal->armed = true; + signal->sigaction = false; + thr->pending_signal_count++; + } +} + +static void rtl_sigaction(int sig, my_siginfo_t *info, void *ctx) { + ThreadState *thr = cur_thread(); + SignalDesc *signal = &thr->pending_signals[sig]; + if (signal->armed == false) { + signal->armed = true; + signal->sigaction = true; + signal->siginfo = *info; + thr->pending_signal_count++; + } +} + +TSAN_INTERCEPTOR(int, sigaction, int sig, sigaction_t *act, sigaction_t *old) { + SCOPED_TSAN_INTERCEPTOR(sigaction, sig, act, old); + int res = 0; + if (act == 0 || act->sa_handler == SIG_IGN || act->sa_handler == SIG_DFL) { + res = REAL(sigaction)(sig, act, old); + } else { + sigactions[sig] = *act; + sigaction_t newact = *act; + if (newact.sa_flags & SA_SIGINFO) + newact.sa_sigaction = rtl_sigaction; + else + newact.sa_handler = rtl_sighandler; + res = REAL(sigaction)(sig, &newact, old); + } + return res; +} + +static void process_pending_signals(ThreadState *thr) { + CHECK_EQ(thr->in_rtl, 0); + if (thr->pending_signal_count == 0 || thr->in_signal_handler) + return; + thr->in_signal_handler = true; + thr->pending_signal_count = 0; + // These are too big for stack. + static THREADLOCAL ucontext_t uctx; + static THREADLOCAL sigset_t emptyset, oldset; + getcontext(&uctx); + sigfillset(&emptyset); + pthread_sigmask(SIG_SETMASK, &emptyset, &oldset); + for (int sig = 0; sig < kSigCount; sig++) { + SignalDesc *signal = &thr->pending_signals[sig]; + if (signal->armed) { + signal->armed = false; + if (signal->sigaction) + sigactions[sig].sa_sigaction(sig, &signal->siginfo, &uctx); + else + sigactions[sig].sa_handler(sig); + } + } + pthread_sigmask(SIG_SETMASK, &oldset, 0); + CHECK_EQ(thr->in_signal_handler, true); + thr->in_signal_handler = false; +} + +namespace __tsan { + +// Used until we obtain real efficient functions. +static void* poormans_memset(void *dst, int v, uptr size) { + for (uptr i = 0; i < size; i++) + ((char*)dst)[i] = (char)v; + return dst; +} + +static void* poormans_memcpy(void *dst, const void *src, uptr size) { + for (uptr i = 0; i < size; i++) + ((char*)dst)[i] = ((char*)src)[i]; + return dst; +} + +void InitializeInterceptors() { + CHECK_GT(cur_thread()->in_rtl, 0); + + // We need to setup it early, because functions like dlsym() can call it. + REAL(memset) = poormans_memset; + REAL(memcpy) = poormans_memcpy; + + TSAN_INTERCEPT(malloc); + TSAN_INTERCEPT(calloc); + TSAN_INTERCEPT(realloc); + TSAN_INTERCEPT(free); + TSAN_INTERCEPT(cfree); + TSAN_INTERCEPT(mmap); + TSAN_INTERCEPT(mmap64); + TSAN_INTERCEPT(munmap); + TSAN_INTERCEPT(memalign); + TSAN_INTERCEPT(valloc); + TSAN_INTERCEPT(pvalloc); + TSAN_INTERCEPT(posix_memalign); + + TSAN_INTERCEPT(_Znwm); + TSAN_INTERCEPT(_ZnwmRKSt9nothrow_t); + TSAN_INTERCEPT(_Znam); + TSAN_INTERCEPT(_ZnamRKSt9nothrow_t); + TSAN_INTERCEPT(_ZdlPv); + TSAN_INTERCEPT(_ZdlPvRKSt9nothrow_t); + TSAN_INTERCEPT(_ZdaPv); + TSAN_INTERCEPT(_ZdaPvRKSt9nothrow_t); + + TSAN_INTERCEPT(strlen); + TSAN_INTERCEPT(memset); + TSAN_INTERCEPT(memcpy); + TSAN_INTERCEPT(strcmp); + TSAN_INTERCEPT(memchr); + TSAN_INTERCEPT(memrchr); + TSAN_INTERCEPT(memmove); + TSAN_INTERCEPT(memcmp); + TSAN_INTERCEPT(strchr); + TSAN_INTERCEPT(strchrnul); + TSAN_INTERCEPT(strrchr); + TSAN_INTERCEPT(strncmp); + TSAN_INTERCEPT(strcpy); // NOLINT + TSAN_INTERCEPT(strncpy); + TSAN_INTERCEPT(strstr); + + TSAN_INTERCEPT(__cxa_guard_acquire); + TSAN_INTERCEPT(__cxa_guard_release); + + TSAN_INTERCEPT(pthread_create); + TSAN_INTERCEPT(pthread_join); + TSAN_INTERCEPT(pthread_detach); + + TSAN_INTERCEPT(pthread_mutex_init); + TSAN_INTERCEPT(pthread_mutex_destroy); + TSAN_INTERCEPT(pthread_mutex_lock); + TSAN_INTERCEPT(pthread_mutex_trylock); + TSAN_INTERCEPT(pthread_mutex_timedlock); + TSAN_INTERCEPT(pthread_mutex_unlock); + + TSAN_INTERCEPT(pthread_spin_init); + TSAN_INTERCEPT(pthread_spin_destroy); + TSAN_INTERCEPT(pthread_spin_lock); + TSAN_INTERCEPT(pthread_spin_trylock); + TSAN_INTERCEPT(pthread_spin_unlock); + + TSAN_INTERCEPT(pthread_rwlock_init); + TSAN_INTERCEPT(pthread_rwlock_destroy); + TSAN_INTERCEPT(pthread_rwlock_rdlock); + TSAN_INTERCEPT(pthread_rwlock_tryrdlock); + TSAN_INTERCEPT(pthread_rwlock_timedrdlock); + TSAN_INTERCEPT(pthread_rwlock_wrlock); + TSAN_INTERCEPT(pthread_rwlock_trywrlock); + TSAN_INTERCEPT(pthread_rwlock_timedwrlock); + TSAN_INTERCEPT(pthread_rwlock_unlock); + + TSAN_INTERCEPT(pthread_cond_init); + TSAN_INTERCEPT(pthread_cond_destroy); + TSAN_INTERCEPT(pthread_cond_signal); + TSAN_INTERCEPT(pthread_cond_broadcast); + TSAN_INTERCEPT(pthread_cond_wait); + TSAN_INTERCEPT(pthread_cond_timedwait); + + TSAN_INTERCEPT(pthread_barrier_init); + TSAN_INTERCEPT(pthread_barrier_destroy); + TSAN_INTERCEPT(pthread_barrier_wait); + + TSAN_INTERCEPT(pthread_once); + + TSAN_INTERCEPT(sem_init); + TSAN_INTERCEPT(sem_destroy); + TSAN_INTERCEPT(sem_wait); + TSAN_INTERCEPT(sem_trywait); + TSAN_INTERCEPT(sem_timedwait); + TSAN_INTERCEPT(sem_post); + TSAN_INTERCEPT(sem_getvalue); + + TSAN_INTERCEPT(read); + TSAN_INTERCEPT(pread); + TSAN_INTERCEPT(pread64); + TSAN_INTERCEPT(readv); + TSAN_INTERCEPT(preadv64); + TSAN_INTERCEPT(write); + TSAN_INTERCEPT(pwrite); + TSAN_INTERCEPT(pwrite64); + TSAN_INTERCEPT(writev); + TSAN_INTERCEPT(pwritev64); + TSAN_INTERCEPT(send); + TSAN_INTERCEPT(sendmsg); + TSAN_INTERCEPT(recv); + TSAN_INTERCEPT(recvmsg); + + TSAN_INTERCEPT(unlink); + TSAN_INTERCEPT(fopen); + TSAN_INTERCEPT(fread); + TSAN_INTERCEPT(fwrite); + TSAN_INTERCEPT(puts); + TSAN_INTERCEPT(rmdir); + TSAN_INTERCEPT(opendir); + + TSAN_INTERCEPT(epoll_ctl); + TSAN_INTERCEPT(epoll_wait); + + TSAN_INTERCEPT(sigaction); + + atexit_ctx = new(internal_alloc(MBlockAtExit, sizeof(AtExitContext))) + AtExitContext(); + + if (__cxa_atexit(&finalize, 0, 0)) { + Printf("ThreadSanitizer: failed to setup atexit callback\n"); + Die(); + } + + if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) { + Printf("ThreadSanitizer: failed to create thread key\n"); + Die(); + } +} + +void internal_memset(void *ptr, int c, uptr size) { + REAL(memset)(ptr, c, size); +} + +void internal_memcpy(void *dst, const void *src, uptr size) { + REAL(memcpy)(dst, src, size); +} + +int internal_memcmp(const void *s1, const void *s2, uptr size) { + return REAL(memcmp)(s1, s2, size); +} + +int internal_strcmp(const char *s1, const char *s2) { + return REAL(strcmp)(s1, s2); +} + +int internal_strncmp(const char *s1, const char *s2, uptr size) { + return REAL(strncmp)(s1, s2, size); +} + +void internal_strcpy(char *s1, const char *s2) { + REAL(strcpy)(s1, s2); // NOLINT +} + +uptr internal_strlen(const char *s) { + return REAL(strlen)(s); +} + +char* internal_strdup(const char *s) { + uptr len = internal_strlen(s); + char *s2 = (char*)internal_alloc(MBlockString, len + 1); + internal_memcpy(s2, s, len); + s2[len] = 0; + return s2; +} + +const char *internal_strstr(const char *where, const char *what) { + return REAL(strstr)(where, what); +} + +const char *internal_strchr(const char *where, char what) { + return (const char*)REAL(strchr)((void*)where, what); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_interface.cc b/lib/tsan/rtl/tsan_interface.cc new file mode 100644 index 000000000..b7b008564 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface.cc @@ -0,0 +1,42 @@ +//===-- tsan_interface.cc ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface.h" +#include "tsan_interface_ann.h" +#include "tsan_rtl.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +void __tsan_init() { + Initialize(cur_thread()); +} + +void __tsan_read16(void *addr) { + MemoryRead8Byte(cur_thread(), CALLERPC, (uptr)addr); + MemoryRead8Byte(cur_thread(), CALLERPC, (uptr)addr + 8); +} + +void __tsan_write16(void *addr) { + MemoryWrite8Byte(cur_thread(), CALLERPC, (uptr)addr); + MemoryWrite8Byte(cur_thread(), CALLERPC, (uptr)addr + 8); +} + +void __tsan_acquire(void *addr) { + Acquire(cur_thread(), CALLERPC, (uptr)addr); +} + +void __tsan_release(void *addr) { + Release(cur_thread(), CALLERPC, (uptr)addr); +} diff --git a/lib/tsan/rtl/tsan_interface.h b/lib/tsan/rtl/tsan_interface.h new file mode 100644 index 000000000..ed21ec605 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface.h @@ -0,0 +1,51 @@ +//===-- tsan_interface.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// The functions declared in this header will be inserted by the instrumentation +// module. +// This header can be included by the instrumented program or by TSan tests. +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_H +#define TSAN_INTERFACE_H + +// This header should NOT include any other headers. +// All functions in this header are extern "C" and start with __tsan_. + +#ifdef __cplusplus +extern "C" { +#endif + +// This function should be called at the very beginning of the process, +// before any instrumented code is executed and before any call to malloc. +void __tsan_init(); + +void __tsan_read1(void *addr); +void __tsan_read2(void *addr); +void __tsan_read4(void *addr); +void __tsan_read8(void *addr); +void __tsan_read16(void *addr); + +void __tsan_write1(void *addr); +void __tsan_write2(void *addr); +void __tsan_write4(void *addr); +void __tsan_write8(void *addr); +void __tsan_write16(void *addr); + +void __tsan_vptr_update(void **vptr_p, void *new_val); + +void __tsan_func_entry(void *call_pc); +void __tsan_func_exit(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // TSAN_INTERFACE_H diff --git a/lib/tsan/rtl/tsan_interface_ann.cc b/lib/tsan/rtl/tsan_interface_ann.cc new file mode 100644 index 000000000..96f1f8abc --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_ann.cc @@ -0,0 +1,344 @@ +//===-- tsan_interface_ann.cc -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_interface_ann.h" +#include "tsan_mutex.h" +#include "tsan_placement_new.h" +#include "tsan_report.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "tsan_flags.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +namespace __tsan { + +class ScopedAnnotation { + public: + ScopedAnnotation(ThreadState *thr, const char *aname, const char *f, int l, + uptr pc) + : thr_(thr) + , in_rtl_(thr->in_rtl) { + CHECK_EQ(thr_->in_rtl, 0); + FuncEntry(thr_, pc); + thr_->in_rtl++; + DPrintf("#%d: annotation %s() %s:%d\n", thr_->tid, aname, f, l); + } + + ~ScopedAnnotation() { + thr_->in_rtl--; + CHECK_EQ(in_rtl_, thr_->in_rtl); + FuncExit(thr_); + } + private: + ThreadState *const thr_; + const int in_rtl_; +}; + +#define SCOPED_ANNOTATION(typ) \ + if (!flags()->enable_annotations) \ + return; \ + ThreadState *thr = cur_thread(); \ + StatInc(thr, StatAnnotation); \ + StatInc(thr, Stat##typ); \ + ScopedAnnotation sa(thr, __FUNCTION__, f, l, \ + (uptr)__builtin_return_address(0)); \ + const uptr pc = (uptr)&__FUNCTION__; \ + (void)pc; \ +/**/ + +static const int kMaxDescLen = 128; + +struct ExpectRace { + ExpectRace *next; + ExpectRace *prev; + int hitcount; + uptr addr; + uptr size; + char *file; + int line; + char desc[kMaxDescLen]; +}; + +struct DynamicAnnContext { + Mutex mtx; + ExpectRace expect; + ExpectRace benign; + + DynamicAnnContext() + : mtx(MutexTypeAnnotations, StatMtxAnnotations) { + } +}; + +static DynamicAnnContext *dyn_ann_ctx; +static char dyn_ann_ctx_placeholder[sizeof(DynamicAnnContext)] ALIGN(64); + +static void AddExpectRace(ExpectRace *list, + char *f, int l, uptr addr, uptr size, char *desc) { + ExpectRace *race = list->next; + for (; race != list; race = race->next) { + if (race->addr == addr && race->size == size) + return; + } + race = (ExpectRace*)internal_alloc(MBlockExpectRace, sizeof(ExpectRace)); + race->hitcount = 0; + race->addr = addr; + race->size = size; + race->file = f; + race->line = l; + race->desc[0] = 0; + if (desc) { + int i = 0; + for (; i < kMaxDescLen - 1 && desc[i]; i++) + race->desc[i] = desc[i]; + race->desc[i] = 0; + } + race->prev = list; + race->next = list->next; + race->next->prev = race; + list->next = race; +} + +static ExpectRace *FindRace(ExpectRace *list, uptr addr, uptr size) { + for (ExpectRace *race = list->next; race != list; race = race->next) { + uptr maxbegin = max(race->addr, addr); + uptr minend = min(race->addr + race->size, addr + size); + if (maxbegin < minend) + return race; + } + return 0; +} + +static bool CheckContains(ExpectRace *list, uptr addr, uptr size) { + ExpectRace *race = FindRace(list, addr, size); + if (race == 0) + return false; + DPrintf("Hit expected/benign race: %s addr=%lx:%d %s:%d\n", + race->desc, race->addr, (int)race->size, race->file, race->line); + race->hitcount++; + return true; +} + +static void InitList(ExpectRace *list) { + list->next = list; + list->prev = list; +} + +void InitializeDynamicAnnotations() { + dyn_ann_ctx = new(dyn_ann_ctx_placeholder) DynamicAnnContext; + InitList(&dyn_ann_ctx->expect); + InitList(&dyn_ann_ctx->benign); +} + +bool IsExpectedReport(uptr addr, uptr size) { + Lock lock(&dyn_ann_ctx->mtx); + if (CheckContains(&dyn_ann_ctx->expect, addr, size)) + return true; + if (CheckContains(&dyn_ann_ctx->benign, addr, size)) + return true; + return false; +} + +} // namespace __tsan + +using namespace __tsan; // NOLINT + +extern "C" { +void AnnotateHappensBefore(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensBefore); + Release(cur_thread(), CALLERPC, addr); +} + +void AnnotateHappensAfter(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensAfter); + Acquire(cur_thread(), CALLERPC, addr); +} + +void AnnotateCondVarSignal(char *f, int l, uptr cv) { + SCOPED_ANNOTATION(AnnotateCondVarSignal); +} + +void AnnotateCondVarSignalAll(char *f, int l, uptr cv) { + SCOPED_ANNOTATION(AnnotateCondVarSignalAll); +} + +void AnnotateMutexIsNotPHB(char *f, int l, uptr mu) { + SCOPED_ANNOTATION(AnnotateMutexIsNotPHB); +} + +void AnnotateCondVarWait(char *f, int l, uptr cv, uptr lock) { + SCOPED_ANNOTATION(AnnotateCondVarWait); +} + +void AnnotateRWLockCreate(char *f, int l, uptr lock) { + SCOPED_ANNOTATION(AnnotateRWLockCreate); +} + +void AnnotateRWLockDestroy(char *f, int l, uptr lock) { + SCOPED_ANNOTATION(AnnotateRWLockDestroy); +} + +void AnnotateRWLockAcquired(char *f, int l, uptr lock, uptr is_w) { + SCOPED_ANNOTATION(AnnotateRWLockAcquired); +} + +void AnnotateRWLockReleased(char *f, int l, uptr lock, uptr is_w) { + SCOPED_ANNOTATION(AnnotateRWLockReleased); +} + +void AnnotateTraceMemory(char *f, int l, uptr mem) { + SCOPED_ANNOTATION(AnnotateTraceMemory); +} + +void AnnotateFlushState(char *f, int l) { + SCOPED_ANNOTATION(AnnotateFlushState); +} + +void AnnotateNewMemory(char *f, int l, uptr mem, uptr size) { + SCOPED_ANNOTATION(AnnotateNewMemory); +} + +void AnnotateNoOp(char *f, int l, uptr mem) { + SCOPED_ANNOTATION(AnnotateNoOp); +} + +static void ReportMissedExpectedRace(ExpectRace *race) { + Printf("==================\n"); + Printf("WARNING: ThreadSanitizer: missed expected data race\n"); + Printf(" %s addr=%lx %s:%d\n", + race->desc, race->addr, race->file, race->line); + Printf("==================\n"); +} + +void AnnotateFlushExpectedRaces(char *f, int l) { + SCOPED_ANNOTATION(AnnotateFlushExpectedRaces); + Lock lock(&dyn_ann_ctx->mtx); + while (dyn_ann_ctx->expect.next != &dyn_ann_ctx->expect) { + ExpectRace *race = dyn_ann_ctx->expect.next; + if (race->hitcount == 0) { + CTX()->nmissed_expected++; + ReportMissedExpectedRace(race); + } + race->prev->next = race->next; + race->next->prev = race->prev; + internal_free(race); + } +} + +void AnnotateEnableRaceDetection(char *f, int l, int enable) { + SCOPED_ANNOTATION(AnnotateEnableRaceDetection); + // FIXME: Reconsider this functionality later. It may be irrelevant. +} + +void AnnotateMutexIsUsedAsCondVar(char *f, int l, uptr mu) { + SCOPED_ANNOTATION(AnnotateMutexIsUsedAsCondVar); +} + +void AnnotatePCQGet(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQGet); +} + +void AnnotatePCQPut(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQPut); +} + +void AnnotatePCQDestroy(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQDestroy); +} + +void AnnotatePCQCreate(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQCreate); +} + +void AnnotateExpectRace(char *f, int l, uptr mem, char *desc) { + SCOPED_ANNOTATION(AnnotateExpectRace); + Lock lock(&dyn_ann_ctx->mtx); + AddExpectRace(&dyn_ann_ctx->expect, + f, l, mem, 1, desc); + DPrintf("Add expected race: %s addr=%lx %s:%d\n", desc, mem, f, l); +} + +static void BenignRaceImpl(char *f, int l, uptr mem, uptr size, char *desc) { + Lock lock(&dyn_ann_ctx->mtx); + AddExpectRace(&dyn_ann_ctx->benign, + f, l, mem, size, desc); + DPrintf("Add benign race: %s addr=%lx %s:%d\n", desc, mem, f, l); +} + +// FIXME: Turn it off later. WTF is benign race?1?? Go talk to Hans Boehm. +void AnnotateBenignRaceSized(char *f, int l, uptr mem, uptr size, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRaceSized); + BenignRaceImpl(f, l, mem, size, desc); +} + +void AnnotateBenignRace(char *f, int l, uptr mem, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRace); + BenignRaceImpl(f, l, mem, 1, desc); +} + +void AnnotateIgnoreReadsBegin(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreReadsBegin); + IgnoreCtl(cur_thread(), false, true); +} + +void AnnotateIgnoreReadsEnd(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreReadsEnd); + IgnoreCtl(cur_thread(), false, false); +} + +void AnnotateIgnoreWritesBegin(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreWritesBegin); + IgnoreCtl(cur_thread(), true, true); +} + +void AnnotateIgnoreWritesEnd(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreWritesEnd); + IgnoreCtl(cur_thread(), true, false); +} + +void AnnotatePublishMemoryRange(char *f, int l, uptr addr, uptr size) { + SCOPED_ANNOTATION(AnnotatePublishMemoryRange); +} + +void AnnotateUnpublishMemoryRange(char *f, int l, uptr addr, uptr size) { + SCOPED_ANNOTATION(AnnotateUnpublishMemoryRange); +} + +void AnnotateThreadName(char *f, int l, char *name) { + SCOPED_ANNOTATION(AnnotateThreadName); +} + +void WTFAnnotateHappensBefore(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensBefore); +} + +void WTFAnnotateHappensAfter(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensAfter); +} + +void WTFAnnotateBenignRaceSized(char *f, int l, uptr mem, uptr sz, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRaceSized); +} + +int RunningOnValgrind() { + return 0; +} + +const char *ThreadSanitizerQuery(const char *query) { + if (internal_strcmp(query, "pure_happens_before") == 0) + return "1"; + else + return "0"; +} +} // extern "C" diff --git a/lib/tsan/rtl/tsan_interface_ann.h b/lib/tsan/rtl/tsan_interface_ann.h new file mode 100644 index 000000000..09e807a00 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_ann.h @@ -0,0 +1,31 @@ +//===-- tsan_interface_ann.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Interface for dynamic annotations. +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_ANN_H +#define TSAN_INTERFACE_ANN_H + +// This header should NOT include any other headers. +// All functions in this header are extern "C" and start with __tsan_. + +#ifdef __cplusplus +extern "C" { +#endif + +void __tsan_acquire(void *addr); +void __tsan_release(void *addr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // TSAN_INTERFACE_ANN_H diff --git a/lib/tsan/rtl/tsan_interface_atomic.cc b/lib/tsan/rtl/tsan_interface_atomic.cc new file mode 100644 index 000000000..7e5f191a5 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_atomic.cc @@ -0,0 +1,194 @@ +//===-- tsan_interface_atomic.cc --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface_atomic.h" +#include "tsan_placement_new.h" +#include "tsan_flags.h" +#include "tsan_rtl.h" + +using namespace __tsan; // NOLINT + +class ScopedAtomic { + public: + ScopedAtomic(ThreadState *thr, uptr pc, const char *func) + : thr_(thr) { + CHECK_EQ(thr_->in_rtl, 1); // 1 due to our own ScopedInRtl member. + DPrintf("#%d: %s\n", thr_->tid, func); + } + ~ScopedAtomic() { + CHECK_EQ(thr_->in_rtl, 1); + } + private: + ThreadState *thr_; + ScopedInRtl in_rtl_; +}; + +// Some shortcuts. +typedef __tsan_memory_order morder; +typedef __tsan_atomic8 a8; +typedef __tsan_atomic16 a16; +typedef __tsan_atomic32 a32; +typedef __tsan_atomic64 a64; +const int mo_relaxed = __tsan_memory_order_relaxed; +const int mo_consume = __tsan_memory_order_consume; +const int mo_acquire = __tsan_memory_order_acquire; +const int mo_release = __tsan_memory_order_release; +const int mo_acq_rel = __tsan_memory_order_acq_rel; +const int mo_seq_cst = __tsan_memory_order_seq_cst; + +static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { + StatInc(thr, StatAtomic); + StatInc(thr, t); + StatInc(thr, size == 1 ? StatAtomic1 + : size == 2 ? StatAtomic2 + : size == 4 ? StatAtomic4 + : StatAtomic8); + StatInc(thr, mo == mo_relaxed ? StatAtomicRelaxed + : mo == mo_consume ? StatAtomicConsume + : mo == mo_acquire ? StatAtomicAcquire + : mo == mo_release ? StatAtomicRelease + : mo == mo_acq_rel ? StatAtomicAcq_Rel + : StatAtomicSeq_Cst); +} + +#define SCOPED_ATOMIC(func, ...) \ + mo = flags()->force_seq_cst_atomics ? (morder)mo_seq_cst : mo; \ + ThreadState *const thr = cur_thread(); \ + const uptr pc = (uptr)__builtin_return_address(0); \ + AtomicStatInc(thr, sizeof(*a), mo, StatAtomic##func); \ + ScopedAtomic sa(thr, pc, __FUNCTION__); \ + return Atomic##func(thr, pc, __VA_ARGS__); \ +/**/ + +template<typename T> +static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a, + morder mo) { + CHECK(mo & (mo_relaxed | mo_consume | mo_acquire | mo_seq_cst)); + T v = *a; + if (mo & (mo_consume | mo_acquire | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template<typename T> +static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + CHECK(mo & (mo_relaxed | mo_release | mo_seq_cst)); + if (mo & (mo_release | mo_seq_cst)) + Release(thr, pc, (uptr)a); + *a = v; +} + +template<typename T> +static T AtomicExchange(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_lock_test_and_set(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template<typename T> +static T AtomicFetchAdd(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_fetch_and_add(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template<typename T> +static bool AtomicCAS(ThreadState *thr, uptr pc, + volatile T *a, T *c, T v, morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + T cc = *c; + T pr = __sync_val_compare_and_swap(a, cc, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + if (pr == cc) + return true; + *c = pr; + return false; +} + +static void AtomicFence(ThreadState *thr, uptr pc, morder mo) { + __sync_synchronize(); +} + +a8 __tsan_atomic8_load(const volatile a8 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a16 __tsan_atomic16_load(const volatile a16 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a32 __tsan_atomic32_load(const volatile a32 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a64 __tsan_atomic64_load(const volatile a64 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +void __tsan_atomic8_store(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic16_store(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic32_store(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic64_store(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +a32 __tsan_atomic32_exchange(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a64 __tsan_atomic64_exchange(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a32 __tsan_atomic32_fetch_add(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +a64 __tsan_atomic64_fetch_add(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +int __tsan_atomic32_compare_exchange_strong(volatile a32 *a, a32 *c, a32 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic64_compare_exchange_strong(volatile a64 *a, a64 *c, a64 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +void __tsan_atomic_thread_fence(morder mo) { + char* a; + SCOPED_ATOMIC(Fence, mo); +} diff --git a/lib/tsan/rtl/tsan_interface_atomic.h b/lib/tsan/rtl/tsan_interface_atomic.h new file mode 100644 index 000000000..724407998 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_atomic.h @@ -0,0 +1,73 @@ +//===-- tsan_interface_atomic.h ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_ATOMIC_H +#define TSAN_INTERFACE_ATOMIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char __tsan_atomic8; +typedef short __tsan_atomic16; // NOLINT +typedef int __tsan_atomic32; +typedef long __tsan_atomic64; // NOLINT + +typedef enum { + __tsan_memory_order_relaxed = 1 << 0, + __tsan_memory_order_consume = 1 << 1, + __tsan_memory_order_acquire = 1 << 2, + __tsan_memory_order_release = 1 << 3, + __tsan_memory_order_acq_rel = 1 << 4, + __tsan_memory_order_seq_cst = 1 << 5, +} __tsan_memory_order; + +__tsan_atomic8 __tsan_atomic8_load(const volatile __tsan_atomic8 *a, + __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_load(const volatile __tsan_atomic16 *a, + __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_load(const volatile __tsan_atomic32 *a, + __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_load(const volatile __tsan_atomic64 *a, + __tsan_memory_order mo); + +void __tsan_atomic8_store(volatile __tsan_atomic8 *a, __tsan_atomic8 v, + __tsan_memory_order mo); +void __tsan_atomic16_store(volatile __tsan_atomic16 *a, __tsan_atomic16 v, + __tsan_memory_order mo); +void __tsan_atomic32_store(volatile __tsan_atomic32 *a, __tsan_atomic32 v, + __tsan_memory_order mo); +void __tsan_atomic64_store(volatile __tsan_atomic64 *a, __tsan_atomic64 v, + __tsan_memory_order mo); + +__tsan_atomic32 __tsan_atomic32_exchange(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_exchange(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +__tsan_atomic32 __tsan_atomic32_fetch_add(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_add(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +int __tsan_atomic32_compare_exchange_strong(volatile __tsan_atomic32 *a, + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo); +int __tsan_atomic64_compare_exchange_strong(volatile __tsan_atomic64 *a, + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo); + +void __tsan_atomic_thread_fence(__tsan_memory_order mo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef TSAN_INTERFACE_ATOMIC_H diff --git a/lib/tsan/rtl/tsan_interface_inl.h b/lib/tsan/rtl/tsan_interface_inl.h new file mode 100644 index 000000000..233f9028a --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_inl.h @@ -0,0 +1,65 @@ +//===-- tsan_interface_inl.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface.h" +#include "tsan_rtl.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +void __tsan_read1(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 0, 0); +} + +void __tsan_read2(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 1, 0); +} + +void __tsan_read4(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 2, 0); +} + +void __tsan_read8(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 3, 0); +} + +void __tsan_write1(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 0, 1); +} + +void __tsan_write2(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 1, 1); +} + +void __tsan_write4(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 2, 1); +} + +void __tsan_write8(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 3, 1); +} + +void __tsan_vptr_update(void **vptr_p, void *new_val) { + CHECK_EQ(sizeof(vptr_p), 8); + if (*vptr_p != new_val) + MemoryAccess(cur_thread(), CALLERPC, (uptr)vptr_p, 3, 1); +} + +void __tsan_func_entry(void *pc) { + FuncEntry(cur_thread(), (uptr)pc); +} + +void __tsan_func_exit() { + FuncExit(cur_thread()); +} diff --git a/lib/tsan/rtl/tsan_md5.cc b/lib/tsan/rtl/tsan_md5.cc new file mode 100644 index 000000000..487336025 --- /dev/null +++ b/lib/tsan/rtl/tsan_md5.cc @@ -0,0 +1,245 @@ +//===-- tsan_md5.cc ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_defs.h" + +namespace __tsan { + +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +#define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) + +typedef unsigned int MD5_u32plus; +typedef unsigned long ulong_t; // NOLINT + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +static void *body(MD5_CTX *ctx, void *data, ulong_t size) { + unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (unsigned char*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Init(MD5_CTX *ctx) { + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void MD5_Update(MD5_CTX *ctx, void *data, ulong_t size) { + MD5_u32plus saved_lo; + ulong_t used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + internal_memcpy(&ctx->buffer[used], data, size); + return; + } + + internal_memcpy(&ctx->buffer[used], data, free); + data = (unsigned char *)data + free; + size -= free; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(ulong_t)0x3f); + size &= 0x3f; + } + + internal_memcpy(ctx->buffer, data, size); +} + +void MD5_Final(unsigned char *result, MD5_CTX *ctx) { + ulong_t used, free; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + internal_memset(&ctx->buffer[used], 0, free); + body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + internal_memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + internal_memset(ctx, 0, sizeof(*ctx)); +} + +MD5Hash md5_hash(const void *data, uptr size) { + MD5Hash res; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, (void*)data, size); + MD5_Final((unsigned char*)&res.hash[0], &ctx); + return res; +} +} diff --git a/lib/tsan/rtl/tsan_mman.cc b/lib/tsan/rtl/tsan_mman.cc new file mode 100644 index 000000000..e24074eeb --- /dev/null +++ b/lib/tsan/rtl/tsan_mman.cc @@ -0,0 +1,137 @@ +//===-- tsan_mman.cc --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mman.h" +#include "tsan_allocator.h" +#include "tsan_rtl.h" +#include "tsan_report.h" +#include "tsan_flags.h" + +namespace __tsan { + +static void SignalUnsafeCall(ThreadState *thr, uptr pc) { + if (!thr->in_signal_handler || !flags()->report_signal_unsafe) + return; + StackTrace stack; + stack.ObtainCurrent(thr, pc); + ScopedReport rep(ReportTypeSignalUnsafe); + rep.AddStack(&stack); + OutputReport(rep); +} + +void *user_alloc(ThreadState *thr, uptr pc, uptr sz) { + CHECK_GT(thr->in_rtl, 0); + MBlock *b = (MBlock*)Alloc(sz + sizeof(MBlock)); + b->size = sz; + void *p = b + 1; + if (CTX() && CTX()->initialized) { + MemoryResetRange(thr, pc, (uptr)p, sz); + } + DPrintf("#%d: alloc(%lu) = %p\n", thr->tid, sz, p); + SignalUnsafeCall(thr, pc); + return p; +} + +void user_free(ThreadState *thr, uptr pc, void *p) { + CHECK_GT(thr->in_rtl, 0); + CHECK_NE(p, (void*)0); + DPrintf("#%d: free(%p)\n", thr->tid, p); + MBlock *b = user_mblock(thr, p); + p = b + 1; + if (CTX() && CTX()->initialized && thr->in_rtl == 1) { + MemoryRangeFreed(thr, pc, (uptr)p, b->size); + } + Free(b); + SignalUnsafeCall(thr, pc); +} + +void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) { + CHECK_GT(thr->in_rtl, 0); + void *p2 = 0; + // FIXME: Handle "shrinking" more efficiently, + // it seems that some software actually does this. + if (sz) { + p2 = user_alloc(thr, pc, sz); + if (p) { + MBlock *b = user_mblock(thr, p); + internal_memcpy(p2, p, min(b->size, sz)); + } + } + if (p) { + user_free(thr, pc, p); + } + return p2; +} + +void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align) { + CHECK_GT(thr->in_rtl, 0); + void *p = user_alloc(thr, pc, sz + align); + void *pa = RoundUp(p, align); + DCHECK_LE((uptr)pa + sz, (uptr)p + sz + align); + return pa; +} + +MBlock *user_mblock(ThreadState *thr, void *p) { + CHECK_GT(thr->in_rtl, 0); + CHECK_NE(p, (void*)0); + MBlock *b = (MBlock*)AllocBlock(p); + // FIXME: Output a warning, it's a user error. + if (p < (char*)(b + 1) || p > (char*)(b + 1) + b->size) { + Printf("user_mblock p=%p b=%p size=%lu beg=%p end=%p\n", + p, b, b->size, (char*)(b + 1), (char*)(b + 1) + b->size); + CHECK_GE(p, (char*)(b + 1)); + CHECK_LE(p, (char*)(b + 1) + b->size); + } + return b; +} + +#if TSAN_DEBUG +struct InternalMBlock { + static u32 const kMagic = 0xBCEBC041; + u32 magic; + u32 typ; + u64 sz; +}; +#endif + +void *internal_alloc(MBlockType typ, uptr sz) { + ThreadState *thr = cur_thread(); + CHECK_GT(thr->in_rtl, 0); +#if TSAN_DEBUG + InternalMBlock *b = (InternalMBlock*)Alloc(sizeof(InternalMBlock) + sz); + b->magic = InternalMBlock::kMagic; + b->typ = typ; + b->sz = sz; + thr->int_alloc_cnt[typ] += 1; + thr->int_alloc_siz[typ] += sz; + void *p = b + 1; + return p; +#else + return Alloc(sz); +#endif +} + +void internal_free(void *p) { + ThreadState *thr = cur_thread(); + CHECK_GT(thr->in_rtl, 0); +#if TSAN_DEBUG + InternalMBlock *b = (InternalMBlock*)p - 1; + CHECK_EQ(b->magic, InternalMBlock::kMagic); + thr->int_alloc_cnt[b->typ] -= 1; + thr->int_alloc_siz[b->typ] -= b->sz; + Free(b); +#else + Free(p); +#endif +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mman.h b/lib/tsan/rtl/tsan_mman.h new file mode 100644 index 000000000..8b51de6f3 --- /dev/null +++ b/lib/tsan/rtl/tsan_mman.h @@ -0,0 +1,111 @@ +//===-- tsan_mman.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_MMAN_H +#define TSAN_MMAN_H + +#include "tsan_defs.h" + +namespace __tsan { + +// Descriptor of user's memory block. +struct MBlock { + uptr size; +}; + +// For user allocations. +void *user_alloc(ThreadState *thr, uptr pc, uptr sz); +// Does not accept NULL. +void user_free(ThreadState *thr, uptr pc, void *p); +void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz); +void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align); +// Given the pointer p into a valid allocated block, +// returns the descriptor of the block. +MBlock *user_mblock(ThreadState *thr, void *p); + +enum MBlockType { + MBlockScopedBuf, + MBlockString, + MBlockStackTrace, + MBlockSync, + MBlockClock, + MBlockThreadContex, + MBlockRacyStacks, + MBlockRacyAddresses, + MBlockAtExit, + MBlockFlag, + MBlockReport, + MBlockReportMop, + MBlockReportThread, + MBlockReportMutex, + MBlockReportLoc, + MBlockReportStack, + MBlockSuppression, + MBlockExpectRace, + + // This must be the last. + MBlockTypeCount, +}; + +// For internal data structures. +void *internal_alloc(MBlockType typ, uptr sz); +void internal_free(void *p); + +template<typename T> +void DestroyAndFree(T *&p) { + p->~T(); + internal_free(p); + p = 0; +} + +template<typename T> +class InternalScopedBuf { + public: + explicit InternalScopedBuf(uptr cnt) { + cnt_ = cnt; + ptr_ = (T*)internal_alloc(MBlockScopedBuf, cnt * sizeof(T)); + } + + ~InternalScopedBuf() { + internal_free(ptr_); + } + + operator T *() { + return ptr_; + } + + T &operator[](uptr i) { + return ptr_[i]; + } + + T *Ptr() { + return ptr_; + } + + uptr Count() { + return cnt_; + } + + uptr Size() { + return cnt_ * sizeof(T); + } + + private: + T *ptr_; + uptr cnt_; + + InternalScopedBuf(const InternalScopedBuf&); + void operator = (const InternalScopedBuf&); +}; + +} // namespace __tsan +#endif // TSAN_MMAN_H diff --git a/lib/tsan/rtl/tsan_mutex.cc b/lib/tsan/rtl/tsan_mutex.cc new file mode 100644 index 000000000..a343a8bc2 --- /dev/null +++ b/lib/tsan/rtl/tsan_mutex.cc @@ -0,0 +1,275 @@ +//===-- tsan_mutex.cc -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mutex.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" + +namespace __tsan { + +// Simple reader-writer spin-mutex. Optimized for not-so-contended case. +// Readers have preference, can possibly starvate writers. + +// The table fixes what mutexes can be locked under what mutexes. +// E.g. if the row for MutexTypeThreads contains MutexTypeReport, +// then Report mutex can be locked while under Threads mutex. +// The leaf mutexes can be locked under any other mutexes. +// Recursive locking is not supported. +const MutexType MutexTypeLeaf = (MutexType)-1; +static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = { + /*0 MutexTypeInvalid*/ {}, + /*1 MutexTypeTrace*/ {MutexTypeLeaf}, + /*2 MutexTypeThreads*/ {MutexTypeReport}, + /*3 MutexTypeReport*/ {}, + /*4 MutexTypeSyncVar*/ {}, + /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, + /*6 MutexTypeSlab*/ {MutexTypeLeaf}, + /*7 MutexTypeAnnotations*/ {}, + /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, +}; + +static bool CanLockAdj[MutexTypeCount][MutexTypeCount]; + +void InitializeMutex() { + // Build the "can lock" adjacency matrix. + // If [i][j]==true, then one can lock mutex j while under mutex i. + const int N = MutexTypeCount; + int cnt[N] = {}; + bool leaf[N] = {}; + for (int i = 1; i < N; i++) { + for (int j = 0; j < N; j++) { + int z = CanLockTab[i][j]; + if (z == MutexTypeInvalid) + continue; + if (z == MutexTypeLeaf) { + CHECK(!leaf[i]); + leaf[i] = true; + continue; + } + CHECK(!CanLockAdj[i][z]); + CanLockAdj[i][z] = true; + cnt[i]++; + } + } + for (int i = 0; i < N; i++) { + CHECK(!leaf[i] || cnt[i] == 0); + } + // Add leaf mutexes. + for (int i = 0; i < N; i++) { + if (!leaf[i]) + continue; + for (int j = 0; j < N; j++) { + if (i == j || leaf[j] || j == MutexTypeInvalid) + continue; + CHECK(!CanLockAdj[j][i]); + CanLockAdj[j][i] = true; + } + } + // Build the transitive closure. + bool CanLockAdj2[MutexTypeCount][MutexTypeCount]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + CanLockAdj2[i][j] = CanLockAdj[i][j]; + } + } + for (int k = 0; k < N; k++) { + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (CanLockAdj2[i][k] && CanLockAdj2[k][j]) { + CanLockAdj2[i][j] = true; + } + } + } + } +#if 0 + Printf("Can lock graph:\n"); + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + Printf("%d ", CanLockAdj[i][j]); + } + Printf("\n"); + } + Printf("Can lock graph closure:\n"); + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + Printf("%d ", CanLockAdj2[i][j]); + } + Printf("\n"); + } +#endif + // Verify that the graph is acyclic. + for (int i = 0; i < N; i++) { + if (CanLockAdj2[i][i]) { + Printf("Mutex %d participates in a cycle\n", i); + Die(); + } + } +} + +DeadlockDetector::DeadlockDetector() { + // Rely on zero initialization because some mutexes can be locked before ctor. +} + +void DeadlockDetector::Lock(MutexType t) { + // Printf("LOCK %d @%llu\n", t, seq_ + 1); + u64 max_seq = 0; + u64 max_idx = MutexTypeInvalid; + for (int i = 0; i != MutexTypeCount; i++) { + if (locked_[i] == 0) + continue; + CHECK_NE(locked_[i], max_seq); + if (max_seq < locked_[i]) { + max_seq = locked_[i]; + max_idx = i; + } + } + locked_[t] = ++seq_; + if (max_idx == MutexTypeInvalid) + return; + // Printf(" last %d @%llu\n", max_idx, max_seq); + if (!CanLockAdj[max_idx][t]) { + Printf("ThreadSanitizer: internal deadlock detected\n"); + Printf("ThreadSanitizer: can't lock %d while under %llu\n", t, max_idx); + Die(); + } +} + +void DeadlockDetector::Unlock(MutexType t) { + // Printf("UNLO %d @%llu #%llu\n", t, seq_, locked_[t]); + CHECK(locked_[t]); + locked_[t] = 0; +} + +const uptr kUnlocked = 0; +const uptr kWriteLock = 1; +const uptr kReadLock = 2; + +class Backoff { + public: + Backoff() + : iter_() { + } + + bool Do() { + if (iter_++ < kActiveSpinIters) + proc_yield(kActiveSpinCnt); + else + sched_yield(); + return true; + } + + u64 Contention() const { + u64 active = iter_ % kActiveSpinIters; + u64 passive = iter_ - active; + return active + 10 * passive; + } + + private: + int iter_; + static const int kActiveSpinIters = 10; + static const int kActiveSpinCnt = 20; +}; + +Mutex::Mutex(MutexType type, StatType stat_type) { + CHECK_GT(type, MutexTypeInvalid); + CHECK_LT(type, MutexTypeCount); +#if TSAN_DEBUG + type_ = type; +#endif +#if TSAN_COLLECT_STATS + stat_type_ = stat_type; +#endif + atomic_store(&state_, kUnlocked, memory_order_relaxed); +} + +Mutex::~Mutex() { + CHECK_EQ(atomic_load(&state_, memory_order_relaxed), kUnlocked); +} + +void Mutex::Lock() { +#if TSAN_DEBUG + cur_thread()->deadlock_detector.Lock(type_); +#endif + uptr cmp = kUnlocked; + if (atomic_compare_exchange_strong(&state_, &cmp, kWriteLock, + memory_order_acquire)) + return; + for (Backoff backoff; backoff.Do();) { + if (atomic_load(&state_, memory_order_relaxed) == kUnlocked) { + cmp = kUnlocked; + if (atomic_compare_exchange_weak(&state_, &cmp, kWriteLock, + memory_order_acquire)) { +#if TSAN_COLLECT_STATS + StatInc(cur_thread(), stat_type_, backoff.Contention()); +#endif + return; + } + } + } +} + +void Mutex::Unlock() { + uptr prev = atomic_fetch_sub(&state_, kWriteLock, memory_order_release); + (void)prev; + DCHECK_NE(prev & kWriteLock, 0); +#if TSAN_DEBUG + cur_thread()->deadlock_detector.Unlock(type_); +#endif +} + +void Mutex::ReadLock() { +#if TSAN_DEBUG + cur_thread()->deadlock_detector.Lock(type_); +#endif + uptr prev = atomic_fetch_add(&state_, kReadLock, memory_order_acquire); + if ((prev & kWriteLock) == 0) + return; + for (Backoff backoff; backoff.Do();) { + prev = atomic_load(&state_, memory_order_acquire); + if ((prev & kWriteLock) == 0) { +#if TSAN_COLLECT_STATS + StatInc(cur_thread(), stat_type_, backoff.Contention()); +#endif + return; + } + } +} + +void Mutex::ReadUnlock() { + uptr prev = atomic_fetch_sub(&state_, kReadLock, memory_order_release); + (void)prev; + DCHECK_EQ(prev & kWriteLock, 0); + DCHECK_GT(prev & ~kWriteLock, 0); +#if TSAN_DEBUG + cur_thread()->deadlock_detector.Unlock(type_); +#endif +} + +Lock::Lock(Mutex *m) + : m_(m) { + m_->Lock(); +} + +Lock::~Lock() { + m_->Unlock(); +} + +ReadLock::ReadLock(Mutex *m) + : m_(m) { + m_->ReadLock(); +} + +ReadLock::~ReadLock() { + m_->ReadUnlock(); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mutex.h b/lib/tsan/rtl/tsan_mutex.h new file mode 100644 index 000000000..2180978f9 --- /dev/null +++ b/lib/tsan/rtl/tsan_mutex.h @@ -0,0 +1,98 @@ +//===-- tsan_mutex.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_MUTEX_H +#define TSAN_MUTEX_H + +#include "tsan_atomic.h" +#include "tsan_defs.h" + +namespace __tsan { + +enum MutexType { + MutexTypeInvalid, + MutexTypeTrace, + MutexTypeThreads, + MutexTypeReport, + MutexTypeSyncVar, + MutexTypeSyncTab, + MutexTypeSlab, + MutexTypeAnnotations, + MutexTypeAtExit, + + // This must be the last. + MutexTypeCount, +}; + +class Mutex { + public: + explicit Mutex(MutexType type, StatType stat_type); + ~Mutex(); + + void Lock(); + void Unlock(); + + void ReadLock(); + void ReadUnlock(); + + private: + atomic_uintptr_t state_; +#if TSAN_DEBUG + MutexType type_; +#endif +#if TSAN_COLLECT_STATS + StatType stat_type_; +#endif + + Mutex(const Mutex&); + void operator = (const Mutex&); +}; + +class Lock { + public: + explicit Lock(Mutex *m); + ~Lock(); + + private: + Mutex *m_; + + Lock(const Lock&); + void operator = (const Lock&); +}; + +class ReadLock { + public: + explicit ReadLock(Mutex *m); + ~ReadLock(); + + private: + Mutex *m_; + + ReadLock(const ReadLock&); + void operator = (const ReadLock&); +}; + +class DeadlockDetector { + public: + DeadlockDetector(); + void Lock(MutexType t); + void Unlock(MutexType t); + private: + u64 seq_; + u64 locked_[MutexTypeCount]; +}; + +void InitializeMutex(); + +} // namespace __tsan + +#endif // TSAN_MUTEX_H diff --git a/lib/tsan/rtl/tsan_placement_new.h b/lib/tsan/rtl/tsan_placement_new.h new file mode 100644 index 000000000..7b8ba036e --- /dev/null +++ b/lib/tsan/rtl/tsan_placement_new.h @@ -0,0 +1,24 @@ +//===-- tsan_placement_new.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// The file provides 'placement new' +// Do not include it into header files, only into source files. +//===----------------------------------------------------------------------===// +#ifndef TSAN_PLACEMENT_NEW_H +#define TSAN_PLACEMENT_NEW_H + +#include "tsan_defs.h" + +inline void *operator new(__tsan::uptr sz, void *p) { + return p; +} + +#endif // TSAN_PLACEMENT_NEW_H diff --git a/lib/tsan/rtl/tsan_platform.h b/lib/tsan/rtl/tsan_platform.h new file mode 100644 index 000000000..9cfe4763f --- /dev/null +++ b/lib/tsan/rtl/tsan_platform.h @@ -0,0 +1,93 @@ +//===-- tsan_platform.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Platform-specific code. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_LINUX_H +#define TSAN_LINUX_H +#ifdef __linux__ + +#include "tsan_rtl.h" + +#if __LP64__ +namespace __tsan { + +// TSAN_COMPAT_SHADOW is intended for COMPAT virtual memory layout, +// when memory addresses are of the 0x2axxxxxxxxxx form. +// The option is enabled with 'setarch x86_64 -L'. +#if defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW + +static const uptr kLinuxAppMemBeg = 0x2a0000000000ULL; +static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; + +#else + +static const uptr kLinuxAppMemBeg = 0x7ef000000000ULL; +static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; + +#endif + +static const uptr kLinuxAppMemMsk = 0x7c0000000000ULL; + +// This has to be a macro to allow constant initialization of constants below. +#define MemToShadow(addr) \ + (((addr) & ~(kLinuxAppMemMsk | (kShadowCell - 1))) * kShadowCnt) + +static const uptr kLinuxShadowBeg = MemToShadow(kLinuxAppMemBeg); +static const uptr kLinuxShadowEnd = + MemToShadow(kLinuxAppMemEnd) | (kPageSize - 1); + +static inline bool IsAppMem(uptr mem) { + return mem >= kLinuxAppMemBeg && mem <= kLinuxAppMemEnd; +} + +static inline bool IsShadowMem(uptr mem) { + return mem >= kLinuxShadowBeg && mem <= kLinuxShadowEnd; +} + +static inline uptr ShadowToMem(uptr shadow) { + CHECK(IsShadowMem(shadow)); +#if defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW + // COMPAT mapping is not quite one-to-one. + return (shadow / kShadowCnt) | 0x280000000000ULL; +#else + return (shadow / kShadowCnt) | kLinuxAppMemMsk; +#endif +} + +const char *InitializePlatform(); +void FinalizePlatform(); +int GetPid(); + +void sched_yield(); + +typedef int fd_t; +const fd_t kInvalidFd = -1; +fd_t internal_open(const char *name, bool write); +void internal_close(fd_t fd); +uptr internal_filesize(fd_t fd); // -1 on error. +uptr internal_read(fd_t fd, void *p, uptr size); +uptr internal_write(fd_t fd, const void *p, uptr size); +const char *internal_getpwd(); + +uptr GetTlsSize(); +void GetThreadStackAndTls(uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size); + +} // namespace __tsan + +#else // __LP64__ +# error "Only 64-bit is supported" +#endif + +#endif // __linux__ +#endif // TSAN_LINUX_H diff --git a/lib/tsan/rtl/tsan_platform_linux.cc b/lib/tsan/rtl/tsan_platform_linux.cc new file mode 100644 index 000000000..171c3486a --- /dev/null +++ b/lib/tsan/rtl/tsan_platform_linux.cc @@ -0,0 +1,250 @@ +//===-- tsan_platform_linux.cc ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Linux-specific code. +//===----------------------------------------------------------------------===// + +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_flags.h" + +#include <asm/prctl.h> +#include <fcntl.h> +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <sched.h> +#include <dlfcn.h> + +extern "C" int arch_prctl(int code, __tsan::uptr *addr); + +namespace __tsan { + +static uptr g_tls_size; + +ScopedInRtl::ScopedInRtl() + : thr_(cur_thread()) { + in_rtl_ = thr_->in_rtl; + thr_->in_rtl++; + errno_ = errno; +} + +ScopedInRtl::~ScopedInRtl() { + thr_->in_rtl--; + errno = errno_; + CHECK_EQ(in_rtl_, thr_->in_rtl); +} + +void Die() { + _exit(1); +} + +static void *my_mmap(void *addr, size_t length, int prot, int flags, + int fd, u64 offset) { + ScopedInRtl in_rtl; +# if __WORDSIZE == 64 + return (void *)syscall(__NR_mmap, addr, length, prot, flags, fd, offset); +# else + return (void *)syscall(__NR_mmap2, addr, length, prot, flags, fd, offset); +# endif +} + +void sched_yield() { + ScopedInRtl in_rtl; + syscall(__NR_sched_yield); +} + +fd_t internal_open(const char *name, bool write) { + ScopedInRtl in_rtl; + return syscall(__NR_open, name, + write ? O_WRONLY | O_CREAT | O_CLOEXEC : O_RDONLY, 0660); +} + +void internal_close(fd_t fd) { + ScopedInRtl in_rtl; + syscall(__NR_close, fd); +} + +uptr internal_filesize(fd_t fd) { + struct stat st = {}; + if (syscall(__NR_fstat, fd, &st)) + return -1; + return (uptr)st.st_size; +} + +uptr internal_read(fd_t fd, void *p, uptr size) { + ScopedInRtl in_rtl; + return syscall(__NR_read, fd, p, size); +} + +uptr internal_write(fd_t fd, const void *p, uptr size) { + ScopedInRtl in_rtl; + return syscall(__NR_write, fd, p, size); +} + +const char *internal_getpwd() { + return getenv("PWD"); +} + +static void ProtectRange(uptr beg, uptr end) { + ScopedInRtl in_rtl; + CHECK_LE(beg, end); + if (beg == end) + return; + if (beg != (uptr)my_mmap((void*)(beg), end - beg, + PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + -1, 0)) { + Printf("FATAL: ThreadSanitizer can not protect [%lx,%lx]\n", beg, end); + Printf("FATAL: Make sure you are not using unlimited stack\n"); + Die(); + } +} + +void InitializeShadowMemory() { + const uptr kClosedLowBeg = 0x200000; + const uptr kClosedLowEnd = kLinuxShadowBeg - 1; + const uptr kClosedMidBeg = kLinuxShadowEnd + 1; + const uptr kClosedMidEnd = kLinuxAppMemBeg - 1; + uptr shadow = (uptr)my_mmap((void*)kLinuxShadowBeg, + kLinuxShadowEnd - kLinuxShadowBeg, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED | MAP_NORESERVE, + 0, 0); + if (shadow != kLinuxShadowBeg) { + Printf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); + Printf("FATAL: Make sure to compile with -fPIE and to link with -pie.\n"); + Die(); + } + ProtectRange(kClosedLowBeg, kClosedLowEnd); + ProtectRange(kClosedMidBeg, kClosedMidEnd); + DPrintf("kClosedLow %lx-%lx (%luGB)\n", + kClosedLowBeg, kClosedLowEnd, (kClosedLowEnd - kClosedLowBeg) >> 30); + DPrintf("kLinuxShadow %lx-%lx (%luGB)\n", + kLinuxShadowBeg, kLinuxShadowEnd, + (kLinuxShadowEnd - kLinuxShadowBeg) >> 30); + DPrintf("kClosedMid %lx-%lx (%luGB)\n", + kClosedMidBeg, kClosedMidEnd, (kClosedMidEnd - kClosedMidBeg) >> 30); + DPrintf("kLinuxAppMem %lx-%lx (%luGB)\n", + kLinuxAppMemBeg, kLinuxAppMemEnd, + (kLinuxAppMemEnd - kLinuxAppMemBeg) >> 30); + DPrintf("stack %lx\n", (uptr)&shadow); +} + +static void CheckPIE() { + // Ensure that the binary is indeed compiled with -pie. + fd_t fmaps = internal_open("/proc/self/maps", false); + if (fmaps == kInvalidFd) + return; + char buf[20]; + if (internal_read(fmaps, buf, sizeof(buf)) == sizeof(buf)) { + buf[sizeof(buf) - 1] = 0; + u64 addr = strtoll(buf, 0, 16); + if ((u64)addr < kLinuxAppMemBeg) { + Printf("FATAL: ThreadSanitizer can not mmap the shadow memory (" + "something is mapped at 0x%llx < 0x%lx)\n", + addr, kLinuxAppMemBeg); + Printf("FATAL: Make sure to compile with -fPIE" + " and to link with -pie.\n"); + Die(); + } + } + internal_close(fmaps); +} + +#ifdef __i386__ +# define INTERNAL_FUNCTION __attribute__((regparm(3), stdcall)) +#else +# define INTERNAL_FUNCTION +#endif +extern "C" void _dl_get_tls_static_info(size_t*, size_t*) + __attribute__((weak)) INTERNAL_FUNCTION; + +static int InitTlsSize() { + typedef void (*get_tls_func)(size_t*, size_t*) INTERNAL_FUNCTION; + get_tls_func get_tls = &_dl_get_tls_static_info; + if (get_tls == 0) + get_tls = (get_tls_func)dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); + CHECK_NE(get_tls, 0); + size_t tls_size = 0; + size_t tls_align = 0; + get_tls(&tls_size, &tls_align); + return tls_size; +} + +const char *InitializePlatform() { + void *p = 0; + if (sizeof(p) == 8) { + // Disable core dumps, dumping of 16TB usually takes a bit long. + // The following magic is to prevent clang from replacing it with memset. + volatile rlimit lim; + lim.rlim_cur = 0; + lim.rlim_max = 0; + setrlimit(RLIMIT_CORE, (rlimit*)&lim); + } + + CheckPIE(); + g_tls_size = (uptr)InitTlsSize(); + return getenv("TSAN_OPTIONS"); +} + +void FinalizePlatform() { + fflush(0); +} + +uptr GetTlsSize() { + return g_tls_size; +} + +void GetThreadStackAndTls(uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size) { + *stk_addr = 0; + *stk_size = 0; + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) == 0) { + pthread_attr_getstack(&attr, (void**)stk_addr, (size_t*)stk_size); + pthread_attr_destroy(&attr); + } + arch_prctl(ARCH_GET_FS, tls_addr); + *tls_addr -= g_tls_size; + *tls_size = g_tls_size; + + // If stack and tls intersect, make them non-intersecting. + if (*tls_addr > *stk_addr && *tls_addr < *stk_addr + *stk_size) { + CHECK_GT(*tls_addr + *tls_size, *stk_addr); + CHECK_LE(*tls_addr + *tls_size, *stk_addr + *stk_size); + *stk_size = *tls_addr - *stk_addr; + *stk_size = RoundUp(*stk_size, kPageSize); + uptr stk_end = *stk_addr + *stk_size; + if (stk_end > *tls_addr) { + *tls_size -= *tls_addr - stk_end; + *tls_addr = stk_end; + } + } +} + +int GetPid() { + return getpid(); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_printf.cc b/lib/tsan/rtl/tsan_printf.cc new file mode 100644 index 000000000..96c4d24eb --- /dev/null +++ b/lib/tsan/rtl/tsan_printf.cc @@ -0,0 +1,147 @@ +//===-- tsan_printf.cc ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_defs.h" +#include "tsan_mman.h" +#include "tsan_platform.h" + +#include <stdarg.h> // va_list + +typedef long long i64; // NOLINT +typedef long iptr; // NOLINT + +namespace __tsan { + +static int AppendChar(char **buff, const char *buff_end, char c) { + if (*buff < buff_end) { + **buff = c; + (*buff)++; + } + return 1; +} + +static int AppendUnsigned(char **buff, const char *buff_end, u64 num, + int base, uptr minimal_num_length) { + uptr const kMaxLen = 30; + uptr num_buffer[kMaxLen]; + uptr pos = 0; + do { + num_buffer[pos++] = num % base; + num /= base; + } while (num > 0); + while (pos < minimal_num_length) num_buffer[pos++] = 0; + int result = 0; + while (pos-- > 0) { + uptr digit = num_buffer[pos]; + result += AppendChar(buff, buff_end, (digit < 10) ? '0' + digit + : 'a' + digit - 10); + } + return result; +} + +static int AppendSignedDecimal(char **buff, const char *buff_end, i64 num) { + int result = 0; + if (num < 0) { + result += AppendChar(buff, buff_end, '-'); + num = -num; + } + result += AppendUnsigned(buff, buff_end, (u64)num, 10, 0); + return result; +} + +static int AppendString(char **buff, const char *buff_end, const char *s) { + if (s == 0) + s = "<null>"; + int result = 0; + for (; *s; s++) { + result += AppendChar(buff, buff_end, *s); + } + return result; +} + +static int AppendPointer(char **buff, const char *buff_end, u64 ptr_value) { + int result = 0; + result += AppendString(buff, buff_end, "0x"); + result += AppendUnsigned(buff, buff_end, ptr_value, 16, + (sizeof(void*) == 8) ? 12 : 8); // NOLINT + return result; +} + +static uptr VSNPrintf(char *buff, int buff_length, + const char *format, va_list args) { + const char *buff_end = &buff[buff_length - 1]; + const char *cur = format; + int result = 0; + for (; *cur; cur++) { + if (*cur != '%') { + result += AppendChar(&buff, buff_end, *cur); + continue; + } + cur++; + bool is_long = (*cur == 'l'); + cur += is_long; + bool is_llong = (*cur == 'l'); + cur += is_llong; + switch (*cur) { + case 'd': { + i64 v = is_llong ? va_arg(args, i64) + : is_long ? va_arg(args, iptr) + : va_arg(args, int); + result += AppendSignedDecimal(&buff, buff_end, v); + break; + } + case 'u': + case 'x': { + u64 v = is_llong ? va_arg(args, u64) + : is_long ? va_arg(args, uptr) + : va_arg(args, unsigned); + result += AppendUnsigned(&buff, buff_end, v, *cur == 'u' ? 10: 16, 0); + break; + } + case 'p': { + result += AppendPointer(&buff, buff_end, va_arg(args, uptr)); + break; + } + case 's': { + result += AppendString(&buff, buff_end, va_arg(args, char*)); + break; + } + default: { + Die(); + } + } + } + AppendChar(&buff, buff_end + 1, '\0'); + return result; +} + +void Printf(const char *format, ...) { + ScopedInRtl in_rtl; + const uptr kMaxLen = 16 * 1024; + InternalScopedBuf<char> buffer(kMaxLen); + va_list args; + va_start(args, format); + uptr len = VSNPrintf(buffer, buffer.Size(), format, args); + va_end(args); + internal_write(CTX() ? flags()->log_fileno : 2, + buffer, len < buffer.Size() ? len : buffer.Size() - 1); +} + +uptr Snprintf(char *buffer, uptr length, const char *format, ...) { + va_list args; + va_start(args, format); + uptr len = VSNPrintf(buffer, length, format, args); + va_end(args); + return len; +} +} diff --git a/lib/tsan/rtl/tsan_report.cc b/lib/tsan/rtl/tsan_report.cc new file mode 100644 index 000000000..f231ed07d --- /dev/null +++ b/lib/tsan/rtl/tsan_report.cc @@ -0,0 +1,124 @@ +//===-- tsan_report.cc ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_report.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" + +namespace __tsan { + +ReportDesc::ReportDesc() + : stacks(MBlockReportStack) + , mops(MBlockReportMop) + , locs(MBlockReportLoc) + , mutexes(MBlockReportMutex) + , threads(MBlockReportThread) { +} + +ReportDesc::~ReportDesc() { +} + +static void PrintHeader(ReportType typ) { + Printf("WARNING: ThreadSanitizer: "); + + if (typ == ReportTypeRace) + Printf("data race"); + else if (typ == ReportTypeThreadLeak) + Printf("thread leak"); + else if (typ == ReportTypeMutexDestroyLocked) + Printf("destroy of a locked mutex"); + else if (typ == ReportTypeSignalUnsafe) + Printf("signal-unsafe call inside of a signal"); + + Printf(" (pid=%d)\n", GetPid()); +} + +static void PrintStack(const ReportStack *ent) { + for (int i = 0; ent; ent = ent->next, i++) { + Printf(" #%d %s %s:%d", i, ent->func, ent->file, ent->line); + if (ent->col) + Printf(":%d", ent->col); + if (ent->module && ent->offset) + Printf(" (%s+%p)\n", ent->module, (void*)ent->offset); + else + Printf(" (%p)\n", (void*)ent->pc); + } +} + +static void PrintMop(const ReportMop *mop, bool first) { + Printf(" %s of size %d at %p", + (first ? (mop->write ? "Write" : "Read") + : (mop->write ? "Previous write" : "Previous read")), + mop->size, (void*)mop->addr); + if (mop->tid == 0) + Printf(" by main thread:\n"); + else + Printf(" by thread %d:\n", mop->tid); + PrintStack(mop->stack); +} + +static void PrintLocation(const ReportLocation *loc) { + if (loc->type == ReportLocationGlobal) { + Printf(" Location is global '%s' of size %lu at %lx %s:%d\n", + loc->name, loc->size, loc->addr, loc->file, loc->line); + } else if (loc->type == ReportLocationHeap) { + Printf(" Location is heap of size %lu at %lx allocated by thread %d:\n", + loc->size, loc->addr, loc->tid); + PrintStack(loc->stack); + } else if (loc->type == ReportLocationStack) { + Printf(" Location is stack of thread %d:\n", loc->tid); + } +} + +static void PrintMutex(const ReportMutex *rm) { + if (rm->stack == 0) + return; + Printf(" Mutex %d created at:\n", rm->id); + PrintStack(rm->stack); +} + +static void PrintThread(const ReportThread *rt) { + if (rt->id == 0) // Little sense in describing the main thread. + return; + Printf(" Thread %d", rt->id); + if (rt->name) + Printf(" '%s'", rt->name); + Printf(" (%s)", rt->running ? "running" : "finished"); + if (rt->stack) + Printf(" created at:"); + Printf("\n"); + PrintStack(rt->stack); +} + +void PrintReport(const ReportDesc *rep) { + Printf("==================\n"); + PrintHeader(rep->typ); + + for (uptr i = 0; i < rep->stacks.Size(); i++) + PrintStack(rep->stacks[i]); + + for (uptr i = 0; i < rep->mops.Size(); i++) + PrintMop(rep->mops[i], i == 0); + + for (uptr i = 0; i < rep->locs.Size(); i++) + PrintLocation(rep->locs[i]); + + for (uptr i = 0; i < rep->mutexes.Size(); i++) + PrintMutex(rep->mutexes[i]); + + for (uptr i = 0; i < rep->threads.Size(); i++) + PrintThread(rep->threads[i]); + + Printf("==================\n"); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_report.h b/lib/tsan/rtl/tsan_report.h new file mode 100644 index 000000000..70c59324d --- /dev/null +++ b/lib/tsan/rtl/tsan_report.h @@ -0,0 +1,100 @@ +//===-- tsan_report.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_REPORT_H +#define TSAN_REPORT_H + +#include "tsan_defs.h" +#include "tsan_vector.h" + +namespace __tsan { + +enum ReportType { + ReportTypeRace, + ReportTypeThreadLeak, + ReportTypeMutexDestroyLocked, + ReportTypeSignalUnsafe, +}; + +struct ReportStack { + ReportStack *next; + char *module; + uptr offset; + uptr pc; + char *func; + char *file; + int line; + int col; +}; + +struct ReportMop { + int tid; + uptr addr; + int size; + bool write; + int nmutex; + int *mutex; + ReportStack *stack; +}; + +enum ReportLocationType { + ReportLocationGlobal, + ReportLocationHeap, + ReportLocationStack, +}; + +struct ReportLocation { + ReportLocationType type; + uptr addr; + uptr size; + int tid; + char *name; + char *file; + int line; + ReportStack *stack; +}; + +struct ReportThread { + int id; + bool running; + char *name; + ReportStack *stack; +}; + +struct ReportMutex { + int id; + ReportStack *stack; +}; + +class ReportDesc { + public: + ReportType typ; + Vector<ReportStack*> stacks; + Vector<ReportMop*> mops; + Vector<ReportLocation*> locs; + Vector<ReportMutex*> mutexes; + Vector<ReportThread*> threads; + + ReportDesc(); + ~ReportDesc(); + + private: + ReportDesc(const ReportDesc&); + void operator = (const ReportDesc&); +}; + +// Format and output the report to the console/log. No additional logic. +void PrintReport(const ReportDesc *rep); + +} // namespace __tsan + +#endif // TSAN_REPORT_H diff --git a/lib/tsan/rtl/tsan_rtl.cc b/lib/tsan/rtl/tsan_rtl.cc new file mode 100644 index 000000000..0e04c4494 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl.cc @@ -0,0 +1,460 @@ +//===-- tsan_rtl.cc ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Main file (entry points) for the TSan run-time. +//===----------------------------------------------------------------------===// + +#include "tsan_defs.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_interface.h" +#include "tsan_atomic.h" +#include "tsan_mman.h" +#include "tsan_placement_new.h" +#include "tsan_suppressions.h" + +volatile int __tsan_stop = 0; + +extern "C" void __tsan_resume() { + __tsan_stop = 0; +} + +namespace __tsan { + +THREADLOCAL char cur_thread_placeholder[sizeof(ThreadState)] ALIGN(64); +static char ctx_placeholder[sizeof(Context)] ALIGN(64); + +static Context *ctx; +Context *CTX() { + return ctx; +} + +Context::Context() + : initialized() + , report_mtx(MutexTypeReport, StatMtxReport) + , nreported() + , nmissed_expected() + , thread_mtx(MutexTypeThreads, StatMtxThreads) + , racy_stacks(MBlockRacyStacks) + , racy_addresses(MBlockRacyAddresses) { +} + +// The objects are allocated in TLS, so one may rely on zero-initialization. +ThreadState::ThreadState(Context *ctx, int tid, u64 epoch, + uptr stk_addr, uptr stk_size, + uptr tls_addr, uptr tls_size) + : fast_state(tid, epoch) + // Do not touch these, rely on zero initialization, + // they may be accessed before the ctor. + // , fast_ignore_reads() + // , fast_ignore_writes() + // , in_rtl() + , shadow_stack_pos(&shadow_stack[0]) + , tid(tid) + , func_call_count() + , stk_addr(stk_addr) + , stk_size(stk_size) + , tls_addr(tls_addr) + , tls_size(tls_size) { +} + +ThreadContext::ThreadContext(int tid) + : tid(tid) + , unique_id() + , user_id() + , thr() + , status(ThreadStatusInvalid) + , detached() + , reuse_count() + , epoch0() + , epoch1() + , dead_next() { +} + +void Initialize(ThreadState *thr) { + // Thread safe because done before all threads exist. + static bool is_initialized = false; + if (is_initialized) + return; + is_initialized = true; + ScopedInRtl in_rtl; + InitializeInterceptors(); + const char *env = InitializePlatform(); + InitializeMutex(); + InitializeDynamicAnnotations(); + ctx = new(ctx_placeholder) Context; + InitializeShadowMemory(); + ctx->dead_list_size = 0; + ctx->dead_list_head = 0; + ctx->dead_list_tail = 0; + InitializeFlags(&ctx->flags, env); + InitializeSuppressions(); + + if (ctx->flags.verbosity) + Printf("***** Running under ThreadSanitizer v2 (pid=%d) *****\n", GetPid()); + + // Initialize thread 0. + ctx->thread_seq = 0; + int tid = ThreadCreate(thr, 0, 0, true); + CHECK_EQ(tid, 0); + ThreadStart(thr, tid); + CHECK_EQ(thr->in_rtl, 1); + ctx->initialized = true; + + if (__tsan_stop) { + Printf("ThreadSanitizer is suspended at startup.\n"); + while (__tsan_stop); + } +} + +int Finalize(ThreadState *thr) { + ScopedInRtl in_rtl; + Context *ctx = __tsan::ctx; + bool failed = false; + + // Be very careful beyond that point. + // All bets are off. Everything is destroyed. + ThreadFinish(thr); + ThreadFinalize(thr); + FinalizeFlags(&ctx->flags); + + if (ctx->nreported) { + failed = true; + Printf("ThreadSanitizer: reported %d warnings\n", ctx->nreported); + } + + if (ctx->nmissed_expected) { + failed = true; + Printf("ThreadSanitizer: missed %d expected races\n", + ctx->nmissed_expected); + } + + StatOutput(ctx->stat); + FinalizeSuppressions(); + FinalizePlatform(); + + const int exitcode = failed ? flags()->exitcode : 0; + const int log_fileno = flags()->log_fileno; + __tsan::ctx->~Context(); + __tsan::ctx = 0; + + InternalAllocStatAggregate(ctx, thr); + + for (int i = 0; i < (int)MBlockTypeCount; i++) { + if (ctx->int_alloc_cnt[i] == 0 && ctx->int_alloc_siz[i] == 0) + continue; + InternalScopedBuf<char> tmp(1024); + Snprintf(tmp, tmp.Size(), "ThreadSanitizer: Internal memory leak: " + "type=%d count=%lld size=%lld\n", + (int)i, ctx->int_alloc_cnt[i], ctx->int_alloc_siz[i]); + internal_write(log_fileno, tmp, internal_strlen(tmp)); + } + + return exitcode; +} + +static void TraceSwitch(ThreadState *thr) { + ScopedInRtl in_rtl; + Lock l(&thr->trace.mtx); + unsigned trace = (thr->fast_state.epoch() / kTracePartSize) % kTraceParts; + TraceHeader *hdr = &thr->trace.headers[trace]; + hdr->epoch0 = thr->fast_state.epoch(); + hdr->stack0.ObtainCurrent(thr, 0); +} + +extern "C" void __tsan_trace_switch() { + TraceSwitch(cur_thread()); +} + +extern "C" void __tsan_report_race() { + ReportRace(cur_thread()); +} + +ALWAYS_INLINE +static Shadow LoadShadow(u64 *p) { + u64 raw = atomic_load((atomic_uint64_t*)p, memory_order_relaxed); + return Shadow(raw); +} + +ALWAYS_INLINE +static void StoreShadow(u64 *sp, u64 s) { + atomic_store((atomic_uint64_t*)sp, s, memory_order_relaxed); +} + +ALWAYS_INLINE +static void StoreIfNotYetStored(u64 *sp, u64 *s) { + StoreShadow(sp, *s); + *s = 0; +} + +static inline void HandleRace(ThreadState *thr, u64 *shadow_mem, + Shadow cur, Shadow old) { + thr->racy_state[0] = cur.raw(); + thr->racy_state[1] = old.raw(); + thr->racy_shadow_addr = shadow_mem; + HACKY_CALL(__tsan_report_race); +} + +static inline bool BothReads(Shadow s, int kAccessIsWrite) { + return !kAccessIsWrite && !s.is_write(); +} + +static inline bool OldIsRWStronger(Shadow old, int kAccessIsWrite) { + return old.is_write() || !kAccessIsWrite; +} + +static inline bool OldIsRWWeaker(Shadow old, int kAccessIsWrite) { + return !old.is_write() || kAccessIsWrite; +} + +static inline bool OldIsInSameSynchEpoch(Shadow old, ThreadState *thr) { + return old.epoch() >= thr->fast_synch_epoch; +} + +static inline bool HappensBefore(Shadow old, ThreadState *thr) { + return thr->clock.get(old.tid()) >= old.epoch(); +} + +ALWAYS_INLINE +void MemoryAccessImpl(ThreadState *thr, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + u64 *shadow_mem, Shadow cur) { + StatInc(thr, StatMop); + StatInc(thr, kAccessIsWrite ? StatMopWrite : StatMopRead); + StatInc(thr, (StatType)(StatMop1 + kAccessSizeLog)); + + // This potentially can live in an MMX/SSE scratch register. + // The required intrinsics are: + // __m128i _mm_move_epi64(__m128i*); + // _mm_storel_epi64(u64*, __m128i); + u64 store_word = cur.raw(); + + // scan all the shadow values and dispatch to 4 categories: + // same, replace, candidate and race (see comments below). + // we consider only 3 cases regarding access sizes: + // equal, intersect and not intersect. initially I considered + // larger and smaller as well, it allowed to replace some + // 'candidates' with 'same' or 'replace', but I think + // it's just not worth it (performance- and complexity-wise). + + Shadow old(0); + if (kShadowCnt == 1) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 2) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 4) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + idx = 2; +#include "tsan_update_shadow_word_inl.h" + idx = 3; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 8) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + idx = 2; +#include "tsan_update_shadow_word_inl.h" + idx = 3; +#include "tsan_update_shadow_word_inl.h" + idx = 4; +#include "tsan_update_shadow_word_inl.h" + idx = 5; +#include "tsan_update_shadow_word_inl.h" + idx = 6; +#include "tsan_update_shadow_word_inl.h" + idx = 7; +#include "tsan_update_shadow_word_inl.h" + } else { + CHECK(false); + } + + // we did not find any races and had already stored + // the current access info, so we are done + if (LIKELY(store_word == 0)) + return; + // choose a random candidate slot and replace it + StoreShadow(shadow_mem + (cur.epoch() % kShadowCnt), store_word); + StatInc(thr, StatShadowReplace); + return; + RACE: + HandleRace(thr, shadow_mem, cur, old); + return; +} + +ALWAYS_INLINE +void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite) { + u64 *shadow_mem = (u64*)MemToShadow(addr); + DPrintf2("#%d: tsan::OnMemoryAccess: @%p %p size=%d" + " is_write=%d shadow_mem=%p {%llx, %llx, %llx, %llx}\n", + (int)thr->fast_state.tid(), (void*)pc, (void*)addr, + (int)(1 << kAccessSizeLog), kAccessIsWrite, shadow_mem, + shadow_mem[0], shadow_mem[1], shadow_mem[2], shadow_mem[3]); +#if TSAN_DEBUG + if (!IsAppMem(addr)) { + Printf("Access to non app mem %lx\n", addr); + DCHECK(IsAppMem(addr)); + } + if (!IsShadowMem((uptr)shadow_mem)) { + Printf("Bad shadow addr %p (%lx)\n", shadow_mem, addr); + DCHECK(IsShadowMem((uptr)shadow_mem)); + } +#endif + + FastState fast_state = thr->fast_state; + if (fast_state.GetIgnoreBit()) + return; + fast_state.IncrementEpoch(); + thr->fast_state = fast_state; + Shadow cur(fast_state); + cur.SetAddr0AndSizeLog(addr & 7, kAccessSizeLog); + cur.SetWrite(kAccessIsWrite); + + // We must not store to the trace if we do not store to the shadow. + // That is, this call must be moved somewhere below. + TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + + MemoryAccessImpl(thr, addr, kAccessSizeLog, kAccessIsWrite, fast_state, + shadow_mem, cur); +} + +static void MemoryRangeSet(ThreadState *thr, uptr pc, uptr addr, uptr size, + u64 val) { + if (size == 0) + return; + // FIXME: fix me. + uptr offset = addr % kShadowCell; + if (offset) { + offset = kShadowCell - offset; + if (size <= offset) + return; + addr += offset; + size -= offset; + } + CHECK_EQ(addr % 8, 0); + CHECK(IsAppMem(addr)); + CHECK(IsAppMem(addr + size - 1)); + (void)thr; + (void)pc; + // Some programs mmap like hundreds of GBs but actually used a small part. + // So, it's better to report a false positive on the memory + // then to hang here senselessly. + const uptr kMaxResetSize = 1024*1024*1024; + if (size > kMaxResetSize) + size = kMaxResetSize; + size = (size + 7) & ~7; + u64 *p = (u64*)MemToShadow(addr); + CHECK(IsShadowMem((uptr)p)); + CHECK(IsShadowMem((uptr)(p + size * kShadowCnt / kShadowCell - 1))); + // FIXME: may overwrite a part outside the region + for (uptr i = 0; i < size * kShadowCnt / kShadowCell; i++) + p[i] = val; +} + +void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size) { + MemoryRangeSet(thr, pc, addr, size, 0); +} + +void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size) { + MemoryAccessRange(thr, pc, addr, size, true); + MemoryRangeSet(thr, pc, addr, size, kShadowFreed); +} + +void FuncEntry(ThreadState *thr, uptr pc) { + DCHECK_EQ(thr->in_rtl, 0); + StatInc(thr, StatFuncEnter); + DPrintf2("#%d: tsan::FuncEntry %p\n", (int)thr->fast_state.tid(), (void*)pc); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncEnter, pc); + + // Shadow stack maintenance can be replaced with + // stack unwinding during trace switch (which presumably must be faster). + DCHECK(thr->shadow_stack_pos >= &thr->shadow_stack[0]); + DCHECK(thr->shadow_stack_pos < &thr->shadow_stack[kShadowStackSize]); + thr->shadow_stack_pos[0] = pc; + thr->shadow_stack_pos++; + +#if 1 + // While we are testing on single-threaded benchmarks, + // emulate some synchronization activity. + // FIXME: remove me later. + if (((++thr->func_call_count) % 1000) == 0) { + thr->clock.set(thr->fast_state.tid(), thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + } +#endif +} + +void FuncExit(ThreadState *thr) { + DCHECK_EQ(thr->in_rtl, 0); + StatInc(thr, StatFuncExit); + DPrintf2("#%d: tsan::FuncExit\n", (int)thr->fast_state.tid()); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncExit, 0); + + DCHECK(thr->shadow_stack_pos > &thr->shadow_stack[0]); + DCHECK(thr->shadow_stack_pos < &thr->shadow_stack[kShadowStackSize]); + thr->shadow_stack_pos--; +} + +void IgnoreCtl(ThreadState *thr, bool write, bool begin) { + DPrintf("#%d: IgnoreCtl(%d, %d)\n", thr->tid, write, begin); + thr->ignore_reads_and_writes += begin ? 1 : -1; + CHECK_GE(thr->ignore_reads_and_writes, 0); + if (thr->ignore_reads_and_writes) + thr->fast_state.SetIgnoreBit(); + else + thr->fast_state.ClearIgnoreBit(); +} + +void InternalAllocStatAggregate(Context *ctx, ThreadState *thr) { + for (int i = 0; i < (int)MBlockTypeCount; i++) { + ctx->int_alloc_cnt[i] += thr->int_alloc_cnt[i]; + ctx->int_alloc_siz[i] += thr->int_alloc_siz[i]; + thr->int_alloc_cnt[i] = 0; + thr->int_alloc_siz[i] = 0; + } +} + +#if TSAN_DEBUG +void build_consistency_debug() {} +#else +void build_consistency_release() {} +#endif + +#if TSAN_COLLECT_STATS +void build_consistency_stats() {} +#else +void build_consistency_nostats() {} +#endif + +#if TSAN_SHADOW_COUNT == 1 +void build_consistency_shadow1() {} +#elif TSAN_SHADOW_COUNT == 2 +void build_consistency_shadow2() {} +#elif TSAN_SHADOW_COUNT == 4 +void build_consistency_shadow4() {} +#else +void build_consistency_shadow8() {} +#endif + +} // namespace __tsan + +// Must be included in this file to make sure everything is inlined. +#include "tsan_interface_inl.h" diff --git a/lib/tsan/rtl/tsan_rtl.h b/lib/tsan/rtl/tsan_rtl.h new file mode 100644 index 000000000..11179045c --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl.h @@ -0,0 +1,459 @@ +//===-- tsan_rtl.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Main internal TSan header file. +// +// Ground rules: +// - C++ run-time should not be used (static CTORs, RTTI, exceptions, static +// function-scope locals) +// - All functions/classes/etc reside in namespace __tsan, except for those +// declared in tsan_interface.h. +// - Platform-specific files should be used instead of ifdefs (*). +// - No system headers included in header files (*). +// - Platform specific headres included only into platform-specific files (*). +// +// (*) Except when inlining is critical for performance. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_RTL_H +#define TSAN_RTL_H + +#include "tsan_clock.h" +#include "tsan_defs.h" +#include "tsan_flags.h" +#include "tsan_sync.h" +#include "tsan_trace.h" +#include "tsan_vector.h" +#include "tsan_report.h" + +namespace __tsan { + +void Printf(const char *format, ...) FORMAT(1, 2); +uptr Snprintf(char *buffer, uptr length, const char *format, ...) FORMAT(3, 4); + +inline void NOINLINE breakhere() { + volatile int x = 42; + (void)x; +} + +// FastState (from most significant bit): +// tid : kTidBits +// epoch : kClkBits +// unused : +// ignore_bit : 1 +class FastState { + public: + FastState(u64 tid, u64 epoch) { + x_ = tid << (64 - kTidBits); + x_ |= epoch << (64 - kTidBits - kClkBits); + CHECK(tid == this->tid()); + CHECK(epoch == this->epoch()); + } + + explicit FastState(u64 x) + : x_(x) { + } + + u64 tid() const { + u64 res = x_ >> (64 - kTidBits); + return res; + } + u64 epoch() const { + u64 res = (x_ << kTidBits) >> (64 - kClkBits); + return res; + }; + void IncrementEpoch() { + // u64 old_epoch = epoch(); + x_ += 1 << (64 - kTidBits - kClkBits); + // CHECK(old_epoch + 1 == epoch()); + } + void SetIgnoreBit() { x_ |= 1; } + void ClearIgnoreBit() { x_ &= ~(u64)1; } + bool GetIgnoreBit() { return x_ & 1; } + + private: + friend class Shadow; + u64 x_; +}; + +// Shadow (from most significant bit): +// tid : kTidBits +// epoch : kClkBits +// is_write : 1 +// size_log : 2 +// addr0 : 3 +class Shadow: public FastState { + public: + explicit Shadow(u64 x) : FastState(x) { } + + explicit Shadow(const FastState &s) : FastState(s.x_) { } + + void SetAddr0AndSizeLog(u64 addr0, unsigned kAccessSizeLog) { + DCHECK_EQ(x_ & 31, 0); + DCHECK_LE(addr0, 7); + DCHECK_LE(kAccessSizeLog, 3); + x_ |= (kAccessSizeLog << 3) | addr0; + DCHECK_EQ(kAccessSizeLog, size_log()); + DCHECK_EQ(addr0, this->addr0()); + } + + void SetWrite(unsigned kAccessIsWrite) { + DCHECK_EQ(x_ & 32, 0); + if (kAccessIsWrite) + x_ |= 32; + DCHECK_EQ(kAccessIsWrite, is_write()); + } + + bool IsZero() const { return x_ == 0; } + u64 raw() const { return x_; } + + static inline bool TidsAreEqual(Shadow s1, Shadow s2) { + u64 shifted_xor = (s1.x_ ^ s2.x_) >> (64 - kTidBits); + DCHECK_EQ(shifted_xor == 0, s1.tid() == s2.tid()); + return shifted_xor == 0; + } + static inline bool Addr0AndSizeAreEqual(Shadow s1, Shadow s2) { + u64 masked_xor = (s1.x_ ^ s2.x_) & 31; + return masked_xor == 0; + } + + static bool TwoRangesIntersectSLOW(Shadow s1, Shadow s2) { + if (s1.addr0() == s2.addr0()) return true; + if (s1.addr0() < s2.addr0() && s1.addr0() + s1.size() > s2.addr0()) + return true; + if (s2.addr0() < s1.addr0() && s2.addr0() + s2.size() > s1.addr0()) + return true; + return false; + } + + static inline bool TwoRangesIntersect(Shadow s1, Shadow s2, + unsigned kS2AccessSize) { + bool res = false; + u64 diff = s1.addr0() - s2.addr0(); + if ((s64)diff < 0) { // s1.addr0 < s2.addr0 // NOLINT + // if (s1.addr0() + size1) > s2.addr0()) return true; + if (s1.size() > -diff) res = true; + } else { + // if (s2.addr0() + kS2AccessSize > s1.addr0()) return true; + if (kS2AccessSize > diff) res = true; + } + DCHECK_EQ(res, TwoRangesIntersectSLOW(s1, s2)); + DCHECK_EQ(res, TwoRangesIntersectSLOW(s2, s1)); + return res; + } + + // The idea behind the offset is as follows. + // Consider that we have 8 bool's contained within a single 8-byte block + // (mapped to a single shadow "cell"). Now consider that we write to the bools + // from a single thread (which we consider the common case). + // W/o offsetting each access will have to scan 4 shadow values at average + // to find the corresponding shadow value for the bool. + // With offsetting we start scanning shadow with the offset so that + // each access hits necessary shadow straight off (at least in an expected + // optimistic case). + // This logic works seamlessly for any layout of user data. For example, + // if user data is {int, short, char, char}, then accesses to the int are + // offsetted to 0, short - 4, 1st char - 6, 2nd char - 7. Hopefully, accesses + // from a single thread won't need to scan all 8 shadow values. + unsigned ComputeSearchOffset() { + return x_ & 7; + } + u64 addr0() const { return x_ & 7; } + u64 size() const { return 1ull << size_log(); } + bool is_write() const { return x_ & 32; } + + private: + u64 size_log() const { return (x_ >> 3) & 3; } +}; + +// Freed memory. +// As if 8-byte write by thread 0xff..f at epoch 0xff..f, races with everything. +const u64 kShadowFreed = 0xfffffffffffffff8ull; + +const int kSigCount = 1024; +const int kShadowStackSize = 1024; + +struct my_siginfo_t { + int opaque[128]; +}; + +struct SignalDesc { + bool armed; + bool sigaction; + my_siginfo_t siginfo; +}; + +// This struct is stored in TLS. +struct ThreadState { + FastState fast_state; + // Synch epoch represents the threads's epoch before the last synchronization + // action. It allows to reduce number of shadow state updates. + // For example, fast_synch_epoch=100, last write to addr X was at epoch=150, + // if we are processing write to X from the same thread at epoch=200, + // we do nothing, because both writes happen in the same 'synch epoch'. + // That is, if another memory access does not race with the former write, + // it does not race with the latter as well. + // QUESTION: can we can squeeze this into ThreadState::Fast? + // E.g. ThreadState::Fast is a 44-bit, 32 are taken by synch_epoch and 12 are + // taken by epoch between synchs. + // This way we can save one load from tls. + u64 fast_synch_epoch; + // This is a slow path flag. On fast path, fast_state.GetIgnoreBit() is read. + // We do not distinguish beteween ignoring reads and writes + // for better performance. + int ignore_reads_and_writes; + uptr *shadow_stack_pos; + u64 *racy_shadow_addr; + u64 racy_state[2]; + Trace trace; + uptr shadow_stack[kShadowStackSize]; + ThreadClock clock; + u64 stat[StatCnt]; + u64 int_alloc_cnt[MBlockTypeCount]; + u64 int_alloc_siz[MBlockTypeCount]; + const int tid; + int in_rtl; + int func_call_count; + const uptr stk_addr; + const uptr stk_size; + const uptr tls_addr; + const uptr tls_size; + + DeadlockDetector deadlock_detector; + + bool in_signal_handler; + int pending_signal_count; + SignalDesc pending_signals[kSigCount]; + + explicit ThreadState(Context *ctx, int tid, u64 epoch, + uptr stk_addr, uptr stk_size, + uptr tls_addr, uptr tls_size); +}; + +Context *CTX(); +extern THREADLOCAL char cur_thread_placeholder[]; + +INLINE ThreadState *cur_thread() { + return reinterpret_cast<ThreadState *>(&cur_thread_placeholder); +} + +enum ThreadStatus { + ThreadStatusInvalid, // Non-existent thread, data is invalid. + ThreadStatusCreated, // Created but not yet running. + ThreadStatusRunning, // The thread is currently running. + ThreadStatusFinished, // Joinable thread is finished but not yet joined. + ThreadStatusDead, // Joined, but some info (trace) is still alive. +}; + +// An info about a thread that is hold for some time after its termination. +struct ThreadDeadInfo { + Trace trace; +}; + +struct ThreadContext { + const int tid; + int unique_id; // Non-rolling thread id. + uptr user_id; // Some opaque user thread id (e.g. pthread_t). + ThreadState *thr; + ThreadStatus status; + bool detached; + int reuse_count; + SyncClock sync; + // Epoch at which the thread had started. + // If we see an event from the thread stamped by an older epoch, + // the event is from a dead thread that shared tid with this thread. + u64 epoch0; + u64 epoch1; + StackTrace creation_stack; + ThreadDeadInfo dead_info; + ThreadContext* dead_next; // In dead thread list. + + explicit ThreadContext(int tid); +}; + +struct RacyStacks { + MD5Hash hash[2]; + bool operator==(const RacyStacks &other) const { + if (hash[0] == other.hash[0] && hash[1] == other.hash[1]) + return true; + if (hash[0] == other.hash[1] && hash[1] == other.hash[0]) + return true; + return false; + } +}; + +struct RacyAddress { + uptr addr_min; + uptr addr_max; +}; + +struct Context { + Context(); + + bool initialized; + + SyncTab synctab; + + Mutex report_mtx; + int nreported; + int nmissed_expected; + + Mutex thread_mtx; + int thread_seq; + int unique_thread_seq; + int alive_threads; + int max_alive_threads; + ThreadContext *threads[kMaxTid]; + int dead_list_size; + ThreadContext* dead_list_head; + ThreadContext* dead_list_tail; + + Vector<RacyStacks> racy_stacks; + Vector<RacyAddress> racy_addresses; + + Flags flags; + + u64 stat[StatCnt]; + u64 int_alloc_cnt[MBlockTypeCount]; + u64 int_alloc_siz[MBlockTypeCount]; +}; + +class ScopedInRtl { + public: + ScopedInRtl(); + ~ScopedInRtl(); + private: + ThreadState*thr_; + int in_rtl_; + int errno_; +}; + +class ScopedReport { + public: + explicit ScopedReport(ReportType typ); + ~ScopedReport(); + + void AddStack(const StackTrace *stack); + void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack); + void AddThread(const ThreadContext *tctx); + void AddMutex(const SyncVar *s); + void AddLocation(uptr addr, uptr size); + + const ReportDesc *GetReport() const; + + private: + Context *ctx_; + ReportDesc *rep_; + + ScopedReport(const ScopedReport&); + void operator = (const ScopedReport&); +}; + +void InternalAllocStatAggregate(Context *ctx, ThreadState *thr); +void StatAggregate(u64 *dst, u64 *src); +void StatOutput(u64 *stat); +void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) { + if (kCollectStats) + thr->stat[typ] += n; +} + +void InitializeShadowMemory(); +void InitializeInterceptors(); +void InitializeDynamicAnnotations(); +void Die() NORETURN; + +void ReportRace(ThreadState *thr); +bool OutputReport(const ScopedReport &srep, ReportStack *suppress_stack = 0); +bool IsExpectedReport(uptr addr, uptr size); + +#if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 1 +# define DPrintf Printf +#else +# define DPrintf(...) +#endif + +#if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 2 +# define DPrintf2 Printf +#else +# define DPrintf2(...) +#endif + +void Initialize(ThreadState *thr); +int Finalize(ThreadState *thr); + +void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite); +void MemoryAccessImpl(ThreadState *thr, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + u64 *shadow_mem, Shadow cur); +void MemoryRead1Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryWrite1Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryRead8Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryWrite8Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, + uptr size, bool is_write); +void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size); +void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size); +void IgnoreCtl(ThreadState *thr, bool write, bool begin); + +void FuncEntry(ThreadState *thr, uptr pc); +void FuncExit(ThreadState *thr); + +int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached); +void ThreadStart(ThreadState *thr, int tid); +void ThreadFinish(ThreadState *thr); +int ThreadTid(ThreadState *thr, uptr pc, uptr uid); +void ThreadJoin(ThreadState *thr, uptr pc, int tid); +void ThreadDetach(ThreadState *thr, uptr pc, int tid); +void ThreadFinalize(ThreadState *thr); + +void MutexCreate(ThreadState *thr, uptr pc, uptr addr, bool rw, bool recursive); +void MutexDestroy(ThreadState *thr, uptr pc, uptr addr); +void MutexLock(ThreadState *thr, uptr pc, uptr addr); +void MutexUnlock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadLock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr); + +void Acquire(ThreadState *thr, uptr pc, uptr addr); +void Release(ThreadState *thr, uptr pc, uptr addr); + +// The hacky call uses custom calling convention and an assembly thunk. +// It is considerably faster that a normal call for the caller +// if it is not executed (it is intended for slow paths from hot functions). +// The trick is that the call preserves all registers and the compiler +// does not treat it as a call. +// If it does not work for you, use normal call. +#if TSAN_DEBUG == 0 +// The caller may not create the stack frame for itself at all, +// so we create a reserve stack frame for it (1024b must be enough). +#define HACKY_CALL(f) \ + __asm__ __volatile__("sub $0x400, %%rsp;" \ + "call " #f "_thunk;" \ + "add $0x400, %%rsp;" ::: "memory"); +#else +#define HACKY_CALL(f) f() +#endif + +extern "C" void __tsan_trace_switch(); +void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, u64 epoch, + EventType typ, uptr addr) { + StatInc(thr, StatEvents); + if (UNLIKELY((epoch % kTracePartSize) == 0)) + HACKY_CALL(__tsan_trace_switch); + Event *evp = &thr->trace.events[epoch % kTraceSize]; + Event ev = (u64)addr | ((u64)typ << 61); + *evp = ev; +} + +} // namespace __tsan + +#endif // TSAN_RTL_H diff --git a/lib/tsan/rtl/tsan_rtl_amd64.S b/lib/tsan/rtl/tsan_rtl_amd64.S new file mode 100644 index 000000000..2028ec508 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_amd64.S @@ -0,0 +1,71 @@ +.section .text + +.globl __tsan_trace_switch_thunk +__tsan_trace_switch_thunk: + # Save scratch registers. + push %rax + push %rcx + push %rdx + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + # Align stack frame. + push %rbx # non-scratch + mov %rsp, %rbx # save current rsp + shr $4, %rsp # clear 4 lsb, align to 16 + shl $4, %rsp + + call __tsan_trace_switch + + # Unalign stack frame back. + mov %rbx, %rsp # restore the original rsp + pop %rbx + # Restore scratch registers. + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rdx + pop %rcx + pop %rax + ret + +.globl __tsan_report_race_thunk +__tsan_report_race_thunk: + # Save scratch registers. + push %rax + push %rcx + push %rdx + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + # Align stack frame. + push %rbx # non-scratch + mov %rsp, %rbx # save current rsp + shr $4, %rsp # clear 4 lsb, align to 16 + shl $4, %rsp + + call __tsan_report_race + + # Unalign stack frame back. + mov %rbx, %rsp # restore the original rsp + pop %rbx + # Restore scratch registers. + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rdx + pop %rcx + pop %rax + ret diff --git a/lib/tsan/rtl/tsan_rtl_mutex.cc b/lib/tsan/rtl/tsan_rtl_mutex.cc new file mode 100644 index 000000000..f144209d8 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_mutex.cc @@ -0,0 +1,209 @@ +//===-- tsan_rtl_mutex.cc ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_rtl.h" +#include "tsan_sync.h" +#include "tsan_report.h" +#include "tsan_symbolize.h" + +namespace __tsan { + +void MutexCreate(ThreadState *thr, uptr pc, uptr addr, + bool rw, bool recursive) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexCreate %lx\n", thr->tid, addr); + StatInc(thr, StatMutexCreate); + MemoryWrite1Byte(thr, pc, addr); + SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true); + s->is_rw = rw; + s->is_recursive = recursive; + s->mtx.Unlock(); +} + +void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexDestroy %lx\n", thr->tid, addr); + StatInc(thr, StatMutexDestroy); + MemoryWrite1Byte(thr, pc, addr); + SyncVar *s = ctx->synctab.GetAndRemove(thr, pc, addr); + if (s == 0) + return; + if (s->owner_tid != SyncVar::kInvalidTid && !s->is_broken) { + s->is_broken = true; + ScopedReport rep(ReportTypeMutexDestroyLocked); + rep.AddMutex(s); + rep.AddLocation(s->addr, 1); + OutputReport(rep); + } + DestroyAndFree(s); +} + +void MutexLock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexLock %lx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeLock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid == SyncVar::kInvalidTid) { + CHECK_EQ(s->recursion, 0); + s->owner_tid = thr->tid; + } else if (s->owner_tid == thr->tid) { + CHECK_GT(s->recursion, 0); + } else { + Printf("ThreadSanitizer WARNING: double lock\n"); + } + if (s->recursion == 0) { + StatInc(thr, StatMutexLock); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + thr->clock.acquire(&s->read_clock); + StatInc(thr, StatSyncAcquire); + } else if (!s->is_recursive) { + StatInc(thr, StatMutexRecLock); + } + s->recursion++; + s->mtx.Unlock(); +} + +void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexUnlock %lx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->recursion == 0) { + if (!s->is_broken) { + s->is_broken = true; + Printf("ThreadSanitizer WARNING: unlock of unlocked mutex\n"); + } + } else if (s->owner_tid != thr->tid) { + if (!s->is_broken) { + s->is_broken = true; + Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + } + } else { + s->recursion--; + if (s->recursion == 0) { + StatInc(thr, StatMutexUnlock); + s->owner_tid = SyncVar::kInvalidTid; + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + } else { + StatInc(thr, StatMutexRecUnlock); + } + } + s->mtx.Unlock(); +} + +void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadLock %lx\n", thr->tid, addr); + StatInc(thr, StatMutexReadLock); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRLock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + if (s->owner_tid != SyncVar::kInvalidTid) + Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n"); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + s->mtx.ReadUnlock(); +} + +void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadUnlock %lx\n", thr->tid, addr); + StatInc(thr, StatMutexReadUnlock); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid != SyncVar::kInvalidTid) + Printf("ThreadSanitizer WARNING: read unlock of a write locked mutex\n"); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->read_clock); + StatInc(thr, StatSyncRelease); + s->mtx.Unlock(); +} + +void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadOrWriteUnlock %lx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid == SyncVar::kInvalidTid) { + // Seems to be read unlock. + StatInc(thr, StatMutexReadUnlock); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->read_clock); + StatInc(thr, StatSyncRelease); + } else if (s->owner_tid == thr->tid) { + // Seems to be write unlock. + CHECK_GT(s->recursion, 0); + s->recursion--; + if (s->recursion == 0) { + StatInc(thr, StatMutexUnlock); + s->owner_tid = SyncVar::kInvalidTid; + // FIXME: Refactor me, plz. + // The sequence of events is quite tricky and doubled in several places. + // First, it's a bug to increment the epoch w/o writing to the trace. + // Then, the acquire/release logic can be factored out as well. + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + } else { + StatInc(thr, StatMutexRecUnlock); + } + } else if (!s->is_broken) { + s->is_broken = true; + Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + } + s->mtx.Unlock(); +} + +void Acquire(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: Acquire %lx\n", thr->tid, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + s->mtx.ReadUnlock(); +} + +void Release(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: Release %lx\n", thr->tid, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + s->mtx.Unlock(); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_report.cc b/lib/tsan/rtl/tsan_rtl_report.cc new file mode 100644 index 000000000..a5c541753 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_report.cc @@ -0,0 +1,354 @@ +//===-- tsan_rtl.cc ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_suppressions.h" +#include "tsan_symbolize.h" +#include "tsan_report.h" +#include "tsan_sync.h" +#include "tsan_mman.h" +#include "tsan_flags.h" +#include "tsan_placement_new.h" + +namespace __tsan { + +// Can be overriden by an application/test to intercept reports. +bool WEAK OnReport(const ReportDesc *rep, bool suppressed) { + (void)rep; + return suppressed; +} + +static void StackStripMain(ReportStack *stack) { + ReportStack *last_frame = 0; + ReportStack *last_frame2 = 0; + const char *prefix = "interception_wrap_"; + uptr prefix_len = internal_strlen(prefix); + const char *path_prefix = flags()->strip_path_prefix; + uptr path_prefix_len = internal_strlen(path_prefix); + for (ReportStack *ent = stack; ent; ent = ent->next) { + if (ent->func && 0 == internal_strncmp(ent->func, prefix, prefix_len)) + ent->func += prefix_len; + if (ent->file && 0 == internal_strncmp(ent->file, path_prefix, + path_prefix_len)) + ent->file += path_prefix_len; + if (ent->file && ent->file[0] == '.' && ent->file[1] == '/') + ent->file += 2; + last_frame2 = last_frame; + last_frame = ent; + } + + if (last_frame2 == 0) + return; + const char *last = last_frame->func; + const char *last2 = last_frame2->func; + // Strip frame above 'main' + if (last2 && 0 == internal_strcmp(last2, "main")) { + last_frame2->next = 0; + // Strip our internal thread start routine. + } else if (last && 0 == internal_strcmp(last, "__tsan_thread_start_func")) { + last_frame2->next = 0; + // Strip global ctors init. + } else if (last && 0 == internal_strcmp(last, "__do_global_ctors_aux")) { + last_frame2->next = 0; + // If both are 0, then we probably just failed to symbolize. + } else if (last || last2) { + // Ensure that we recovered stack completely. Trimmed stack + // can actually happen if we do not instrument some code, + // so it's only a DCHECK. However we must try hard to not miss it + // due to our fault. + Printf("Bottom stack frame of stack %lx is missed\n", stack->pc); + } +} + +static ReportStack *SymbolizeStack(const StackTrace& trace) { + if (trace.IsEmpty()) + return 0; + ReportStack *stack = 0; + for (uptr si = 0; si < trace.Size(); si++) { + // We obtain the return address, that is, address of the next instruction, + // so offset it by 1 byte. + bool is_last = (si == trace.Size() - 1); + ReportStack *ent = SymbolizeCode(trace.Get(si) - !is_last); + CHECK_NE(ent, 0); + ReportStack *last = ent; + while (last->next) { + last->pc += !is_last; + last = last->next; + } + last->pc += !is_last; + last->next = stack; + stack = ent; + } + StackStripMain(stack); + return stack; +} + +ScopedReport::ScopedReport(ReportType typ) { + ctx_ = CTX(); + void *mem = internal_alloc(MBlockReport, sizeof(ReportDesc)); + rep_ = new(mem) ReportDesc; + rep_->typ = typ; + ctx_->report_mtx.Lock(); +} + +ScopedReport::~ScopedReport() { + ctx_->report_mtx.Unlock(); + rep_->~ReportDesc(); + internal_free(rep_); +} + +void ScopedReport::AddStack(const StackTrace *stack) { + ReportStack **rs = rep_->stacks.PushBack(); + *rs = SymbolizeStack(*stack); +} + +void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, + const StackTrace *stack) { + void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); + ReportMop *mop = new(mem) ReportMop; + rep_->mops.PushBack(mop); + mop->tid = s.tid(); + mop->addr = addr + s.addr0(); + mop->size = s.size(); + mop->write = s.is_write(); + mop->nmutex = 0; + mop->stack = SymbolizeStack(*stack); +} + +void ScopedReport::AddThread(const ThreadContext *tctx) { + void *mem = internal_alloc(MBlockReportThread, sizeof(ReportThread)); + ReportThread *rt = new(mem) ReportThread(); + rep_->threads.PushBack(rt); + rt->id = tctx->tid; + rt->running = (tctx->status == ThreadStatusRunning); + rt->stack = SymbolizeStack(tctx->creation_stack); +} + +void ScopedReport::AddMutex(const SyncVar *s) { + void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); + ReportMutex *rm = new(mem) ReportMutex(); + rep_->mutexes.PushBack(rm); + rm->id = 42; + rm->stack = SymbolizeStack(s->creation_stack); +} + +void ScopedReport::AddLocation(uptr addr, uptr size) { + ReportStack *symb = SymbolizeData(addr); + if (symb) { + void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); + ReportLocation *loc = new(mem) ReportLocation(); + rep_->locs.PushBack(loc); + loc->type = ReportLocationGlobal; + loc->addr = addr; + loc->size = size; + loc->tid = 0; + loc->name = symb->func; + loc->file = symb->file; + loc->line = symb->line; + loc->stack = 0; + internal_free(symb); + } +} + +const ReportDesc *ScopedReport::GetReport() const { + return rep_; +} + +static void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { + ThreadContext *tctx = CTX()->threads[tid]; + if (tctx == 0) + return; + Trace* trace = 0; + if (tctx->status == ThreadStatusRunning) { + CHECK(tctx->thr); + trace = &tctx->thr->trace; + } else if (tctx->status == ThreadStatusFinished + || tctx->status == ThreadStatusDead) { + trace = &tctx->dead_info.trace; + } else { + return; + } + Lock l(&trace->mtx); + const int partidx = (epoch / (kTraceSize / kTraceParts)) % kTraceParts; + TraceHeader* hdr = &trace->headers[partidx]; + if (epoch < hdr->epoch0) + return; + const u64 eend = epoch % kTraceSize; + const u64 ebegin = eend / kTracePartSize * kTracePartSize; + DPrintf("#%d: RestoreStack epoch=%llu ebegin=%llu eend=%llu partidx=%d\n", + tid, epoch, ebegin, eend, partidx); + InternalScopedBuf<uptr> stack(1024); // FIXME: de-hardcode 1024 + for (uptr i = 0; i < hdr->stack0.Size(); i++) { + stack[i] = hdr->stack0.Get(i); + DPrintf2(" #%02lu: pc=%lx\n", i, stack[i]); + } + uptr pos = hdr->stack0.Size(); + for (uptr i = ebegin; i <= eend; i++) { + Event ev = trace->events[i]; + EventType typ = (EventType)(ev >> 61); + uptr pc = (uptr)(ev & 0xffffffffffffull); + DPrintf2(" %lu typ=%d pc=%lx\n", i, typ, pc); + if (typ == EventTypeMop) { + stack[pos] = pc; + } else if (typ == EventTypeFuncEnter) { + stack[pos++] = pc; + } else if (typ == EventTypeFuncExit) { + // Since we have full stacks, this should never happen. + DCHECK_GT(pos, 0); + if (pos > 0) + pos--; + } + for (uptr j = 0; j <= pos; j++) + DPrintf2(" #%lu: %lx\n", j, stack[j]); + } + if (pos == 0 && stack[0] == 0) + return; + pos++; + stk->Init(stack, pos); +} + +static bool HandleRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], + uptr addr_min, uptr addr_max) { + Context *ctx = CTX(); + bool equal_stack = false; + RacyStacks hash = {}; + if (flags()->suppress_equal_stacks) { + hash.hash[0] = md5_hash(traces[0].Begin(), traces[0].Size() * sizeof(uptr)); + hash.hash[1] = md5_hash(traces[1].Begin(), traces[1].Size() * sizeof(uptr)); + for (uptr i = 0; i < ctx->racy_stacks.Size(); i++) { + if (hash == ctx->racy_stacks[i]) { + DPrintf("ThreadSanitizer: suppressing report as doubled (stack)\n"); + equal_stack = true; + break; + } + } + } + bool equal_address = false; + RacyAddress ra0 = {addr_min, addr_max}; + if (flags()->suppress_equal_addresses) { + for (uptr i = 0; i < ctx->racy_addresses.Size(); i++) { + RacyAddress ra2 = ctx->racy_addresses[i]; + uptr maxbeg = max(ra0.addr_min, ra2.addr_min); + uptr minend = min(ra0.addr_max, ra2.addr_max); + if (maxbeg < minend) { + DPrintf("ThreadSanitizer: suppressing report as doubled (addr)\n"); + equal_address = true; + break; + } + } + } + if (equal_stack || equal_address) { + if (!equal_stack) + ctx->racy_stacks.PushBack(hash); + if (!equal_address) + ctx->racy_addresses.PushBack(ra0); + return true; + } + return false; +} + +static void AddRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], + uptr addr_min, uptr addr_max) { + Context *ctx = CTX(); + if (flags()->suppress_equal_stacks) { + RacyStacks hash; + hash.hash[0] = md5_hash(traces[0].Begin(), traces[0].Size() * sizeof(uptr)); + hash.hash[1] = md5_hash(traces[1].Begin(), traces[1].Size() * sizeof(uptr)); + ctx->racy_stacks.PushBack(hash); + } + if (flags()->suppress_equal_addresses) { + RacyAddress ra0 = {addr_min, addr_max}; + ctx->racy_addresses.PushBack(ra0); + } +} + +bool OutputReport(const ScopedReport &srep, ReportStack *suppress_stack) { + const ReportDesc *rep = srep.GetReport(); + bool suppressed = IsSuppressed(rep->typ, suppress_stack); + suppressed = OnReport(rep, suppressed); + if (suppressed) + return false; + PrintReport(rep); + CTX()->nreported++; + return true; +} + +void ReportRace(ThreadState *thr) { + ScopedInRtl in_rtl; + uptr addr = ShadowToMem((uptr)thr->racy_shadow_addr); + uptr addr_min = 0; + uptr addr_max = 0; + { + uptr a0 = addr + Shadow(thr->racy_state[0]).addr0(); + uptr a1 = addr + Shadow(thr->racy_state[1]).addr0(); + uptr e0 = a0 + Shadow(thr->racy_state[0]).size(); + uptr e1 = a1 + Shadow(thr->racy_state[1]).size(); + addr_min = min(a0, a1); + addr_max = max(e0, e1); + if (IsExpectedReport(addr_min, addr_max - addr_min)) + return; + } + + Context *ctx = CTX(); + Lock l0(&ctx->thread_mtx); + + ScopedReport rep(ReportTypeRace); + const uptr nmop = thr->racy_state[1] == kShadowFreed ? 1 : 2; + + StackTrace traces[2]; + for (uptr i = 0; i < nmop; i++) { + Shadow s(thr->racy_state[i]); + RestoreStack(s.tid(), s.epoch(), &traces[i]); + } + + if (HandleRacyStacks(thr, traces, addr_min, addr_max)) + return; + + for (uptr i = 0; i < nmop; i++) { + Shadow s(thr->racy_state[i]); + rep.AddMemoryAccess(addr, s, &traces[i]); + } + + // Ensure that we have at least something for the current thread. + CHECK_EQ(traces[0].IsEmpty(), false); + + for (uptr i = 0; i < nmop; i++) { + FastState s(thr->racy_state[i]); + ThreadContext *tctx = ctx->threads[s.tid()]; + if (s.epoch() < tctx->epoch0 || s.epoch() > tctx->epoch1) + continue; + rep.AddThread(tctx); + } + + if (!OutputReport(rep, rep.GetReport()->mops[0]->stack)) + return; + + AddRacyStacks(thr, traces, addr_min, addr_max); + + // Bump the thread's clock a bit. + // This avoids series of similar reports between the same threads + // that happen close to each other (e.g. accessing several fields + // of the same object). + FastState s(thr->racy_state[1]); + thr->clock.set(s.tid(), s.epoch() + 100); +} + +void CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2) { + ScopedInRtl in_rtl; + Printf("FATAL: ThreadSanitizer CHECK failed: %s:%d \"%s\" (%llx, %llx)\n", + file, line, cond, v1, v2); + Die(); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_thread.cc b/lib/tsan/rtl/tsan_rtl_thread.cc new file mode 100644 index 000000000..b87482642 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_thread.cc @@ -0,0 +1,368 @@ +//===-- tsan_rtl_thread.cc --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "tsan_placement_new.h" +#include "tsan_platform.h" +#include "tsan_report.h" +#include "tsan_sync.h" + +namespace __tsan { + +const int kThreadQuarantineSize = 100; + +static void MaybeReportThreadLeak(ThreadContext *tctx) { + if (tctx->detached) + return; + if (tctx->status != ThreadStatusCreated + && tctx->status != ThreadStatusRunning + && tctx->status != ThreadStatusFinished) + return; + ScopedReport rep(ReportTypeThreadLeak); + rep.AddThread(tctx); + OutputReport(rep); +} + +void ThreadFinalize(ThreadState *thr) { + CHECK_GT(thr->in_rtl, 0); + if (!flags()->report_thread_leaks) + return; + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + for (int i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + MaybeReportThreadLeak(tctx); + DestroyAndFree(tctx); + ctx->threads[i] = 0; + } +} + +static void ThreadDead(ThreadState *thr, ThreadContext *tctx) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + CHECK(tctx->status == ThreadStatusRunning + || tctx->status == ThreadStatusFinished); + DPrintf("#%d: ThreadDead uid=%lu\n", thr->tid, tctx->user_id); + tctx->status = ThreadStatusDead; + tctx->user_id = 0; + tctx->sync.Reset(); + + // Put to dead list. + tctx->dead_next = 0; + if (ctx->dead_list_size == 0) + ctx->dead_list_head = tctx; + else + ctx->dead_list_tail->dead_next = tctx; + ctx->dead_list_tail = tctx; + ctx->dead_list_size++; +} + +int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { + CHECK_GT(thr->in_rtl, 0); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + StatInc(thr, StatThreadCreate); + int tid = -1; + ThreadContext *tctx = 0; + if (ctx->dead_list_size > kThreadQuarantineSize + || ctx->thread_seq >= kMaxTid) { + if (ctx->dead_list_size == 0) { + Printf("ThreadSanitizer: %d thread limit exceeded. Dying.\n", kMaxTid); + Die(); + } + StatInc(thr, StatThreadReuse); + tctx = ctx->dead_list_head; + ctx->dead_list_head = tctx->dead_next; + ctx->dead_list_size--; + if (ctx->dead_list_size == 0) { + CHECK_EQ(tctx->dead_next, 0); + ctx->dead_list_head = 0; + } + CHECK_EQ(tctx->status, ThreadStatusDead); + tctx->status = ThreadStatusInvalid; + tctx->reuse_count++; + tid = tctx->tid; + // The point to reclain dead_info. + // delete tctx->dead_info; + } else { + StatInc(thr, StatThreadMaxTid); + tid = ctx->thread_seq++; + void *mem = internal_alloc(MBlockThreadContex, sizeof(ThreadContext)); + tctx = new(mem) ThreadContext(tid); + ctx->threads[tid] = tctx; + } + CHECK_NE(tctx, 0); + CHECK_GE(tid, 0); + CHECK_LT(tid, kMaxTid); + DPrintf("#%d: ThreadCreate tid=%d uid=%lu\n", thr->tid, tid, uid); + CHECK_EQ(tctx->status, ThreadStatusInvalid); + ctx->alive_threads++; + if (ctx->max_alive_threads < ctx->alive_threads) { + ctx->max_alive_threads++; + CHECK_EQ(ctx->max_alive_threads, ctx->alive_threads); + StatInc(thr, StatThreadMaxAlive); + } + tctx->status = ThreadStatusCreated; + tctx->thr = 0; + tctx->user_id = uid; + tctx->unique_id = ctx->unique_thread_seq++; + tctx->detached = detached; + if (tid) { + thr->fast_state.IncrementEpoch(); + // Can't increment epoch w/o writing to the trace as well. + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&tctx->sync); + StatInc(thr, StatSyncRelease); + + tctx->creation_stack.ObtainCurrent(thr, pc); + } + return tid; +} + +void ThreadStart(ThreadState *thr, int tid) { + CHECK_GT(thr->in_rtl, 0); + uptr stk_addr = 0; + uptr stk_size = 0; + uptr tls_addr = 0; + uptr tls_size = 0; + GetThreadStackAndTls(&stk_addr, &stk_size, &tls_addr, &tls_size); + + MemoryResetRange(thr, /*pc=*/ 1, stk_addr, stk_size); + + // Check that the thr object is in tls; + const uptr thr_beg = (uptr)thr; + const uptr thr_end = (uptr)thr + sizeof(*thr); + CHECK_GE(thr_beg, tls_addr); + CHECK_LE(thr_beg, tls_addr + tls_size); + CHECK_GE(thr_end, tls_addr); + CHECK_LE(thr_end, tls_addr + tls_size); + // Since the thr object is huge, skip it. + MemoryResetRange(thr, /*pc=*/ 2, tls_addr, thr_beg - tls_addr); + MemoryResetRange(thr, /*pc=*/ 2, thr_end, tls_addr + tls_size - thr_end); + + Lock l(&CTX()->thread_mtx); + ThreadContext *tctx = CTX()->threads[tid]; + CHECK_NE(tctx, 0); + CHECK_EQ(tctx->status, ThreadStatusCreated); + tctx->status = ThreadStatusRunning; + tctx->epoch0 = tctx->epoch1 + 1; + tctx->epoch1 = (u64)-1; + new(thr) ThreadState(CTX(), tid, tctx->epoch0, stk_addr, stk_size, + tls_addr, tls_size); + tctx->thr = thr; + thr->fast_synch_epoch = tctx->epoch0; + thr->clock.set(tid, tctx->epoch0); + thr->clock.acquire(&tctx->sync); + StatInc(thr, StatSyncAcquire); + DPrintf("#%d: ThreadStart epoch=%llu stk_addr=%lx stk_size=%lx " + "tls_addr=%lx tls_size=%lx\n", + tid, tctx->epoch0, stk_addr, stk_size, tls_addr, tls_size); +} + +void ThreadFinish(ThreadState *thr) { + CHECK_GT(thr->in_rtl, 0); + StatInc(thr, StatThreadFinish); + // FIXME: Treat it as write. + if (thr->stk_addr && thr->stk_size) + MemoryResetRange(thr, /*pc=*/ 3, thr->stk_addr, thr->stk_size); + if (thr->tls_addr && thr->tls_size) { + const uptr thr_beg = (uptr)thr; + const uptr thr_end = (uptr)thr + sizeof(*thr); + // Since the thr object is huge, skip it. + MemoryResetRange(thr, /*pc=*/ 4, thr->tls_addr, thr_beg - thr->tls_addr); + MemoryResetRange(thr, /*pc=*/ 5, + thr_end, thr->tls_addr + thr->tls_size - thr_end); + } + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[thr->tid]; + CHECK_NE(tctx, 0); + CHECK_EQ(tctx->status, ThreadStatusRunning); + CHECK_GT(ctx->alive_threads, 0); + ctx->alive_threads--; + if (tctx->detached) { + ThreadDead(thr, tctx); + } else { + thr->fast_state.IncrementEpoch(); + // Can't increment epoch w/o writing to the trace as well. + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&tctx->sync); + StatInc(thr, StatSyncRelease); + tctx->status = ThreadStatusFinished; + } + + // Save from info about the thread. + // If dead_info will become dynamically allocated again, + // it is the point to allocate it. + // tctx->dead_info = new ThreadDeadInfo; + internal_memcpy(&tctx->dead_info.trace.events[0], + &thr->trace.events[0], sizeof(thr->trace.events)); + for (int i = 0; i < kTraceParts; i++) { + tctx->dead_info.trace.headers[i].stack0.CopyFrom( + thr->trace.headers[i].stack0); + } + tctx->epoch1 = thr->clock.get(tctx->tid); + + thr->~ThreadState(); + StatAggregate(ctx->stat, thr->stat); + InternalAllocStatAggregate(ctx, thr); + tctx->thr = 0; +} + +int ThreadTid(ThreadState *thr, uptr pc, uptr uid) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: ThreadTid uid=%lu\n", thr->tid, uid); + Lock l(&CTX()->thread_mtx); + for (int tid = 0; tid < kMaxTid; tid++) { + if (CTX()->threads[tid] != 0 + && CTX()->threads[tid]->user_id == uid + && CTX()->threads[tid]->status != ThreadStatusInvalid) + return tid; + } + return -1; +} + +void ThreadJoin(ThreadState *thr, uptr pc, int tid) { + CHECK_GT(thr->in_rtl, 0); + CHECK_GT(tid, 0); + CHECK_LT(tid, kMaxTid); + DPrintf("#%d: ThreadJoin tid=%d\n", thr->tid, tid); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[tid]; + if (tctx->status == ThreadStatusInvalid) { + Printf("ThreadSanitizer: join of non-existent thread\n"); + return; + } + CHECK_EQ(tctx->detached, false); + CHECK_EQ(tctx->status, ThreadStatusFinished); + thr->clock.acquire(&tctx->sync); + StatInc(thr, StatSyncAcquire); + ThreadDead(thr, tctx); +} + +void ThreadDetach(ThreadState *thr, uptr pc, int tid) { + CHECK_GT(thr->in_rtl, 0); + CHECK_GT(tid, 0); + CHECK_LT(tid, kMaxTid); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[tid]; + if (tctx->status == ThreadStatusInvalid) { + Printf("ThreadSanitizer: detach of non-existent thread\n"); + return; + } + if (tctx->status == ThreadStatusFinished) { + ThreadDead(thr, tctx); + } else { + tctx->detached = true; + } +} + +void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, + uptr size, bool is_write) { + if (size == 0) + return; + + u64 *shadow_mem = (u64*)MemToShadow(addr); + DPrintf2("#%d: MemoryAccessRange: @%p %p size=%d is_write=%d\n", + thr->tid, (void*)pc, (void*)addr, + (int)size, is_write); + +#if TSAN_DEBUG + if (!IsAppMem(addr)) { + Printf("Access to non app mem %lx\n", addr); + DCHECK(IsAppMem(addr)); + } + if (!IsAppMem(addr + size - 1)) { + Printf("Access to non app mem %lx\n", addr + size - 1); + DCHECK(IsAppMem(addr + size - 1)); + } + if (!IsShadowMem((uptr)shadow_mem)) { + Printf("Bad shadow addr %p (%lx)\n", shadow_mem, addr); + DCHECK(IsShadowMem((uptr)shadow_mem)); + } + if (!IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))) { + Printf("Bad shadow addr %p (%lx)\n", + shadow_mem + size * kShadowCnt / 8 - 1, addr + size - 1); + DCHECK(IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))); + } +#endif + + StatInc(thr, StatMopRange); + + FastState fast_state = thr->fast_state; + if (fast_state.GetIgnoreBit()) + return; + + fast_state.IncrementEpoch(); + thr->fast_state = fast_state; + TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + + bool unaligned = (addr % kShadowCell) != 0; + + // Handle unaligned beginning, if any. + for (; addr % kShadowCell && size; addr++, size--) { + int const kAccessSizeLog = 0; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + } + if (unaligned) + shadow_mem += kShadowCnt; + // Handle middle part, if any. + for (; size >= kShadowCell; addr += kShadowCell, size -= kShadowCell) { + int const kAccessSizeLog = 3; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(0, kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + shadow_mem += kShadowCnt; + } + // Handle ending, if any. + for (; size; addr++, size--) { + int const kAccessSizeLog = 0; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + } +} + +void MemoryRead1Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 0, 0); +} + +void MemoryWrite1Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 0, 1); +} + +void MemoryRead8Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 3, 0); +} + +void MemoryWrite8Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 3, 1); +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_stat.cc b/lib/tsan/rtl/tsan_stat.cc new file mode 100644 index 000000000..21e1ee73f --- /dev/null +++ b/lib/tsan/rtl/tsan_stat.cc @@ -0,0 +1,247 @@ +//===-- tsan_stat.cc --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_stat.h" +#include "tsan_rtl.h" + +namespace __tsan { + +void StatAggregate(u64 *dst, u64 *src) { + if (!kCollectStats) + return; + for (int i = 0; i < StatCnt; i++) + dst[i] += src[i]; +} + +void StatOutput(u64 *stat) { + if (!kCollectStats) + return; + + stat[StatShadowNonZero] = stat[StatShadowProcessed] - stat[StatShadowZero]; + + static const char *name[StatCnt] = {}; + name[StatMop] = "Memory accesses "; + name[StatMopRead] = " Including reads "; + name[StatMopWrite] = " writes "; + name[StatMop1] = " Including size 1 "; + name[StatMop2] = " size 2 "; + name[StatMop4] = " size 4 "; + name[StatMop8] = " size 8 "; + name[StatMopSame] = " Including same "; + name[StatMopRange] = " Including range "; + name[StatShadowProcessed] = "Shadow processed "; + name[StatShadowZero] = " Including empty "; + name[StatShadowNonZero] = " Including non empty "; + name[StatShadowSameSize] = " Including same size "; + name[StatShadowIntersect] = " intersect "; + name[StatShadowNotIntersect] = " not intersect "; + name[StatShadowSameThread] = " Including same thread "; + name[StatShadowAnotherThread] = " another thread "; + name[StatShadowReplace] = " Including evicted "; + + name[StatFuncEnter] = "Function entries "; + name[StatFuncExit] = "Function exits "; + name[StatEvents] = "Events collected "; + + name[StatThreadCreate] = "Total threads created "; + name[StatThreadFinish] = " threads finished "; + name[StatThreadReuse] = " threads reused "; + name[StatThreadMaxTid] = " max tid "; + name[StatThreadMaxAlive] = " max alive threads "; + + name[StatMutexCreate] = "Mutexes created "; + name[StatMutexDestroy] = " destroyed "; + name[StatMutexLock] = " lock "; + name[StatMutexUnlock] = " unlock "; + name[StatMutexRecLock] = " recursive lock "; + name[StatMutexRecUnlock] = " recursive unlock "; + name[StatMutexReadLock] = " read lock "; + name[StatMutexReadUnlock] = " read unlock "; + + name[StatSyncCreated] = "Sync objects created "; + name[StatSyncDestroyed] = " destroyed "; + name[StatSyncAcquire] = " acquired "; + name[StatSyncRelease] = " released "; + + name[StatAtomic] = "Atomic operations "; + name[StatAtomicLoad] = " Including load "; + name[StatAtomicStore] = " store "; + name[StatAtomicExchange] = " exchange "; + name[StatAtomicFetchAdd] = " fetch_add "; + name[StatAtomicCAS] = " compare_exchange "; + name[StatAtomicFence] = " fence "; + name[StatAtomicRelaxed] = " Including relaxed "; + name[StatAtomicConsume] = " consume "; + name[StatAtomicAcquire] = " acquire "; + name[StatAtomicRelease] = " release "; + name[StatAtomicAcq_Rel] = " acq_rel "; + name[StatAtomicSeq_Cst] = " seq_cst "; + name[StatAtomic1] = " Including size 1 "; + name[StatAtomic2] = " size 2 "; + name[StatAtomic4] = " size 4 "; + name[StatAtomic8] = " size 8 "; + + name[StatInterceptor] = "Interceptors "; + name[StatInt_malloc] = " malloc "; + name[StatInt_calloc] = " calloc "; + name[StatInt_realloc] = " realloc "; + name[StatInt_free] = " free "; + name[StatInt_cfree] = " cfree "; + name[StatInt_mmap] = " mmap "; + name[StatInt_mmap64] = " mmap64 "; + name[StatInt_munmap] = " munmap "; + name[StatInt_memalign] = " memalign "; + name[StatInt_valloc] = " valloc "; + name[StatInt_pvalloc] = " pvalloc "; + name[StatInt_posix_memalign] = " posix_memalign "; + name[StatInt__Znwm] = " _Znwm "; + name[StatInt__ZnwmRKSt9nothrow_t] = " _ZnwmRKSt9nothrow_t "; + name[StatInt__Znam] = " _Znam "; + name[StatInt__ZnamRKSt9nothrow_t] = " _ZnamRKSt9nothrow_t "; + name[StatInt__ZdlPv] = " _ZdlPv "; + name[StatInt__ZdlPvRKSt9nothrow_t] = " _ZdlPvRKSt9nothrow_t "; + name[StatInt__ZdaPv] = " _ZdaPv "; + name[StatInt__ZdaPvRKSt9nothrow_t] = " _ZdaPvRKSt9nothrow_t "; + name[StatInt_strlen] = " strlen "; + name[StatInt_memset] = " memset "; + name[StatInt_memcpy] = " memcpy "; + name[StatInt_strcmp] = " strcmp "; + name[StatInt_memchr] = " memchr "; + name[StatInt_memrchr] = " memrchr "; + name[StatInt_memmove] = " memmove "; + name[StatInt_memcmp] = " memcmp "; + name[StatInt_strchr] = " strchr "; + name[StatInt_strchrnul] = " strchrnul "; + name[StatInt_strrchr] = " strrchr "; + name[StatInt_strncmp] = " strncmp "; + name[StatInt_strcpy] = " strcpy "; + name[StatInt_strncpy] = " strncpy "; + name[StatInt_strstr] = " strstr "; + name[StatInt_atexit] = " atexit "; + name[StatInt___cxa_guard_acquire] = " __cxa_guard_acquire "; + name[StatInt___cxa_guard_release] = " __cxa_guard_release "; + name[StatInt_pthread_create] = " pthread_create "; + name[StatInt_pthread_join] = " pthread_join "; + name[StatInt_pthread_detach] = " pthread_detach "; + name[StatInt_pthread_mutex_init] = " pthread_mutex_init "; + name[StatInt_pthread_mutex_destroy] = " pthread_mutex_destroy "; + name[StatInt_pthread_mutex_lock] = " pthread_mutex_lock "; + name[StatInt_pthread_mutex_trylock] = " pthread_mutex_trylock "; + name[StatInt_pthread_mutex_timedlock] = " pthread_mutex_timedlock "; + name[StatInt_pthread_mutex_unlock] = " pthread_mutex_unlock "; + name[StatInt_pthread_spin_init] = " pthread_spin_init "; + name[StatInt_pthread_spin_destroy] = " pthread_spin_destroy "; + name[StatInt_pthread_spin_lock] = " pthread_spin_lock "; + name[StatInt_pthread_spin_trylock] = " pthread_spin_trylock "; + name[StatInt_pthread_spin_unlock] = " pthread_spin_unlock "; + name[StatInt_pthread_rwlock_init] = " pthread_rwlock_init "; + name[StatInt_pthread_rwlock_destroy] = " pthread_rwlock_destroy "; + name[StatInt_pthread_rwlock_rdlock] = " pthread_rwlock_rdlock "; + name[StatInt_pthread_rwlock_tryrdlock] = " pthread_rwlock_tryrdlock "; + name[StatInt_pthread_rwlock_timedrdlock] + = " pthread_rwlock_timedrdlock "; + name[StatInt_pthread_rwlock_wrlock] = " pthread_rwlock_wrlock "; + name[StatInt_pthread_rwlock_trywrlock] = " pthread_rwlock_trywrlock "; + name[StatInt_pthread_rwlock_timedwrlock] + = " pthread_rwlock_timedwrlock "; + name[StatInt_pthread_rwlock_unlock] = " pthread_rwlock_unlock "; + name[StatInt_pthread_cond_init] = " pthread_cond_init "; + name[StatInt_pthread_cond_destroy] = " pthread_cond_destroy "; + name[StatInt_pthread_cond_signal] = " pthread_cond_signal "; + name[StatInt_pthread_cond_broadcast] = " pthread_cond_broadcast "; + name[StatInt_pthread_cond_wait] = " pthread_cond_wait "; + name[StatInt_pthread_cond_timedwait] = " pthread_cond_timedwait "; + name[StatInt_pthread_barrier_init] = " pthread_barrier_init "; + name[StatInt_pthread_barrier_destroy] = " pthread_barrier_destroy "; + name[StatInt_pthread_barrier_wait] = " pthread_barrier_wait "; + name[StatInt_pthread_once] = " pthread_once "; + name[StatInt_sem_init] = " sem_init "; + name[StatInt_sem_destroy] = " sem_destroy "; + name[StatInt_sem_wait] = " sem_wait "; + name[StatInt_sem_trywait] = " sem_trywait "; + name[StatInt_sem_timedwait] = " sem_timedwait "; + name[StatInt_sem_post] = " sem_post "; + name[StatInt_sem_getvalue] = " sem_getvalue "; + name[StatInt_read] = " read "; + name[StatInt_pread] = " pread "; + name[StatInt_pread64] = " pread64 "; + name[StatInt_readv] = " readv "; + name[StatInt_preadv64] = " preadv64 "; + name[StatInt_write] = " write "; + name[StatInt_pwrite] = " pwrite "; + name[StatInt_pwrite64] = " pwrite64 "; + name[StatInt_writev] = " writev "; + name[StatInt_pwritev64] = " pwritev64 "; + name[StatInt_send] = " send "; + name[StatInt_sendmsg] = " sendmsg "; + name[StatInt_recv] = " recv "; + name[StatInt_recvmsg] = " recvmsg "; + name[StatInt_unlink] = " unlink "; + name[StatInt_fopen] = " fopen "; + name[StatInt_fread] = " fread "; + name[StatInt_fwrite] = " fwrite "; + name[StatInt_puts] = " puts "; + name[StatInt_rmdir] = " rmdir "; + name[StatInt_opendir] = " opendir "; + name[StatInt_epoll_ctl] = " epoll_ctl "; + name[StatInt_epoll_wait] = " epoll_wait "; + name[StatInt_sigaction] = " sigaction "; + + name[StatAnnotation] = "Dynamic annotations "; + name[StatAnnotateHappensBefore] = " HappensBefore "; + name[StatAnnotateHappensAfter] = " HappensAfter "; + name[StatAnnotateCondVarSignal] = " CondVarSignal "; + name[StatAnnotateCondVarSignalAll] = " CondVarSignalAll "; + name[StatAnnotateMutexIsNotPHB] = " MutexIsNotPHB "; + name[StatAnnotateCondVarWait] = " CondVarWait "; + name[StatAnnotateRWLockCreate] = " RWLockCreate "; + name[StatAnnotateRWLockDestroy] = " RWLockDestroy "; + name[StatAnnotateRWLockAcquired] = " RWLockAcquired "; + name[StatAnnotateRWLockReleased] = " RWLockReleased "; + name[StatAnnotateTraceMemory] = " TraceMemory "; + name[StatAnnotateFlushState] = " FlushState "; + name[StatAnnotateNewMemory] = " NewMemory "; + name[StatAnnotateNoOp] = " NoOp "; + name[StatAnnotateFlushExpectedRaces] = " FlushExpectedRaces "; + name[StatAnnotateEnableRaceDetection] = " EnableRaceDetection "; + name[StatAnnotateMutexIsUsedAsCondVar] = " MutexIsUsedAsCondVar "; + name[StatAnnotatePCQGet] = " PCQGet "; + name[StatAnnotatePCQPut] = " PCQPut "; + name[StatAnnotatePCQDestroy] = " PCQDestroy "; + name[StatAnnotatePCQCreate] = " PCQCreate "; + name[StatAnnotateExpectRace] = " ExpectRace "; + name[StatAnnotateBenignRaceSized] = " BenignRaceSized "; + name[StatAnnotateBenignRace] = " BenignRace "; + name[StatAnnotateIgnoreReadsBegin] = " IgnoreReadsBegin "; + name[StatAnnotateIgnoreReadsEnd] = " IgnoreReadsEnd "; + name[StatAnnotateIgnoreWritesBegin] = " IgnoreWritesBegin "; + name[StatAnnotateIgnoreWritesEnd] = " IgnoreWritesEnd "; + name[StatAnnotatePublishMemoryRange] = " PublishMemoryRange "; + name[StatAnnotateUnpublishMemoryRange] = " UnpublishMemoryRange "; + name[StatAnnotateThreadName] = " ThreadName "; + + name[StatMtxTotal] = "Contentionz "; + name[StatMtxTrace] = " Trace "; + name[StatMtxThreads] = " Threads "; + name[StatMtxReport] = " Report "; + name[StatMtxSyncVar] = " SyncVar "; + name[StatMtxSyncTab] = " SyncTab "; + name[StatMtxSlab] = " Slab "; + name[StatMtxAtExit] = " Atexit "; + name[StatMtxAnnotations] = " Annotations "; + + Printf("Statistics:\n"); + for (int i = 0; i < StatCnt; i++) + Printf("%s: %llu\n", name[i], stat[i]); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_stat.h b/lib/tsan/rtl/tsan_stat.h new file mode 100644 index 000000000..b660b48ae --- /dev/null +++ b/lib/tsan/rtl/tsan_stat.h @@ -0,0 +1,245 @@ +//===-- tsan_stat.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_STAT_H +#define TSAN_STAT_H + +namespace __tsan { + +enum StatType { + // Memory access processing related stuff. + StatMop, + StatMopRead, + StatMopWrite, + StatMop1, // These must be consequtive. + StatMop2, + StatMop4, + StatMop8, + StatMopSame, + StatMopRange, + StatShadowProcessed, + StatShadowZero, + StatShadowNonZero, // Derived. + StatShadowSameSize, + StatShadowIntersect, + StatShadowNotIntersect, + StatShadowSameThread, + StatShadowAnotherThread, + StatShadowReplace, + + // Func processing. + StatFuncEnter, + StatFuncExit, + + // Trace processing. + StatEvents, + + // Threads. + StatThreadCreate, + StatThreadFinish, + StatThreadReuse, + StatThreadMaxTid, + StatThreadMaxAlive, + + // Mutexes. + StatMutexCreate, + StatMutexDestroy, + StatMutexLock, + StatMutexUnlock, + StatMutexRecLock, + StatMutexRecUnlock, + StatMutexReadLock, + StatMutexReadUnlock, + + // Synchronization. + StatSyncCreated, + StatSyncDestroyed, + StatSyncAcquire, + StatSyncRelease, + + // Atomics. + StatAtomic, + StatAtomicLoad, + StatAtomicStore, + StatAtomicExchange, + StatAtomicFetchAdd, + StatAtomicCAS, + StatAtomicFence, + StatAtomicRelaxed, + StatAtomicConsume, + StatAtomicAcquire, + StatAtomicRelease, + StatAtomicAcq_Rel, + StatAtomicSeq_Cst, + StatAtomic1, + StatAtomic2, + StatAtomic4, + StatAtomic8, + + // Interceptors. + StatInterceptor, + StatInt_malloc, + StatInt_calloc, + StatInt_realloc, + StatInt_free, + StatInt_cfree, + StatInt_mmap, + StatInt_mmap64, + StatInt_munmap, + StatInt_memalign, + StatInt_valloc, + StatInt_pvalloc, + StatInt_posix_memalign, + StatInt__Znwm, + StatInt__ZnwmRKSt9nothrow_t, + StatInt__Znam, + StatInt__ZnamRKSt9nothrow_t, + StatInt__ZdlPv, + StatInt__ZdlPvRKSt9nothrow_t, + StatInt__ZdaPv, + StatInt__ZdaPvRKSt9nothrow_t, + StatInt_strlen, + StatInt_memset, + StatInt_memcpy, + StatInt_strcmp, + StatInt_memchr, + StatInt_memrchr, + StatInt_memmove, + StatInt_memcmp, + StatInt_strchr, + StatInt_strchrnul, + StatInt_strrchr, + StatInt_strncmp, + StatInt_strcpy, + StatInt_strncpy, + StatInt_strstr, + StatInt_atexit, + StatInt___cxa_guard_acquire, + StatInt___cxa_guard_release, + StatInt_pthread_create, + StatInt_pthread_join, + StatInt_pthread_detach, + StatInt_pthread_mutex_init, + StatInt_pthread_mutex_destroy, + StatInt_pthread_mutex_lock, + StatInt_pthread_mutex_trylock, + StatInt_pthread_mutex_timedlock, + StatInt_pthread_mutex_unlock, + StatInt_pthread_spin_init, + StatInt_pthread_spin_destroy, + StatInt_pthread_spin_lock, + StatInt_pthread_spin_trylock, + StatInt_pthread_spin_unlock, + StatInt_pthread_rwlock_init, + StatInt_pthread_rwlock_destroy, + StatInt_pthread_rwlock_rdlock, + StatInt_pthread_rwlock_tryrdlock, + StatInt_pthread_rwlock_timedrdlock, + StatInt_pthread_rwlock_wrlock, + StatInt_pthread_rwlock_trywrlock, + StatInt_pthread_rwlock_timedwrlock, + StatInt_pthread_rwlock_unlock, + StatInt_pthread_cond_init, + StatInt_pthread_cond_destroy, + StatInt_pthread_cond_signal, + StatInt_pthread_cond_broadcast, + StatInt_pthread_cond_wait, + StatInt_pthread_cond_timedwait, + StatInt_pthread_barrier_init, + StatInt_pthread_barrier_destroy, + StatInt_pthread_barrier_wait, + StatInt_pthread_once, + StatInt_sem_init, + StatInt_sem_destroy, + StatInt_sem_wait, + StatInt_sem_trywait, + StatInt_sem_timedwait, + StatInt_sem_post, + StatInt_sem_getvalue, + StatInt_read, + StatInt_pread, + StatInt_pread64, + StatInt_readv, + StatInt_preadv64, + StatInt_write, + StatInt_pwrite, + StatInt_pwrite64, + StatInt_writev, + StatInt_pwritev64, + StatInt_send, + StatInt_sendmsg, + StatInt_recv, + StatInt_recvmsg, + StatInt_unlink, + StatInt_fopen, + StatInt_fread, + StatInt_fwrite, + StatInt_puts, + StatInt_rmdir, + StatInt_opendir, + StatInt_epoll_ctl, + StatInt_epoll_wait, + StatInt_sigaction, + + // Dynamic annotations. + StatAnnotation, + StatAnnotateHappensBefore, + StatAnnotateHappensAfter, + StatAnnotateCondVarSignal, + StatAnnotateCondVarSignalAll, + StatAnnotateMutexIsNotPHB, + StatAnnotateCondVarWait, + StatAnnotateRWLockCreate, + StatAnnotateRWLockDestroy, + StatAnnotateRWLockAcquired, + StatAnnotateRWLockReleased, + StatAnnotateTraceMemory, + StatAnnotateFlushState, + StatAnnotateNewMemory, + StatAnnotateNoOp, + StatAnnotateFlushExpectedRaces, + StatAnnotateEnableRaceDetection, + StatAnnotateMutexIsUsedAsCondVar, + StatAnnotatePCQGet, + StatAnnotatePCQPut, + StatAnnotatePCQDestroy, + StatAnnotatePCQCreate, + StatAnnotateExpectRace, + StatAnnotateBenignRaceSized, + StatAnnotateBenignRace, + StatAnnotateIgnoreReadsBegin, + StatAnnotateIgnoreReadsEnd, + StatAnnotateIgnoreWritesBegin, + StatAnnotateIgnoreWritesEnd, + StatAnnotatePublishMemoryRange, + StatAnnotateUnpublishMemoryRange, + StatAnnotateThreadName, + + // Internal mutex contentionz. + StatMtxTotal, + StatMtxTrace, + StatMtxThreads, + StatMtxReport, + StatMtxSyncVar, + StatMtxSyncTab, + StatMtxSlab, + StatMtxAnnotations, + StatMtxAtExit, + + // This must be the last. + StatCnt, +}; + +} // namespace __tsan + +#endif // TSAN_STAT_H diff --git a/lib/tsan/rtl/tsan_suppressions.cc b/lib/tsan/rtl/tsan_suppressions.cc new file mode 100644 index 000000000..d74361469 --- /dev/null +++ b/lib/tsan/rtl/tsan_suppressions.cc @@ -0,0 +1,173 @@ +//===-- tsan_suppressions.cc ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_suppressions.h" +#include "tsan_rtl.h" +#include "tsan_flags.h" +#include "tsan_mman.h" +#include "tsan_platform.h" + +namespace __tsan { + +static Suppression *g_suppressions; + +static char *ReadFile(const char *filename) { + if (filename == 0 || filename[0] == 0) + return 0; + InternalScopedBuf<char> tmp(4*1024); + if (filename[0] == '/') + Snprintf(tmp, tmp.Size(), "%s", filename); + else + Snprintf(tmp, tmp.Size(), "%s/%s", internal_getpwd(), filename); + fd_t fd = internal_open(tmp, false); + if (fd == kInvalidFd) { + Printf("ThreadSanitizer: failed to open suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + const uptr fsize = internal_filesize(fd); + if (fsize == (uptr)-1) { + Printf("ThreadSanitizer: failed to stat suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + char *buf = (char*)internal_alloc(MBlockSuppression, fsize + 1); + if (fsize != internal_read(fd, buf, fsize)) { + Printf("ThreadSanitizer: failed to read suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + internal_close(fd); + buf[fsize] = 0; + return buf; +} + +bool SuppressionMatch(char *templ, const char *str) { + char *tpos; + const char *spos; + while (templ && templ[0]) { + if (templ[0] == '*') { + templ++; + continue; + } + if (str[0] == 0) + return false; + tpos = (char*)internal_strchr(templ, '*'); + if (tpos != 0) + tpos[0] = 0; + spos = internal_strstr(str, templ); + str = spos + internal_strlen(templ); + templ = tpos; + if (tpos) + tpos[0] = '*'; + if (spos == 0) + return false; + } + return true; +} + +Suppression *SuppressionParse(const char* supp) { + Suppression *head = 0; + const char *line = supp; + while (line) { + while (line[0] == ' ' || line[0] == '\t') + line++; + const char *end = internal_strchr(line, '\n'); + if (end == 0) + end = line + internal_strlen(line); + if (line != end && line[0] != '#') { + const char *end2 = end; + while (line != end2 && (end2[-1] == ' ' || end2[-1] == '\t')) + end2--; + SuppressionType stype; + if (0 == internal_strncmp(line, "race:", sizeof("race:") - 1)) { + stype = SuppressionRace; + line += sizeof("race:") - 1; + } else if (0 == internal_strncmp(line, "thread:", + sizeof("thread:") - 1)) { + stype = SuppressionThread; + line += sizeof("thread:") - 1; + } else if (0 == internal_strncmp(line, "mutex:", + sizeof("mutex:") - 1)) { + stype = SuppressionMutex; + line += sizeof("mutex:") - 1; + } else if (0 == internal_strncmp(line, "signal:", + sizeof("signal:") - 1)) { + stype = SuppressionSignal; + line += sizeof("signal:") - 1; + } else { + Printf("ThreadSanitizer: failed to parse suppressions file\n"); + Die(); + } + Suppression *s = (Suppression*)internal_alloc(MBlockSuppression, + sizeof(Suppression)); + s->next = head; + head = s; + s->type = stype; + s->func = (char*)internal_alloc(MBlockSuppression, end2 - line + 1); + internal_memcpy(s->func, line, end2 - line); + s->func[end2 - line] = 0; + } + if (end[0] == 0) + break; + line = end + 1; + } + return head; +} + +void SuppressionFree(Suppression *supp) { + while (supp) { + Suppression *tmp = supp; + supp = tmp->next; + internal_free(tmp->func); + internal_free(tmp); + } +} + +void InitializeSuppressions() { + char *supp = ReadFile(flags()->suppressions); + g_suppressions = SuppressionParse(supp); +} + +void FinalizeSuppressions() { + SuppressionFree(g_suppressions); + g_suppressions = 0; +} + +bool IsSuppressed(ReportType typ, const ReportStack *stack) { + if (g_suppressions == 0 || stack == 0) + return false; + SuppressionType stype; + if (typ == ReportTypeRace) + stype = SuppressionRace; + else if (typ == ReportTypeThreadLeak) + stype = SuppressionThread; + else if (typ == ReportTypeMutexDestroyLocked) + stype = SuppressionMutex; + else if (typ == ReportTypeSignalUnsafe) + stype = SuppressionSignal; + else + return false; + for (const ReportStack *frame = stack; frame; frame = frame->next) { + if (frame->func == 0) + continue; + for (Suppression *supp = g_suppressions; supp; supp = supp->next) { + if (stype == supp->type && SuppressionMatch(supp->func, frame->func)) { + DPrintf("ThreadSanitizer: matched suppression '%s'\n", supp->func); + return true; + } + } + } + return false; +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_suppressions.h b/lib/tsan/rtl/tsan_suppressions.h new file mode 100644 index 000000000..06d7307a4 --- /dev/null +++ b/lib/tsan/rtl/tsan_suppressions.h @@ -0,0 +1,43 @@ +//===-- tsan_suppressions.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SUPPRESSIONS_H +#define TSAN_SUPPRESSIONS_H + +#include "tsan_report.h" + +namespace __tsan { + +void InitializeSuppressions(); +void FinalizeSuppressions(); +bool IsSuppressed(ReportType typ, const ReportStack *stack); + +// Exposed for testing. +enum SuppressionType { + SuppressionRace, + SuppressionMutex, + SuppressionThread, + SuppressionSignal, +}; + +struct Suppression { + Suppression *next; + SuppressionType type; + char *func; +}; +Suppression *SuppressionParse(const char* supp); +bool SuppressionMatch(char *templ, const char *str); +void SuppressionFree(Suppression *supp); + +} // namespace __tsan + +#endif // TSAN_SUPPRESSIONS_H diff --git a/lib/tsan/rtl/tsan_symbolize.h b/lib/tsan/rtl/tsan_symbolize.h new file mode 100644 index 000000000..4eaec4a68 --- /dev/null +++ b/lib/tsan/rtl/tsan_symbolize.h @@ -0,0 +1,26 @@ +//===-- tsan_symbolize.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SYMBOLIZE_H +#define TSAN_SYMBOLIZE_H + +#include "tsan_defs.h" +#include "tsan_report.h" + +namespace __tsan { + +ReportStack *SymbolizeCode(uptr addr); +ReportStack *SymbolizeData(uptr addr); + +} // namespace __tsan + +#endif // TSAN_SYMBOLIZE_H diff --git a/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc new file mode 100644 index 000000000..55b7c5d7c --- /dev/null +++ b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc @@ -0,0 +1,178 @@ +//===-- tsan_symbolize_addr2line.cc -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_symbolize.h" +#include "tsan_mman.h" +#include "tsan_rtl.h" + +#include <unistd.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <link.h> +#include <linux/limits.h> +#include <sys/types.h> +#include <sys/wait.h> + +namespace __tsan { + +static bool GetSymbolizerFd(int *infdp, int *outfdp) { + static int outfd[2]; + static int infd[2]; + static int pid = -1; + static int inited = 0; + if (inited == 0) { + inited = -1; + if (pipe(outfd)) { + Printf("ThreadSanitizer: pipe() failed (%d)\n", errno); + Die(); + } + if (pipe(infd)) { + Printf("ThreadSanitizer: pipe() failed (%d)\n", errno); + Die(); + } + pid = fork(); + if (pid == 0) { + close(STDOUT_FILENO); + close(STDIN_FILENO); + dup2(outfd[0], STDIN_FILENO); + dup2(infd[1], STDOUT_FILENO); + close(outfd[0]); + close(outfd[1]); + close(infd[0]); + close(infd[1]); + InternalScopedBuf<char> exe(PATH_MAX); + ssize_t len = readlink("/proc/self/exe", exe, exe.Size() - 1); + exe.Ptr()[len] = 0; + execl("/usr/bin/addr2line", "/usr/bin/addr2line", "-Cfe", exe.Ptr(), + NULL); + _exit(0); + } else if (pid < 0) { + Printf("ThreadSanitizer: failed to fork symbolizer\n"); + Die(); + } + close(outfd[0]); + close(infd[1]); + inited = 1; + } else if (inited > 0) { + int status = 0; + if (pid == waitpid(pid, &status, WNOHANG)) { + Printf("ThreadSanitizer: symbolizer died with status %d\n", + WEXITSTATUS(status)); + Die(); + } + } + *infdp = infd[0]; + *outfdp = outfd[1]; + return inited > 0; +} + +static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *ctx) { + *(uptr*)ctx = (uptr)info->dlpi_addr; + return 1; +} + +static uptr GetImageBase() { + static uptr base = 0; + if (base == 0) + dl_iterate_phdr(dl_iterate_phdr_cb, &base); + return base; +} + +ReportStack *SymbolizeCode(uptr addr) { + uptr base = GetImageBase(); + uptr offset = addr - base; + int infd = -1; + int outfd = -1; + if (!GetSymbolizerFd(&infd, &outfd)) + return 0; + char addrstr[32]; + Snprintf(addrstr, sizeof(addrstr), "%p\n", (void*)offset); + if (0 >= write(outfd, addrstr, internal_strlen(addrstr))) { + Printf("ThreadSanitizer: can't write from symbolizer\n"); + Die(); + } + InternalScopedBuf<char> func(1024); + ssize_t len = read(infd, func, func.Size() - 1); + if (len <= 0) { + Printf("ThreadSanitizer: can't read from symbolizer\n"); + Die(); + } + func.Ptr()[len] = 0; + ReportStack *res = (ReportStack*)internal_alloc(MBlockReportStack, + sizeof(ReportStack)); + internal_memset(res, 0, sizeof(*res)); + res->module = (char*)internal_alloc(MBlockReportStack, 4); + internal_memcpy(res->module, "exe", 4); + res->offset = offset; + res->pc = addr; + + char *pos = strchr(func, '\n'); + if (pos && func[0] != '?') { + res->func = (char*)internal_alloc(MBlockReportStack, pos - func + 1); + internal_memcpy(res->func, func, pos - func); + res->func[pos - func] = 0; + char *pos2 = strchr(pos, ':'); + if (pos2) { + res->file = (char*)internal_alloc(MBlockReportStack, pos2 - pos - 1 + 1); + internal_memcpy(res->file, pos + 1, pos2 - pos - 1); + res->file[pos2 - pos - 1] = 0; + res->line = atoi(pos2 + 1); + } + } + return res; +} + +ReportStack *SymbolizeData(uptr addr) { + return 0; + /* + if (base == 0) + base = GetImageBase(); + int res = 0; + InternalScopedBuf<char> cmd(1024); + Snprintf(cmd, cmd.Size(), + "nm -alC %s|grep \"%lx\"|awk '{printf(\"%%s\\n%%s\", $3, $4)}' > tsan.tmp2", + exe, (addr - base)); + if (system(cmd)) + return 0; + FILE* f3 = fopen("tsan.tmp2", "rb"); + if (f3) { + InternalScopedBuf<char> tmp(1024); + if (fread(tmp, 1, tmp.Size(), f3) <= 0) + return 0; + char *pos = strchr(tmp, '\n'); + if (pos && tmp[0] != '?') { + res = 1; + symb[0].module = 0; + symb[0].offset = addr; + symb[0].name = alloc->Alloc<char>(pos - tmp + 1); + internal_memcpy(symb[0].name, tmp, pos - tmp); + symb[0].name[pos - tmp] = 0; + symb[0].file = 0; + symb[0].line = 0; + char *pos2 = strchr(pos, ':'); + if (pos2) { + symb[0].file = alloc->Alloc<char>(pos2 - pos - 1 + 1); + internal_memcpy(symb[0].file, pos + 1, pos2 - pos - 1); + symb[0].file[pos2 - pos - 1] = 0; + symb[0].line = atoi(pos2 + 1); + } + } + fclose(f3); + } + return res; + */ +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_sync.cc b/lib/tsan/rtl/tsan_sync.cc new file mode 100644 index 000000000..0b31ab9ce --- /dev/null +++ b/lib/tsan/rtl/tsan_sync.cc @@ -0,0 +1,177 @@ +//===-- tsan_sync.cc --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_sync.h" +#include "tsan_placement_new.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" + +namespace __tsan { + +SyncVar::SyncVar(uptr addr) + : mtx(MutexTypeSyncVar, StatMtxSyncVar) + , addr(addr) + , owner_tid(kInvalidTid) + , recursion() + , is_rw() + , is_recursive() + , is_broken() { +} + +SyncTab::Part::Part() + : mtx(MutexTypeSyncTab, StatMtxSyncTab) + , val() { +} + +SyncTab::SyncTab() { +} + +SyncTab::~SyncTab() { + for (int i = 0; i < kPartCount; i++) { + while (tab_[i].val) { + SyncVar *tmp = tab_[i].val; + tab_[i].val = tmp->next; + DestroyAndFree(tmp); + } + } +} + +SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock) { + Part *p = &tab_[PartIdx(addr)]; + { + ReadLock l(&p->mtx); + for (SyncVar *res = p->val; res; res = res->next) { + if (res->addr == addr) { + if (write_lock) + res->mtx.Lock(); + else + res->mtx.ReadLock(); + return res; + } + } + } + { + Lock l(&p->mtx); + SyncVar *res = p->val; + for (; res; res = res->next) { + if (res->addr == addr) + break; + } + if (res == 0) { + StatInc(thr, StatSyncCreated); + void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); + res = new(mem) SyncVar(addr); + res->creation_stack.ObtainCurrent(thr, pc); + res->next = p->val; + p->val = res; + } + if (write_lock) + res->mtx.Lock(); + else + res->mtx.ReadLock(); + return res; + } +} + +SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) { + Part *p = &tab_[PartIdx(addr)]; + SyncVar *res = 0; + { + Lock l(&p->mtx); + SyncVar **prev = &p->val; + res = *prev; + while (res) { + if (res->addr == addr) { + *prev = res->next; + break; + } + prev = &res->next; + res = *prev; + } + } + if (res) { + StatInc(thr, StatSyncDestroyed); + res->mtx.Lock(); + res->mtx.Unlock(); + } + return res; +} + +int SyncTab::PartIdx(uptr addr) { + return (addr >> 3) % kPartCount; +} + +StackTrace::StackTrace() + : n_() + , s_() { +} + +StackTrace::~StackTrace() { + Reset(); +} + +void StackTrace::Reset() { + if (s_) { + CHECK_NE(n_, 0); + internal_free(s_); + s_ = 0; + n_ = 0; + } +} + +void StackTrace::Init(const uptr *pcs, uptr cnt) { + Reset(); + if (cnt == 0) + return; + n_ = cnt; + s_ = (uptr*)internal_alloc(MBlockStackTrace, cnt * sizeof(s_[0])); + internal_memcpy(s_, pcs, cnt * sizeof(s_[0])); +} + +void StackTrace::ObtainCurrent(ThreadState *thr, uptr toppc) { + Reset(); + n_ = thr->shadow_stack_pos - &thr->shadow_stack[0]; + if (n_ + !!toppc == 0) + return; + s_ = (uptr*)internal_alloc(MBlockStackTrace, (n_ + !!toppc) * sizeof(s_[0])); + for (uptr i = 0; i < n_; i++) + s_[i] = thr->shadow_stack[i]; + if (toppc) { + s_[n_] = toppc; + n_++; + } +} + +void StackTrace::CopyFrom(const StackTrace& other) { + Reset(); + Init(other.Begin(), other.Size()); +} + +bool StackTrace::IsEmpty() const { + return n_ == 0; +} + +uptr StackTrace::Size() const { + return n_; +} + +uptr StackTrace::Get(uptr i) const { + CHECK_LT(i, n_); + return s_[i]; +} + +const uptr *StackTrace::Begin() const { + return s_; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_sync.h b/lib/tsan/rtl/tsan_sync.h new file mode 100644 index 000000000..45251f02e --- /dev/null +++ b/lib/tsan/rtl/tsan_sync.h @@ -0,0 +1,97 @@ +//===-- tsan_sync.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SYNC_H +#define TSAN_SYNC_H + +#include "tsan_atomic.h" +#include "tsan_clock.h" +#include "tsan_defs.h" +#include "tsan_mutex.h" + +namespace __tsan { + +class SlabCache; + +class StackTrace { + public: + StackTrace(); + ~StackTrace(); + void Reset(); + + void Init(const uptr *pcs, uptr cnt); + void ObtainCurrent(ThreadState *thr, uptr toppc); + bool IsEmpty() const; + uptr Size() const; + uptr Get(uptr i) const; + const uptr *Begin() const; + void CopyFrom(const StackTrace& other); + + private: + uptr n_; + uptr *s_; + + StackTrace(const StackTrace&); + void operator = (const StackTrace&); +}; + +struct SyncVar { + explicit SyncVar(uptr addr); + + static const int kInvalidTid = -1; + + Mutex mtx; + const uptr addr; + SyncClock clock; + StackTrace creation_stack; + SyncClock read_clock; // Used for rw mutexes only. + int owner_tid; // Set only by exclusive owners. + int recursion; + bool is_rw; + bool is_recursive; + bool is_broken; + SyncVar *next; // In SyncTab hashtable. +}; + +class SyncTab { + public: + SyncTab(); + ~SyncTab(); + + // If the SyncVar does not exist yet, it is created. + SyncVar* GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock); + + // If the SyncVar does not exist, returns 0. + SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr); + + private: + struct Part { + Mutex mtx; + SyncVar *val; + char pad[kCacheLineSize - sizeof(Mutex) - sizeof(SyncVar*)]; // NOLINT + Part(); + }; + + // FIXME: Implement something more sane. + static const int kPartCount = 1009; + Part tab_[kPartCount]; + + int PartIdx(uptr addr); + + SyncTab(const SyncTab&); // Not implemented. + void operator = (const SyncTab&); // Not implemented. +}; + +} // namespace __tsan + +#endif // TSAN_SYNC_H diff --git a/lib/tsan/rtl/tsan_trace.h b/lib/tsan/rtl/tsan_trace.h new file mode 100644 index 000000000..4a1930d20 --- /dev/null +++ b/lib/tsan/rtl/tsan_trace.h @@ -0,0 +1,59 @@ +//===-- tsan_trace.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_TRACE_H +#define TSAN_TRACE_H + +#include "tsan_defs.h" +#include "tsan_mutex.h" +#include "tsan_sync.h" + +namespace __tsan { + +const int kTraceParts = 8; +const int kTraceSize = 1024*1024; +const int kTracePartSize = kTraceSize / kTraceParts; + +// Must fit into 3 bits. +enum EventType { + EventTypeMop, + EventTypeFuncEnter, + EventTypeFuncExit, + EventTypeLock, + EventTypeUnlock, + EventTypeRLock, + EventTypeRUnlock, +}; + +// Represents a thread event (from most significant bit): +// u64 typ : 3; // EventType. +// u64 addr : 61; // Associated pc. +typedef u64 Event; + +struct TraceHeader { + StackTrace stack0; // Start stack for the trace. + u64 epoch0; // Start epoch for the trace. +}; + +struct Trace { + Event events[kTraceSize]; + TraceHeader headers[kTraceParts]; + Mutex mtx; + + Trace() + : mtx(MutexTypeTrace, StatMtxTrace) { + } +}; + +} // namespace __tsan + +#endif // TSAN_TRACE_H diff --git a/lib/tsan/rtl/tsan_update_shadow_word_inl.h b/lib/tsan/rtl/tsan_update_shadow_word_inl.h new file mode 100644 index 000000000..c7864ce00 --- /dev/null +++ b/lib/tsan/rtl/tsan_update_shadow_word_inl.h @@ -0,0 +1,79 @@ +//===-- tsan_update_shadow_word_inl.h ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Body of the hottest inner loop. +// If we wrap this body into a function, compilers (both gcc and clang) +// produce sligtly less efficient code. +//===----------------------------------------------------------------------===// +do { + StatInc(thr, StatShadowProcessed); + const unsigned kAccessSize = 1 << kAccessSizeLog; + unsigned off = cur.ComputeSearchOffset(); + u64 *sp = &shadow_mem[(idx + off) % kShadowCnt]; + old = LoadShadow(sp); + if (old.IsZero()) { + StatInc(thr, StatShadowZero); + if (store_word) + StoreIfNotYetStored(sp, &store_word); + // The above StoreIfNotYetStored could be done unconditionally + // and it even shows 4% gain on synthetic benchmarks (r4307). + break; + } + // is the memory access equal to the previous? + if (Shadow::Addr0AndSizeAreEqual(cur, old)) { + StatInc(thr, StatShadowSameSize); + // same thread? + if (Shadow::TidsAreEqual(old, cur)) { + StatInc(thr, StatShadowSameThread); + if (OldIsInSameSynchEpoch(old, thr)) { + if (OldIsRWStronger(old, kAccessIsWrite)) { + // found a slot that holds effectively the same info + // (that is, same tid, same sync epoch and same size) + StatInc(thr, StatMopSame); + return; + } + StoreIfNotYetStored(sp, &store_word); + break; + } + if (OldIsRWWeaker(old, kAccessIsWrite)) + StoreIfNotYetStored(sp, &store_word); + break; + } + StatInc(thr, StatShadowAnotherThread); + if (HappensBefore(old, thr)) { + StoreIfNotYetStored(sp, &store_word); + break; + } + if (BothReads(old, kAccessIsWrite)) + break; + goto RACE; + } + + // Do the memory access intersect? + if (Shadow::TwoRangesIntersect(old, cur, kAccessSize)) { + StatInc(thr, StatShadowIntersect); + if (Shadow::TidsAreEqual(old, cur)) { + StatInc(thr, StatShadowSameThread); + break; + } + StatInc(thr, StatShadowAnotherThread); + if (HappensBefore(old, thr)) + break; + + if (BothReads(old, kAccessIsWrite)) + break; + + goto RACE; + } + // The accesses do not intersect. + StatInc(thr, StatShadowNotIntersect); + break; +} while (0); diff --git a/lib/tsan/rtl/tsan_vector.h b/lib/tsan/rtl/tsan_vector.h new file mode 100644 index 000000000..d41063df3 --- /dev/null +++ b/lib/tsan/rtl/tsan_vector.h @@ -0,0 +1,110 @@ +//===-- tsan_vector.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +// Low-fat STL-like vector container. + +#ifndef TSAN_VECTOR_H +#define TSAN_VECTOR_H + +#include "tsan_defs.h" +#include "tsan_mman.h" + +namespace __tsan { + +template<typename T> +class Vector { + public: + explicit Vector(MBlockType typ) + : typ_(typ) + , begin_() + , end_() + , last_() { + } + + ~Vector() { + if (begin_) + internal_free(begin_); + } + + void Reset() { + if (begin_) + internal_free(begin_); + begin_ = 0; + end_ = 0; + last_ = 0; + } + + uptr Size() const { + return end_ - begin_; + } + + T &operator[](uptr i) { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + const T &operator[](uptr i) const { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + T *PushBack(T v = T()) { + EnsureSize(Size() + 1); + end_[-1] = v; + return &end_[-1]; + } + + void Resize(uptr size) { + uptr old_size = Size(); + EnsureSize(size); + if (old_size < size) { + for (uptr i = old_size; i < size; i++) + begin_[i] = T(); + } + } + + private: + const MBlockType typ_; + T *begin_; + T *end_; + T *last_; + + void EnsureSize(uptr size) { + if (size <= Size()) + return; + if (size <= (uptr)(last_ - begin_)) { + end_ = begin_ + size; + return; + } + uptr cap0 = last_ - begin_; + uptr cap = 2 * cap0; + if (cap == 0) + cap = 16; + if (cap < size) + cap = size; + T *p = (T*)internal_alloc(typ_, cap * sizeof(T)); + if (cap0) { + internal_memcpy(p, begin_, cap0 * sizeof(T)); + internal_free(begin_); + } + begin_ = p; + end_ = begin_ + size; + last_ = begin_ + cap; + } + + Vector(const Vector&); + void operator=(const Vector&); +}; +} + +#endif // #ifndef TSAN_VECTOR_H |