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_platform.h"
14 #if SANITIZER_LINUX && defined(__x86_64__)
15
16 #include "sanitizer_common/sanitizer_stoptheworld.h"
17 #include "gtest/gtest.h"
18
19 #include "sanitizer_common/sanitizer_libc.h"
20 #include "sanitizer_common/sanitizer_common.h"
21
22 #include <pthread.h>
23 #include <sched.h>
24
25 namespace __sanitizer {
26
27 static pthread_mutex_t incrementer_thread_exit_mutex;
28
29 struct CallbackArgument {
30 volatile int counter;
31 volatile bool threads_stopped;
32 volatile bool callback_executed;
CallbackArgument__sanitizer::CallbackArgument33 CallbackArgument()
34 : counter(0),
35 threads_stopped(false),
36 callback_executed(false) {}
37 };
38
IncrementerThread(void * argument)39 void *IncrementerThread(void *argument) {
40 CallbackArgument *callback_argument = (CallbackArgument *)argument;
41 while (true) {
42 __sync_fetch_and_add(&callback_argument->counter, 1);
43 if (pthread_mutex_trylock(&incrementer_thread_exit_mutex) == 0) {
44 pthread_mutex_unlock(&incrementer_thread_exit_mutex);
45 return NULL;
46 } else {
47 sched_yield();
48 }
49 }
50 }
51
52 // This callback checks that IncrementerThread is suspended at the time of its
53 // execution.
Callback(const SuspendedThreadsList & suspended_threads_list,void * argument)54 void Callback(const SuspendedThreadsList &suspended_threads_list,
55 void *argument) {
56 CallbackArgument *callback_argument = (CallbackArgument *)argument;
57 callback_argument->callback_executed = true;
58 int counter_at_init = __sync_fetch_and_add(&callback_argument->counter, 0);
59 for (uptr i = 0; i < 1000; i++) {
60 sched_yield();
61 if (__sync_fetch_and_add(&callback_argument->counter, 0) !=
62 counter_at_init) {
63 callback_argument->threads_stopped = false;
64 return;
65 }
66 }
67 callback_argument->threads_stopped = true;
68 }
69
TEST(StopTheWorld,SuspendThreadsSimple)70 TEST(StopTheWorld, SuspendThreadsSimple) {
71 pthread_mutex_init(&incrementer_thread_exit_mutex, NULL);
72 CallbackArgument argument;
73 pthread_t thread_id;
74 int pthread_create_result;
75 pthread_mutex_lock(&incrementer_thread_exit_mutex);
76 pthread_create_result = pthread_create(&thread_id, NULL, IncrementerThread,
77 &argument);
78 ASSERT_EQ(0, pthread_create_result);
79 StopTheWorld(&Callback, &argument);
80 pthread_mutex_unlock(&incrementer_thread_exit_mutex);
81 EXPECT_TRUE(argument.callback_executed);
82 EXPECT_TRUE(argument.threads_stopped);
83 // argument is on stack, so we have to wait for the incrementer thread to
84 // terminate before we can return from this function.
85 ASSERT_EQ(0, pthread_join(thread_id, NULL));
86 pthread_mutex_destroy(&incrementer_thread_exit_mutex);
87 }
88
89 // A more comprehensive test where we spawn a bunch of threads while executing
90 // StopTheWorld in parallel.
91 static const uptr kThreadCount = 50;
92 static const uptr kStopWorldAfter = 10; // let this many threads spawn first
93
94 static pthread_mutex_t advanced_incrementer_thread_exit_mutex;
95
96 struct AdvancedCallbackArgument {
97 volatile uptr thread_index;
98 volatile int counters[kThreadCount];
99 pthread_t thread_ids[kThreadCount];
100 volatile bool threads_stopped;
101 volatile bool callback_executed;
102 volatile bool fatal_error;
AdvancedCallbackArgument__sanitizer::AdvancedCallbackArgument103 AdvancedCallbackArgument()
104 : thread_index(0),
105 threads_stopped(false),
106 callback_executed(false),
107 fatal_error(false) {}
108 };
109
AdvancedIncrementerThread(void * argument)110 void *AdvancedIncrementerThread(void *argument) {
111 AdvancedCallbackArgument *callback_argument =
112 (AdvancedCallbackArgument *)argument;
113 uptr this_thread_index = __sync_fetch_and_add(
114 &callback_argument->thread_index, 1);
115 // Spawn the next thread.
116 int pthread_create_result;
117 if (this_thread_index + 1 < kThreadCount) {
118 pthread_create_result =
119 pthread_create(&callback_argument->thread_ids[this_thread_index + 1],
120 NULL, AdvancedIncrementerThread, argument);
121 // Cannot use ASSERT_EQ in non-void-returning functions. If there's a
122 // problem, defer failing to the main thread.
123 if (pthread_create_result != 0) {
124 callback_argument->fatal_error = true;
125 __sync_fetch_and_add(&callback_argument->thread_index,
126 kThreadCount - callback_argument->thread_index);
127 }
128 }
129 // Do the actual work.
130 while (true) {
131 __sync_fetch_and_add(&callback_argument->counters[this_thread_index], 1);
132 if (pthread_mutex_trylock(&advanced_incrementer_thread_exit_mutex) == 0) {
133 pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex);
134 return NULL;
135 } else {
136 sched_yield();
137 }
138 }
139 }
140
AdvancedCallback(const SuspendedThreadsList & suspended_threads_list,void * argument)141 void AdvancedCallback(const SuspendedThreadsList &suspended_threads_list,
142 void *argument) {
143 AdvancedCallbackArgument *callback_argument =
144 (AdvancedCallbackArgument *)argument;
145 callback_argument->callback_executed = true;
146
147 int counters_at_init[kThreadCount];
148 for (uptr j = 0; j < kThreadCount; j++)
149 counters_at_init[j] = __sync_fetch_and_add(&callback_argument->counters[j],
150 0);
151 for (uptr i = 0; i < 10; i++) {
152 sched_yield();
153 for (uptr j = 0; j < kThreadCount; j++)
154 if (__sync_fetch_and_add(&callback_argument->counters[j], 0) !=
155 counters_at_init[j]) {
156 callback_argument->threads_stopped = false;
157 return;
158 }
159 }
160 callback_argument->threads_stopped = true;
161 }
162
TEST(StopTheWorld,SuspendThreadsAdvanced)163 TEST(StopTheWorld, SuspendThreadsAdvanced) {
164 pthread_mutex_init(&advanced_incrementer_thread_exit_mutex, NULL);
165 AdvancedCallbackArgument argument;
166
167 pthread_mutex_lock(&advanced_incrementer_thread_exit_mutex);
168 int pthread_create_result;
169 pthread_create_result = pthread_create(&argument.thread_ids[0], NULL,
170 AdvancedIncrementerThread,
171 &argument);
172 ASSERT_EQ(0, pthread_create_result);
173 // Wait for several threads to spawn before proceeding.
174 while (__sync_fetch_and_add(&argument.thread_index, 0) < kStopWorldAfter)
175 sched_yield();
176 StopTheWorld(&AdvancedCallback, &argument);
177 EXPECT_TRUE(argument.callback_executed);
178 EXPECT_TRUE(argument.threads_stopped);
179
180 // Wait for all threads to spawn before we start terminating them.
181 while (__sync_fetch_and_add(&argument.thread_index, 0) < kThreadCount)
182 sched_yield();
183 ASSERT_FALSE(argument.fatal_error); // a pthread_create has failed
184 // Signal the threads to terminate.
185 pthread_mutex_unlock(&advanced_incrementer_thread_exit_mutex);
186 for (uptr i = 0; i < kThreadCount; i++)
187 ASSERT_EQ(0, pthread_join(argument.thread_ids[i], NULL));
188 pthread_mutex_destroy(&advanced_incrementer_thread_exit_mutex);
189 }
190
SegvCallback(const SuspendedThreadsList & suspended_threads_list,void * argument)191 static void SegvCallback(const SuspendedThreadsList &suspended_threads_list,
192 void *argument) {
193 *(volatile int*)0x1234 = 0;
194 }
195
TEST(StopTheWorld,SegvInCallback)196 TEST(StopTheWorld, SegvInCallback) {
197 // Test that tracer thread catches SIGSEGV.
198 StopTheWorld(&SegvCallback, NULL);
199 }
200
201 } // namespace __sanitizer
202
203 #endif // SANITIZER_LINUX && defined(__x86_64__)
204