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 @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -190,44 +191,101 @@ #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( - 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(); - } - } +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); +} + +// rdar://58497482: In the future, the OS will provide this function (or +// similar). +static int pthread_setspecific_introspection_np(pthread_t thread, + pthread_key_t key, + void *value) { + // These magic values are not part of the ABI. Do not depend on them! +#if __LP64__ + const unsigned tsd_offset = 224; +#else + const unsigned tsd_offset = 176; +#endif + uintptr_t *tsd_data = (uintptr_t *)((char *)thread + tsd_offset); + tsd_data[key] = (uintptr_t)value; + return 0; +} + +// 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(CreateEvent::Absent == (CreateEvent)0, "Absent must be zero"); +static pthread_key_t create_type_key; + +static void handle_thread_create(pthread_t thread) { + CreateEvent create = + (thread == pthread_self()) ? CreateEvent::Self : CreateEvent::Parent; + int res = pthread_setspecific_introspection_np(thread, create_type_key, + (void *)(uptr)create); + CHECK_EQ(res, 0); +} + +static void handle_thread_start(pthread_t thread) { + CHECK_EQ(thread, pthread_self()); + CreateEvent create = (CreateEvent)(uptr)pthread_getspecific(create_type_key); + if (create == CreateEvent::Parent) + return; + + CHECK(create == CreateEvent::Absent || create == CreateEvent::Self); + ThreadType type = (create == CreateEvent::Absent) ? ThreadType::Regular + : ThreadType::Worker; + InitializeThread(thread, type); +} + +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; + default: + UNREACHABLE("unknown event"); + break; } +} - if (prev_pthread_introspection_hook != nullptr) +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 @@ -253,8 +311,11 @@ CHECK_EQ(main_thread_identity, 0); main_thread_identity = (uptr)pthread_self(); + int res = pthread_key_create(&create_type_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 (GetMacosVersion() >= MACOS_VERSION_MOJAVE) { 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 @@ -494,14 +494,17 @@ void ForkBefore(ThreadState *thr, uptr pc) { ctx->thread_registry->Lock(); ctx->report_mtx.Lock(); + ctx->currently_forking = true; } void ForkParentAfter(ThreadState *thr, uptr pc) { + ctx->currently_forking = false; ctx->report_mtx.Unlock(); ctx->thread_registry->Unlock(); } void ForkChildAfter(ThreadState *thr, uptr pc) { + 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,98 @@ +// 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 + } +} + +int main(int argc, const char *argv[]) { + // 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)