//===-- sanitizer_stoptheworld_test.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 // //===----------------------------------------------------------------------===// // // Tests for sanitizer_stoptheworld.h // //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_stoptheworld.h" #include "sanitizer_common/sanitizer_platform.h" #if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__) # include # include # include # include "gtest/gtest.h" # include "sanitizer_common/sanitizer_common.h" # include "sanitizer_common/sanitizer_libc.h" namespace __sanitizer { static std::mutex mutex; struct CallbackArgument { std::atomic_int counter = {}; std::atomic_bool threads_stopped = {}; std::atomic_bool callback_executed = {}; }; void IncrementerThread(CallbackArgument &callback_argument) { while (true) { callback_argument.counter++; if (mutex.try_lock()) { mutex.unlock(); return; } std::this_thread::yield(); } } // This callback checks that IncrementerThread is suspended at the time of its // execution. void Callback(const SuspendedThreadsList &suspended_threads_list, void *argument) { CallbackArgument *callback_argument = (CallbackArgument *)argument; callback_argument->callback_executed = true; int counter_at_init = callback_argument->counter; for (uptr i = 0; i < 1000; i++) { std::this_thread::yield(); if (callback_argument->counter != counter_at_init) { callback_argument->threads_stopped = false; return; } } callback_argument->threads_stopped = true; } TEST(StopTheWorld, SuspendThreadsSimple) { CallbackArgument argument; std::thread thread; { std::lock_guard lock(mutex); thread = std::thread(IncrementerThread, std::ref(argument)); StopTheWorld(&Callback, &argument); } 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_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 struct AdvancedCallbackArgument { std::atomic_uintptr_t thread_index = {}; std::atomic_int counters[kThreadCount] = {}; std::thread threads[kThreadCount]; std::atomic_bool threads_stopped = {}; std::atomic_bool callback_executed = {}; }; void AdvancedIncrementerThread(AdvancedCallbackArgument &callback_argument) { uptr this_thread_index = callback_argument.thread_index++; // Spawn the next thread. if (this_thread_index + 1 < kThreadCount) { callback_argument.threads[this_thread_index + 1] = std::thread(AdvancedIncrementerThread, std::ref(callback_argument)); } // Do the actual work. while (true) { callback_argument.counters[this_thread_index]++; if (mutex.try_lock()) { mutex.unlock(); return; } std::this_thread::yield(); } } void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list, 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] = callback_argument->counters[j]; for (uptr i = 0; i < 10; i++) { std::this_thread::yield(); for (uptr j = 0; j < kThreadCount; j++) if (callback_argument->counters[j] != counters_at_init[j]) { callback_argument->threads_stopped = false; return; } } callback_argument->threads_stopped = true; } TEST(StopTheWorld, SuspendThreadsAdvanced) { AdvancedCallbackArgument argument; { std::lock_guard lock(mutex); argument.threads[0] = std::thread(AdvancedIncrementerThread, std::ref(argument)); // Wait for several threads to spawn before proceeding. while (argument.thread_index < kStopWorldAfter) 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 (argument.thread_index < kThreadCount) std::this_thread::yield(); } // Signal the threads to terminate. for (auto &t : argument.threads) t.join(); } static void SegvCallback(const SuspendedThreadsList &suspended_threads_list, void *argument) { *(volatile int *)0x1234 = 0; } # if SANITIZER_WINDOWS # define MAYBE_SegvInCallback DISABLED_SegvInCallback # else # define MAYBE_SegvInCallback SegvInCallback # endif TEST(StopTheWorld, MAYBE_SegvInCallback) { // Test that tracer thread catches SIGSEGV. StopTheWorld(&SegvCallback, NULL); } } // namespace __sanitizer #endif // SANITIZER_LINUX && defined(__x86_64__)