Index: compiler-rt/lib/sanitizer_common/CMakeLists.txt =================================================================== --- compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -35,6 +35,7 @@ sanitizer_solaris.cpp sanitizer_stoptheworld_fuchsia.cpp sanitizer_stoptheworld_mac.cpp + sanitizer_stoptheworld_win.cpp sanitizer_suppressions.cpp sanitizer_tls_get_addr.cpp sanitizer_thread_registry.cpp Index: compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_win.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/sanitizer_common/sanitizer_stoptheworld_win.cpp @@ -0,0 +1,151 @@ +//===-- sanitizer_stoptheworld_win.cpp ------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// See sanitizer_stoptheworld.h for details. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common.h" +#include "sanitizer_internal_defs.h" +#include "sanitizer_platform.h" + +#if SANITIZER_WINDOWS + +# define WIN32_LEAN_AND_MEAN +# include + +// windows.h needs to be included before tlhelp32.h +# include + +# include + +# include "sanitizer_stoptheworld.h" + +namespace __sanitizer { + +struct SuspendedThreadsListWindows final : public SuspendedThreadsList { + InternalMmapVector threadHandles; + InternalMmapVector threadIds; + + SuspendedThreadsListWindows() { + threadIds.reserve(1024); + threadHandles.reserve(1024); + } + + PtraceRegistersStatus GetRegistersAndSP(uptr index, + InternalMmapVector *buffer, + uptr *sp) const override; + + tid_t GetThreadID(uptr index) const override; + uptr ThreadCount() const override; +}; + +PtraceRegistersStatus SuspendedThreadsListWindows::GetRegistersAndSP( + uptr index, InternalMmapVector *buffer, uptr *sp) const { + CHECK_LT(index, threadHandles.size()); + + CONTEXT thread_context; + thread_context.ContextFlags = CONTEXT_ALL; + CHECK(GetThreadContext(threadHandles[index], &thread_context)); + + buffer->resize(RoundUpTo(sizeof(thread_context), sizeof(uptr)) / + sizeof(uptr)); + internal_memcpy(buffer->data(), &thread_context, sizeof(thread_context)); + + *sp = thread_context.Rsp; + + return REGISTERS_AVAILABLE; +} + +tid_t SuspendedThreadsListWindows::GetThreadID(uptr index) const { + CHECK_LT(index, threadIds.size()); + return threadIds[index]; +} + +uptr SuspendedThreadsListWindows::ThreadCount() const { + return threadIds.size(); +} + +struct RunThreadArgs { + StopTheWorldCallback callback; + void *argument; +}; + +DWORD WINAPI RunThread(void *argument) { + RunThreadArgs *run_args = (RunThreadArgs *)argument; + + const DWORD this_thread = GetCurrentThreadId(); + const DWORD this_process = GetCurrentProcessId(); + + SuspendedThreadsListWindows suspended_threads_list; + bool new_thread_found; + + do { + // Take a snapshot of all threads of this Process + const HANDLE threads = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + CHECK(threads != INVALID_HANDLE_VALUE); + + THREADENTRY32 thread_entry; + thread_entry.dwSize = sizeof(thread_entry); + new_thread_found = false; + + if (!Thread32First(threads, &thread_entry)) + break; + + do { + if (thread_entry.th32ThreadID == this_thread || + thread_entry.th32OwnerProcessID != this_process) + continue; + + const bool contains_thread = + std::find(suspended_threads_list.threadIds.begin(), + suspended_threads_list.threadIds.end(), + thread_entry.th32ThreadID) != + suspended_threads_list.threadIds.end(); + + if (contains_thread) + continue; + + const HANDLE thread = + OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry.th32ThreadID); + CHECK(thread); + suspended_threads_list.threadIds.push_back(thread_entry.th32ThreadID); + suspended_threads_list.threadHandles.push_back(thread); + SuspendThread(thread); + new_thread_found = true; + } while (Thread32Next(threads, &thread_entry)); + + CloseHandle(threads); + } while (new_thread_found); + + run_args->callback(suspended_threads_list, run_args->argument); + + for (const auto suspended_thread_handle : + suspended_threads_list.threadHandles) { + ResumeThread(suspended_thread_handle); + CloseHandle(suspended_thread_handle); + } + + return 0; +} + +void StopTheWorld(StopTheWorldCallback callback, void *argument) { + struct RunThreadArgs arg = {callback, argument}; + DWORD trace_thread_id; + + auto trace_thread = + CreateThread(nullptr, 0, RunThread, &arg, 0, &trace_thread_id); + CHECK(trace_thread); + + WaitForSingleObject(trace_thread, INFINITE); + CloseHandle(trace_thread); +} + +} // namespace __sanitizer + +#endif // SANITIZER_WINDOWS \ No newline at end of file Index: compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cpp =================================================================== --- compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cpp +++ compiler-rt/lib/sanitizer_common/tests/sanitizer_stoptheworld_test.cpp @@ -11,41 +11,38 @@ //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_platform.h" -#if SANITIZER_LINUX && defined(__x86_64__) +#if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__) -#include "sanitizer_common/sanitizer_stoptheworld.h" -#include "gtest/gtest.h" +# include +# include -#include "sanitizer_common/sanitizer_libc.h" -#include "sanitizer_common/sanitizer_common.h" - -#include -#include +# include "gtest/gtest.h" +# include "sanitizer_common/sanitizer_common.h" +# include "sanitizer_common/sanitizer_libc.h" +# include "sanitizer_common/sanitizer_stoptheworld.h" namespace __sanitizer { -static pthread_mutex_t incrementer_thread_exit_mutex; +static std::mutex incrementer_thread_exit_mutex; struct CallbackArgument { volatile int counter; volatile bool threads_stopped; volatile bool callback_executed; CallbackArgument() - : counter(0), - threads_stopped(false), - callback_executed(false) {} + : counter(0), threads_stopped(false), callback_executed(false) {} }; -void *IncrementerThread(void *argument) { - CallbackArgument *callback_argument = (CallbackArgument *)argument; +void IncrementerThread(CallbackArgument &callback_argument) { while (true) { - __sync_fetch_and_add(&callback_argument->counter, 1); - if (pthread_mutex_trylock(&incrementer_thread_exit_mutex) == 0) { - pthread_mutex_unlock(&incrementer_thread_exit_mutex); - return NULL; - } else { - sched_yield(); + __sync_fetch_and_add(&callback_argument.counter, 1); + + if (incrementer_thread_exit_mutex.try_lock()) { + incrementer_thread_exit_mutex.unlock(); + return; } + + std::this_thread::yield(); } } @@ -57,9 +54,9 @@ callback_argument->callback_executed = true; int counter_at_init = __sync_fetch_and_add(&callback_argument->counter, 0); for (uptr i = 0; i < 1000; i++) { - sched_yield(); + std::this_thread::yield(); if (__sync_fetch_and_add(&callback_argument->counter, 0) != - counter_at_init) { + counter_at_init) { callback_argument->threads_stopped = false; return; } @@ -68,91 +65,84 @@ } TEST(StopTheWorld, SuspendThreadsSimple) { - pthread_mutex_init(&incrementer_thread_exit_mutex, NULL); CallbackArgument argument; - pthread_t thread_id; - int pthread_create_result; - pthread_mutex_lock(&incrementer_thread_exit_mutex); - pthread_create_result = pthread_create(&thread_id, NULL, IncrementerThread, - &argument); - ASSERT_EQ(0, pthread_create_result); + std::thread thread; + incrementer_thread_exit_mutex.lock(); + ASSERT_NO_THROW(thread = std::thread(IncrementerThread, std::ref(argument))); StopTheWorld(&Callback, &argument); - pthread_mutex_unlock(&incrementer_thread_exit_mutex); + incrementer_thread_exit_mutex.unlock(); EXPECT_TRUE(argument.callback_executed); EXPECT_TRUE(argument.threads_stopped); // argument is on stack, so we have to wait for the incrementer thread to // terminate before we can return from this function. - ASSERT_EQ(0, pthread_join(thread_id, NULL)); - pthread_mutex_destroy(&incrementer_thread_exit_mutex); + ASSERT_NO_THROW(thread.join()); } // A more comprehensive test where we spawn a bunch of threads while executing // StopTheWorld in parallel. static const uptr kThreadCount = 50; -static const uptr kStopWorldAfter = 10; // let this many threads spawn first +static const uptr kStopWorldAfter = 10; // let this many threads spawn first -static pthread_mutex_t advanced_incrementer_thread_exit_mutex; +static std::mutex advanced_incrementer_thread_exit_mutex; struct AdvancedCallbackArgument { volatile uptr thread_index; volatile int counters[kThreadCount]; - pthread_t thread_ids[kThreadCount]; + std::thread threads[kThreadCount]; volatile bool threads_stopped; volatile bool callback_executed; volatile bool fatal_error; AdvancedCallbackArgument() - : thread_index(0), - threads_stopped(false), - callback_executed(false), - fatal_error(false) {} + : thread_index(0), + threads_stopped(false), + callback_executed(false), + fatal_error(false) {} }; -void *AdvancedIncrementerThread(void *argument) { - AdvancedCallbackArgument *callback_argument = - (AdvancedCallbackArgument *)argument; - uptr this_thread_index = __sync_fetch_and_add( - &callback_argument->thread_index, 1); +void AdvancedIncrementerThread(AdvancedCallbackArgument &callback_argument) { + uptr this_thread_index = + __sync_fetch_and_add(&callback_argument.thread_index, 1); // Spawn the next thread. int pthread_create_result; if (this_thread_index + 1 < kThreadCount) { - pthread_create_result = - pthread_create(&callback_argument->thread_ids[this_thread_index + 1], - NULL, AdvancedIncrementerThread, argument); - // Cannot use ASSERT_EQ in non-void-returning functions. If there's a - // problem, defer failing to the main thread. - if (pthread_create_result != 0) { - callback_argument->fatal_error = true; - __sync_fetch_and_add(&callback_argument->thread_index, - kThreadCount - callback_argument->thread_index); + try { + callback_argument.threads[this_thread_index + 1] = + std::thread(AdvancedIncrementerThread, std::ref(callback_argument)); + } catch (...) { + // Cannot use ASSERT_EQ in non-void-returning functions. If there's a + // problem, defer failing to the main thread. + callback_argument.fatal_error = true; + __sync_fetch_and_add(&callback_argument.thread_index, + kThreadCount - callback_argument.thread_index); } } // Do the actual work. while (true) { - __sync_fetch_and_add(&callback_argument->counters[this_thread_index], 1); - if (pthread_mutex_trylock(&advanced_incrementer_thread_exit_mutex) == 0) { - pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); - return NULL; - } else { - sched_yield(); + __sync_fetch_and_add(&callback_argument.counters[this_thread_index], 1); + if (advanced_incrementer_thread_exit_mutex.try_lock()) { + advanced_incrementer_thread_exit_mutex.unlock(); + return; } + + std::this_thread::yield(); } } void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list, - void *argument) { + void *argument) { AdvancedCallbackArgument *callback_argument = (AdvancedCallbackArgument *)argument; callback_argument->callback_executed = true; int counters_at_init[kThreadCount]; for (uptr j = 0; j < kThreadCount; j++) - counters_at_init[j] = __sync_fetch_and_add(&callback_argument->counters[j], - 0); + counters_at_init[j] = + __sync_fetch_and_add(&callback_argument->counters[j], 0); for (uptr i = 0; i < 10; i++) { - sched_yield(); + std::this_thread::yield(); for (uptr j = 0; j < kThreadCount; j++) if (__sync_fetch_and_add(&callback_argument->counters[j], 0) != - counters_at_init[j]) { + counters_at_init[j]) { callback_argument->threads_stopped = false; return; } @@ -161,36 +151,30 @@ } TEST(StopTheWorld, SuspendThreadsAdvanced) { - pthread_mutex_init(&advanced_incrementer_thread_exit_mutex, NULL); AdvancedCallbackArgument argument; - pthread_mutex_lock(&advanced_incrementer_thread_exit_mutex); - int pthread_create_result; - pthread_create_result = pthread_create(&argument.thread_ids[0], NULL, - AdvancedIncrementerThread, - &argument); - ASSERT_EQ(0, pthread_create_result); + advanced_incrementer_thread_exit_mutex.lock(); + argument.threads[0] = + std::thread(AdvancedIncrementerThread, std::ref(argument)); // Wait for several threads to spawn before proceeding. while (__sync_fetch_and_add(&argument.thread_index, 0) < kStopWorldAfter) - sched_yield(); + std::this_thread::yield(); StopTheWorld(&AdvancedCallback, &argument); EXPECT_TRUE(argument.callback_executed); EXPECT_TRUE(argument.threads_stopped); // Wait for all threads to spawn before we start terminating them. while (__sync_fetch_and_add(&argument.thread_index, 0) < kThreadCount) - sched_yield(); - ASSERT_FALSE(argument.fatal_error); // a pthread_create has failed + std::this_thread::yield(); + ASSERT_FALSE(argument.fatal_error); // a thread could not be started // Signal the threads to terminate. - pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex); - for (uptr i = 0; i < kThreadCount; i++) - ASSERT_EQ(0, pthread_join(argument.thread_ids[i], NULL)); - pthread_mutex_destroy(&advanced_incrementer_thread_exit_mutex); + advanced_incrementer_thread_exit_mutex.unlock(); + for (uptr i = 0; i < kThreadCount; i++) argument.threads[i].join(); } static void SegvCallback(const SuspendedThreadsList &suspended_threads_list, void *argument) { - *(volatile int*)0x1234 = 0; + *(volatile int *)0x1234 = 0; } TEST(StopTheWorld, SegvInCallback) {