1 | //===-- sanitizer_stoptheworld_test.cpp -----------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Tests for sanitizer_stoptheworld.h |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "sanitizer_common/sanitizer_stoptheworld.h" |
14 | |
15 | #include "sanitizer_common/sanitizer_platform.h" |
16 | #if (SANITIZER_LINUX || SANITIZER_WINDOWS) && defined(__x86_64__) |
17 | |
18 | # include <atomic> |
19 | # include <mutex> |
20 | # include <thread> |
21 | |
22 | # include "gtest/gtest.h" |
23 | # include "sanitizer_common/sanitizer_common.h" |
24 | # include "sanitizer_common/sanitizer_libc.h" |
25 | |
26 | namespace __sanitizer { |
27 | |
28 | static std::mutex mutex; |
29 | |
30 | struct CallbackArgument { |
31 | std::atomic_int counter = {}; |
32 | std::atomic_bool threads_stopped = {}; |
33 | std::atomic_bool callback_executed = {}; |
34 | }; |
35 | |
36 | void IncrementerThread(CallbackArgument &callback_argument) { |
37 | while (true) { |
38 | callback_argument.counter++; |
39 | |
40 | if (mutex.try_lock()) { |
41 | mutex.unlock(); |
42 | return; |
43 | } |
44 | |
45 | std::this_thread::yield(); |
46 | } |
47 | } |
48 | |
49 | // This callback checks that IncrementerThread is suspended at the time of its |
50 | // execution. |
51 | void Callback(const SuspendedThreadsList &suspended_threads_list, |
52 | void *argument) { |
53 | CallbackArgument *callback_argument = (CallbackArgument *)argument; |
54 | callback_argument->callback_executed = true; |
55 | int counter_at_init = callback_argument->counter; |
56 | for (uptr i = 0; i < 1000; i++) { |
57 | std::this_thread::yield(); |
58 | if (callback_argument->counter != counter_at_init) { |
59 | callback_argument->threads_stopped = false; |
60 | return; |
61 | } |
62 | } |
63 | callback_argument->threads_stopped = true; |
64 | } |
65 | |
66 | TEST(StopTheWorld, SuspendThreadsSimple) { |
67 | CallbackArgument argument; |
68 | std::thread thread; |
69 | { |
70 | std::lock_guard<std::mutex> lock(mutex); |
71 | thread = std::thread(IncrementerThread, std::ref(argument)); |
72 | StopTheWorld(&Callback, &argument); |
73 | } |
74 | EXPECT_TRUE(argument.callback_executed); |
75 | EXPECT_TRUE(argument.threads_stopped); |
76 | // argument is on stack, so we have to wait for the incrementer thread to |
77 | // terminate before we can return from this function. |
78 | ASSERT_NO_THROW(thread.join()); |
79 | } |
80 | |
81 | // A more comprehensive test where we spawn a bunch of threads while executing |
82 | // StopTheWorld in parallel. |
83 | static const uptr kThreadCount = 50; |
84 | static const uptr kStopWorldAfter = 10; // let this many threads spawn first |
85 | |
86 | struct AdvancedCallbackArgument { |
87 | std::atomic_uintptr_t thread_index = {}; |
88 | std::atomic_int counters[kThreadCount] = {}; |
89 | std::thread threads[kThreadCount]; |
90 | std::atomic_bool threads_stopped = {}; |
91 | std::atomic_bool callback_executed = {}; |
92 | }; |
93 | |
94 | void AdvancedIncrementerThread(AdvancedCallbackArgument &callback_argument) { |
95 | uptr this_thread_index = callback_argument.thread_index++; |
96 | // Spawn the next thread. |
97 | if (this_thread_index + 1 < kThreadCount) { |
98 | callback_argument.threads[this_thread_index + 1] = |
99 | std::thread(AdvancedIncrementerThread, std::ref(callback_argument)); |
100 | } |
101 | // Do the actual work. |
102 | while (true) { |
103 | callback_argument.counters[this_thread_index]++; |
104 | if (mutex.try_lock()) { |
105 | mutex.unlock(); |
106 | return; |
107 | } |
108 | |
109 | std::this_thread::yield(); |
110 | } |
111 | } |
112 | |
113 | void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list, |
114 | void *argument) { |
115 | AdvancedCallbackArgument *callback_argument = |
116 | (AdvancedCallbackArgument *)argument; |
117 | callback_argument->callback_executed = true; |
118 | |
119 | int counters_at_init[kThreadCount]; |
120 | for (uptr j = 0; j < kThreadCount; j++) |
121 | counters_at_init[j] = callback_argument->counters[j]; |
122 | for (uptr i = 0; i < 10; i++) { |
123 | std::this_thread::yield(); |
124 | for (uptr j = 0; j < kThreadCount; j++) |
125 | if (callback_argument->counters[j] != counters_at_init[j]) { |
126 | callback_argument->threads_stopped = false; |
127 | return; |
128 | } |
129 | } |
130 | callback_argument->threads_stopped = true; |
131 | } |
132 | |
133 | TEST(StopTheWorld, SuspendThreadsAdvanced) { |
134 | AdvancedCallbackArgument argument; |
135 | |
136 | { |
137 | std::lock_guard<std::mutex> lock(mutex); |
138 | argument.threads[0] = |
139 | std::thread(AdvancedIncrementerThread, std::ref(argument)); |
140 | // Wait for several threads to spawn before proceeding. |
141 | while (argument.thread_index < kStopWorldAfter) std::this_thread::yield(); |
142 | StopTheWorld(&AdvancedCallback, &argument); |
143 | EXPECT_TRUE(argument.callback_executed); |
144 | EXPECT_TRUE(argument.threads_stopped); |
145 | |
146 | // Wait for all threads to spawn before we start terminating them. |
147 | while (argument.thread_index < kThreadCount) std::this_thread::yield(); |
148 | } |
149 | // Signal the threads to terminate. |
150 | for (auto &t : argument.threads) t.join(); |
151 | } |
152 | |
153 | static void SegvCallback(const SuspendedThreadsList &suspended_threads_list, |
154 | void *argument) { |
155 | *(volatile int *)0x1234 = 0; |
156 | } |
157 | |
158 | # if SANITIZER_WINDOWS |
159 | # define MAYBE_SegvInCallback DISABLED_SegvInCallback |
160 | # else |
161 | # define MAYBE_SegvInCallback SegvInCallback |
162 | # endif |
163 | |
164 | TEST(StopTheWorld, MAYBE_SegvInCallback) { |
165 | // Test that tracer thread catches SIGSEGV. |
166 | StopTheWorld(&SegvCallback, NULL); |
167 | } |
168 | |
169 | } // namespace __sanitizer |
170 | |
171 | #endif // SANITIZER_LINUX && defined(__x86_64__) |
172 | |