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 @@ -191,44 +191,108 @@ #if !SANITIZER_GO void InitializeShadowMemoryPlatform() { } -// On OS X, GCD worker threads are created without a call to pthread_create. We -// need to properly register these threads with ThreadCreate and ThreadStart. -// These threads don't have a parent thread, as they are created "spuriously". -// We're using a libpthread API that notifies us about a newly created thread. -// 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. -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( +static void InitializeThread(pthread_t thread, ThreadType type) { + ThreadState *thr = cur_thread(); + Processor *proc = ProcCreate(); + ProcWire(proc, thr); + ThreadState *parent = nullptr; // No parent + int tid = ThreadCreate(parent, /*pc=*/0x0, (uptr)thread, /*detached=*/true); + CHECK_NE(tid, 0); + ThreadStart(thr, tid, GetTid(), type); +} + +// #include +extern "C" { +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); +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. - int tid = ThreadCreate(parent_thread_state, 0, (uptr)thread, true); - CHECK_NE(tid, 0); - ThreadStart(thr, tid, GetTid(), ThreadType::Worker); - } - } else if (event == PTHREAD_INTROSPECTION_THREAD_TERMINATE) { - if (thread == pthread_self()) { - ThreadState *thr = cur_thread(); - if (thr->tctx) { - DestroyThreadState(); - } - } +// macOS 11 +__attribute((weak_import)) int pthread_introspection_setspecific_np( + pthread_t thread, pthread_key_t key, const void *value); +} // extern "C" + +// How was the THREAD_CREATE event delivered for this thread? +enum class CreateEvent : uptr { + Absent = 0, // Event was never delivered; indicates "special" thread. + Parent, // Delivery on parent; indicates regular thread. + Self // Delivery on the thread itself; indicates GCD worker. +}; +static_assert((unsigned)CreateEvent::Absent == 0, "Absent must be zero"); +static pthread_key_t create_event_key; + +static void handle_thread_create(pthread_t thread) { + if (!pthread_introspection_setspecific_np) { + if (thread == pthread_self()) + InitializeThread(thread, ThreadType::Worker); + return; // Legacy, GCD-only strategy } - if (prev_pthread_introspection_hook != nullptr) + auto createEvent = + (thread == pthread_self()) ? CreateEvent::Self : CreateEvent::Parent; + int res = pthread_introspection_setspecific_np(thread, create_event_key, + (void *)(uptr)createEvent); + CHECK_EQ(res, 0); +} + +static void handle_thread_start(pthread_t thread) { + CHECK_EQ(thread, pthread_self()); + if (!pthread_introspection_setspecific_np) + return; // Legacy, GCD-only strategy + + auto createEvent = (CreateEvent)(uptr)pthread_getspecific(create_event_key); + if (createEvent == CreateEvent::Parent) + return; // Handled by pthread_create() interceptor + + CHECK(createEvent == CreateEvent::Absent || createEvent == CreateEvent::Self); + auto threadType = (createEvent == CreateEvent::Absent) ? ThreadType::Regular + : ThreadType::Worker; + InitializeThread(thread, threadType); +} + +static void handle_thread_terminate(pthread_t thread) { + CHECK_EQ(thread, pthread_self()); + if (cur_thread()->tctx) + DestroyThreadState(); +} + +// We use the introspection API to handle threads that are not created via +// pthread_create(). We also destroy *all* threads here. +// * GCD workers do not have a parent thread. For them THREAD_CREATE is +// delivered on the worker itself (for regular threads this event is delivered +// on the parent). +// * Special threads for which we are unable to observe their creation, e.g., +// threads created from a Mach thread via pthread_create_from_mach_thread(). +// For these threads, THREAD_START is the first time we see them. +static void handle_pthread_event(unsigned event, pthread_t thread) { + switch (event) { + case PTHREAD_INTROSPECTION_THREAD_CREATE: + handle_thread_create(thread); + break; + case PTHREAD_INTROSPECTION_THREAD_START: + handle_thread_start(thread); + break; + case PTHREAD_INTROSPECTION_THREAD_TERMINATE: + handle_thread_terminate(thread); + break; + case PTHREAD_INTROSPECTION_THREAD_DESTROY: + break; + } +} + +static pthread_introspection_hook_t prev_pthread_introspection_hook; +static void pthread_introspection_hook(unsigned int event, pthread_t thread, + void *addr, size_t size) { + if (thread != ctx->background_thread && !ctx->currently_forking) + handle_pthread_event(event, thread); + + if (prev_pthread_introspection_hook) prev_pthread_introspection_hook(event, thread, addr, size); } #endif @@ -254,8 +318,11 @@ CHECK_EQ(main_thread_identity, 0); main_thread_identity = (uptr)pthread_self(); + int res = pthread_key_create(&create_event_key, /*destructor=*/nullptr); + CHECK_EQ(res, 0); + prev_pthread_introspection_hook = - pthread_introspection_hook_install(&my_pthread_introspection_hook); + pthread_introspection_hook_install(&pthread_introspection_hook); #endif if (GetMacosAlignedVersion() >= MacosVersion(10, 14)) { diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.h b/compiler-rt/lib/tsan/rtl/tsan_rtl.h --- a/compiler-rt/lib/tsan/rtl/tsan_rtl.h +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.h @@ -528,6 +528,7 @@ bool initialized; #if !SANITIZER_GO bool after_multithreaded_fork; + bool currently_forking; #endif MetaMap metamap; diff --git a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp --- a/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_rtl.cpp @@ -519,6 +519,7 @@ void ForkBefore(ThreadState *thr, uptr pc) { ctx->thread_registry->Lock(); ctx->report_mtx.Lock(); + ctx->currently_forking = true; // Ignore memory accesses in the pthread_atfork callbacks. // If any of them triggers a data race we will deadlock // on the report_mtx. @@ -530,12 +531,14 @@ void ForkParentAfter(ThreadState *thr, uptr pc) { ThreadIgnoreEnd(thr, pc); // Begin is in ForkBefore. + ctx->currently_forking = false; ctx->report_mtx.Unlock(); ctx->thread_registry->Unlock(); } void ForkChildAfter(ThreadState *thr, uptr pc) { ThreadIgnoreEnd(thr, pc); // Begin is in ForkBefore. + ctx->currently_forking = false; ctx->report_mtx.Unlock(); ctx->thread_registry->Unlock(); diff --git a/compiler-rt/test/tsan/Darwin/mach-thread.c b/compiler-rt/test/tsan/Darwin/mach-thread.c new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tsan/Darwin/mach-thread.c @@ -0,0 +1,107 @@ +// Ensure we do not crash for threads created from a mach thread. This test is +// adapted from `libpthread/tests/pthread_create_from_mach_thread.c`. +// +// We compile two parts: +// *) Code instrumented with TSan (tsan_instrumented) +// *) Setup code +// +// The setup code first creates a mach thread (thread_create_running) and from +// this mach thread we then create a pthread (pthread_create_from_mach_thread) +// which executes TSan-instrumented code. +// +// Note: we must use two separate object files to accomplish this, because TSan +// still instruments functions (i.e, __tsan_func_entry) that contain calls to +// other functions even if we use the no_sanitize("thread") attribute or +// sanitizer blacklists. In addition, it more closely resembles the SwiftUI +// preview container and application code scenario. + +// RUN: %clang_tsan %s -c -DINSTRUMENTED -o %t.instrumented +// RUN: %clang_tsan %s -c -fno-sanitize=thread -o %t.setup +// RUN: %clang_tsan %t.instrumented %t.setup -o %t +// RUN: %run %t 2>&1 | FileCheck %s --implicit-check-not='ThreadSanitizer' +// +// CHECK: Mach thread started. +// CHECK: Created pthread from mach thread. + +#include +#include +#include +#include +#include +#include + +#if defined(INSTRUMENTED) +void *tsan_instrumented(void *arg) { + printf("Created pthread from mach thread.\n"); + exit(0); +} +#else +void *tsan_instrumented(void *arg); + +static const uint32_t stack_size = 1024; +static uint64_t stack[stack_size] __attribute__((aligned(16))); + +// Ideally, we would want to printf+CHECK here, but we can't do anything useful +// from the context of the mach thread. It has no TSDs, and we can't malloc +// or syscall. So just call the SPI under test. +static void *mach_bootstrap(void *arg) { + pthread_t thread; + int res = + pthread_create_from_mach_thread(&thread, NULL, tsan_instrumented, NULL); + assert(res == 0); + + while (1) { + swtch_pri(0); // mach_yield + } +} + +// macOS 11 or newer +__attribute((weak_import)) int pthread_introspection_setspecific_np( + pthread_t thread, pthread_key_t key, const void *value); + +int main(int argc, const char *argv[]) { + if (!pthread_introspection_setspecific_np) { + printf("Running on old OS, skipping test!\n"); + printf("Mach thread started.\nCreated pthread from mach thread.\n"); + return 0; + } + + // Setup thread state + thread_state_flavor_t flavor; + mach_msg_type_number_t sate_count; + uintptr_t stack_top = (uintptr_t)&stack[stack_size]; + assert((stack_top & 0xF) == 0); // Ensure 16-byte alignment + +#if defined(__x86_64__) + flavor = x86_THREAD_STATE64; + sate_count = x86_THREAD_STATE64_COUNT; + x86_thread_state64_t state = { + .__rip = (uint64_t)&mach_bootstrap, + // Must be 16-byte-off-by-8 aligned + .__rsp = stack_top - 8, + }; +#elif defined(__arm64__) + flavor = ARM_THREAD_STATE64; + sate_count = ARM_THREAD_STATE64_COUNT; + arm_thread_state64_t state = { }; + arm_thread_state64_set_pc_fptr(state, &mach_bootstrap); + arm_thread_state64_set_sp(state, stack_top); +#else +# error Unsupported architecture +#endif + + task_t parent = mach_task_self(); + thread_state_t state_ptr = (thread_state_t)&state; + thread_act_t thread; + + kern_return_t ret = thread_create_running( + parent, flavor, state_ptr, sate_count, &thread); + assert(ret == KERN_SUCCESS); + + printf("Mach thread started. Waiting..\n"); + while (1) { + sched_yield(); + } + return 1; +} +#endif // defined(INSTRUMENTED)