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 @@ -45,23 +45,20 @@ namespace __tsan { #if !SANITIZER_GO -static void *SignalSafeGetOrAllocate(uptr *dst, uptr size) { +static void SignalSafeAllocate(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; - } + CHECK_EQ(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); } - return val; } // On OS X, accessing TLVs via __thread or manually by using pthread_key_* is @@ -76,37 +73,40 @@ ALIGNED(64) static char main_thread_state[sizeof(ThreadState)]; static ThreadState *main_thread_state_loc = (ThreadState *)main_thread_state; +static ThreadState **thread_location(uptr thread_identity) { + if (thread_identity == main_thread_identity) + return &main_thread_state_loc; + return (ThreadState **)MemToShadow(thread_identity); +} + // 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); + return thread_location((uptr)pthread_self()); } ThreadState *cur_thread() { - return (ThreadState *)SignalSafeGetOrAllocate( - (uptr *)cur_thread_location(), sizeof(ThreadState)); + return *cur_thread_location(); } void set_cur_thread(ThreadState *thr) { *cur_thread_location() = thr; } +// Do nothing here, we want to do this final step of cleanup in THREAD_DESTROY. +void cur_thread_finalize() {} + // 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) { - // Calling dispatch_main() or xpc_main() actually invokes pthread_exit to - // exit the main thread. Let's keep the main thread's ThreadState. - return; - } +void thread_finalize(uptr thread_identity) { + // Calling dispatch_main() or xpc_main() actually invokes pthread_exit to + // exit the main thread. Ensure we keep the main thread's ThreadState. + CHECK_NE(thread_identity, main_thread_identity); + ThreadState **thr_state_loc = thread_location(thread_identity); internal_munmap(*thr_state_loc, sizeof(ThreadState)); *thr_state_loc = nullptr; } @@ -198,33 +198,68 @@ // The `thread == pthread_self()` check indicates this is actually a worker // thread. If it's just a regular thread, this hook is called on the parent // thread. +enum { + PTHREAD_INTROSPECTION_THREAD_CREATE = 1, + PTHREAD_INTROSPECTION_THREAD_START, + PTHREAD_INTROSPECTION_THREAD_TERMINATE, + PTHREAD_INTROSPECTION_THREAD_DESTROY, +}; typedef void (*pthread_introspection_hook_t)(unsigned int event, pthread_t thread, void *addr, size_t size); extern "C" pthread_introspection_hook_t pthread_introspection_hook_install( pthread_introspection_hook_t hook); -static const uptr PTHREAD_INTROSPECTION_THREAD_CREATE = 1; -static const uptr PTHREAD_INTROSPECTION_THREAD_TERMINATE = 3; + static pthread_introspection_hook_t prev_pthread_introspection_hook; static void my_pthread_introspection_hook(unsigned int event, pthread_t thread, void *addr, size_t size) { - if (event == PTHREAD_INTROSPECTION_THREAD_CREATE) { - if (thread == pthread_self()) { - // The current thread is a newly created GCD worker thread. - ThreadState *thr = cur_thread(); - Processor *proc = ProcCreate(); - ProcWire(proc, thr); - ThreadState *parent_thread_state = nullptr; // No parent. - Tid tid = ThreadCreate(parent_thread_state, 0, (uptr)thread, true); - CHECK_NE(tid, kMainTid); - ThreadStart(thr, tid, GetTid(), ThreadType::Worker); + switch (event) { + case PTHREAD_INTROSPECTION_THREAD_CREATE: { + CHECK_NE(thread, main_thread_identity); + ThreadState **thr_loc = thread_location((uptr)thread); + DCHECK_EQ(*thr_loc, nullptr); + // Note: the above is a debug check (DCHECK) on purpose! + // Certain frameworks (e.g., IOSurface, IOKit) map and unmap VM ranges + // through direct interaction with the kernel. So unfortunately, there is + // no API that we can intercept to observe these operations and clear the + // associated shadow memory. When such a previously-used VM region is + // reused for a new thread, the shadow memory for the pthread_t struct may + // have leftover, non-zero shadow bytes. Previously, we erroneously + // interpreted these non-zero bytes as a pointer to the ThreadState + // metadata and crashed. Now we allocate this ThreadState metadata below + // so the current value does not matter. However, the above failure + // situation is so rare that we still want to check this assertion in + // debug builds and during testing to avoid hiding other failures. + SignalSafeAllocate((uptr *)thr_loc, sizeof(ThreadState)); + + bool gcd_worker = (thread == pthread_self()); + if (gcd_worker) { + ThreadState *thr = cur_thread(); + Processor *proc = ProcCreate(); + ProcWire(proc, thr); + ThreadState *parent_thread_state = nullptr; // No parent. + Tid tid = ThreadCreate(parent_thread_state, 0, (uptr)thread, true); + CHECK_NE(tid, kMainTid); + ThreadStart(thr, tid, GetTid(), ThreadType::Worker); + } + break; } - } else if (event == PTHREAD_INTROSPECTION_THREAD_TERMINATE) { - if (thread == pthread_self()) { + case PTHREAD_INTROSPECTION_THREAD_START: { + CHECK_EQ(thread, pthread_self()); + break; + } + case PTHREAD_INTROSPECTION_THREAD_TERMINATE: { + CHECK_EQ(thread, pthread_self()); ThreadState *thr = cur_thread(); if (thr->tctx) { DestroyThreadState(); } + break; + } + case PTHREAD_INTROSPECTION_THREAD_DESTROY: { + CHECK_NE(thread, main_thread_identity); + thread_finalize((uptr)thread); + break; } }