diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp @@ -14,6 +14,7 @@ #include "sanitizer_common/sanitizer_platform.h" #if SANITIZER_MAC +#include "sanitizer_common/sanitizer_addrhashmap.h" #include "sanitizer_common/sanitizer_atomic.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_libc.h" @@ -45,70 +46,65 @@ namespace __tsan { #if !SANITIZER_GO -static void *SignalSafeGetOrAllocate(uptr *dst, uptr size) { - atomic_uintptr_t *a = (atomic_uintptr_t *)dst; - void *val = (void *)atomic_load_relaxed(a); - atomic_signal_fence(memory_order_acquire); // Turns the previous load into - // acquire wrt signals. - if (UNLIKELY(val == nullptr)) { - val = (void *)internal_mmap(nullptr, size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); - CHECK(val); - void *cmp = nullptr; - if (!atomic_compare_exchange_strong(a, (uintptr_t *)&cmp, (uintptr_t)val, - memory_order_acq_rel)) { - internal_munmap(val, size); - val = cmp; - } - } - return val; -} - -// On OS X, accessing TLVs via __thread or manually by using pthread_key_* is -// problematic, because there are several places where interceptors are called -// when TLVs are not accessible (early process startup, thread cleanup, ...). -// The following provides a "poor man's TLV" implementation, where we use the -// shadow memory of the pointer returned by pthread_self() to store a pointer to -// the ThreadState object. The main thread's ThreadState is stored separately -// in a static variable, because we need to access it even before the -// shadow memory is set up. -static uptr main_thread_identity = 0; -ALIGNED(64) static char main_thread_state[sizeof(ThreadState)]; -static ThreadState *main_thread_state_loc = (ThreadState *)main_thread_state; - -// We cannot use pthread_self() before libpthread has been initialized. Our -// current heuristic for guarding this is checking `main_thread_identity` which -// is only assigned in `__tsan::InitializePlatform`. -static ThreadState **cur_thread_location() { - if (main_thread_identity == 0) - return &main_thread_state_loc; - uptr thread_identity = (uptr)pthread_self(); - if (thread_identity == main_thread_identity) - return &main_thread_state_loc; - return (ThreadState **)MemToShadow(thread_identity); +// We would like to use TLS to store the ThreadState object (or at least a +// reference ot it), but on Darwin accessing TLS via __thread or manually by +// using pthread_key_* is problematic, because there are several places where +// interceptors are called when TLS is not accessible (early process startup, +// thread cleanup, ...). +// Instead, we use a global, thread-safe hash map to store a pointer to our +// ThreadState objects and use mmap() to allocate the backing memory. The main +// thread's ThreadState is stored separately in a static variable, because we +// need to access it even before we can allocate and initialize the hash map. + +constexpr uptr kNumThreads = 67; // prime +using ThreadStateMap = AddrHashMap; + +static ThreadStateMap* thread_state_map; +static char main_thread_state[sizeof(ThreadState)] ALIGNED(64); + +static uptr thread_identity() { return (uptr)pthread_self(); } + +static void InitializeThreadStateMap() { + CHECK_EQ(thread_state_map, nullptr); + thread_state_map = new ThreadStateMap(); // never freed + + uptr main_thread = thread_identity(); + ThreadStateMap::Handle h(thread_state_map, main_thread); + CHECK(h.created()); + *h = (ThreadState *)main_thread_state; } ThreadState *cur_thread() { - return (ThreadState *)SignalSafeGetOrAllocate( - (uptr *)cur_thread_location(), sizeof(ThreadState)); + if (UNLIKELY(thread_state_map == nullptr)) { + return (ThreadState *)main_thread_state; + } + + // Our first interceptors get called before libpthread has been fully + // initialized and calling pthread_self() would crash. These cases are also + // covered by the above condition, i.e., we only reach this line once TSan + // (and therefore libpthread) have been initialized. + ThreadStateMap::Handle h(thread_state_map, thread_identity()); + if (h.created()) { + *h = (ThreadState *)MmapOrDie(sizeof(ThreadState), "ThreadState"); + } + return *h; } void set_cur_thread(ThreadState *thr) { - *cur_thread_location() = thr; + ThreadStateMap::Handle h(thread_state_map, thread_identity()); + CHECK(h.exists()); + *h = thr; } -// TODO(kuba.brecka): This is not async-signal-safe. In particular, we call -// munmap first and then clear `fake_tls`; if we receive a signal in between, -// handler will try to access the unmapped ThreadState. void cur_thread_finalize() { - ThreadState **thr_state_loc = cur_thread_location(); - if (thr_state_loc == &main_thread_state_loc) { + ThreadStateMap::Handle h(thread_state_map, thread_identity(), /*remove=*/true); + CHECK(h.exists()); + if (UNLIKELY(*h == (ThreadState *)main_thread_state)) { // Calling dispatch_main() or xpc_main() actually invokes pthread_exit to // exit the main thread. Let's keep the main thread's ThreadState. return; } - internal_munmap(*thr_state_loc, sizeof(ThreadState)); - *thr_state_loc = nullptr; + UnmapOrDie(*h, sizeof(ThreadState)); } #endif @@ -225,6 +221,20 @@ if (thr->tctx) { DestroyThreadState(); } + // FIXME: We destroyed the ThreadState object above which includes + // unmapping the backing memory in cur_thread_finalize(). However, other + // intercepted APIs can still get called on the terminating thread. One + // example is from an "outer" THREAD_TERMINATE introspection hook (e.g., + // libBacktraceRecording for Xcode "Queue Debugging" feature). When this + // happens we re-allocate more backing storage in cur_thread() and, in + // most cases, leak the allocated memory. Potential solutions include: + // * Call ThreadFinish() here, but delay releasing the memory for the + // ThreadState object in THREAD_DESTROY event (which is guaranteed to + // be delivered on the parent thread after the thread terminated). + // This requires splitting up the operations in DestroyThreadState() + // between THREAD_TERMINATE and THREAD_DESTROY. + // * Use a dummy "dead thread" ThreadState object (like we do on the + // Linux side) to avoid re-allocation. } } @@ -251,8 +261,7 @@ #if !SANITIZER_GO CheckAndProtect(); - CHECK_EQ(main_thread_identity, 0); - main_thread_identity = (uptr)pthread_self(); + InitializeThreadStateMap(); prev_pthread_introspection_hook = pthread_introspection_hook_install(&my_pthread_introspection_hook); @@ -282,24 +291,8 @@ #if !SANITIZER_GO void ImitateTlsWrite(ThreadState *thr, uptr tls_addr, uptr tls_size) { - // The pointer to the ThreadState object is stored in the shadow memory - // of the tls. - uptr tls_end = tls_addr + tls_size; - uptr thread_identity = (uptr)pthread_self(); - if (thread_identity == main_thread_identity) { - MemoryRangeImitateWrite(thr, /*pc=*/2, tls_addr, tls_size); - } else { - uptr thr_state_start = thread_identity; - uptr thr_state_end = thr_state_start + sizeof(uptr); - CHECK_GE(thr_state_start, tls_addr); - CHECK_LE(thr_state_start, tls_addr + tls_size); - CHECK_GE(thr_state_end, tls_addr); - CHECK_LE(thr_state_end, tls_addr + tls_size); - MemoryRangeImitateWrite(thr, /*pc=*/2, tls_addr, - thr_state_start - tls_addr); - MemoryRangeImitateWrite(thr, /*pc=*/2, thr_state_end, - tls_end - thr_state_end); - } + // Unlike Linux, the ThreadState object is not stored in TLS. + MemoryRangeImitateWrite(thr, /*pc=*/2, tls_addr, tls_size); } #endif