| 1 | //===-- sanitizer_thread_registry_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 | // This file is a part of shared sanitizer runtime. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | #include "sanitizer_common/sanitizer_thread_registry.h" |
| 13 | |
| 14 | #include <iostream> |
| 15 | #include <vector> |
| 16 | |
| 17 | #include "gmock/gmock.h" |
| 18 | #include "gtest/gtest.h" |
| 19 | #include "sanitizer_common/sanitizer_common.h" |
| 20 | #include "sanitizer_common/sanitizer_stackdepot.h" |
| 21 | #include "sanitizer_common/sanitizer_stacktrace.h" |
| 22 | #include "sanitizer_common/sanitizer_thread_history.h" |
| 23 | #include "sanitizer_pthread_wrappers.h" |
| 24 | |
| 25 | using testing::HasSubstr; |
| 26 | |
| 27 | namespace __sanitizer { |
| 28 | |
| 29 | static Mutex tctx_allocator_lock; |
| 30 | static LowLevelAllocator tctx_allocator; |
| 31 | |
| 32 | template<typename TCTX> |
| 33 | static ThreadContextBase *GetThreadContext(u32 tid) { |
| 34 | Lock l(&tctx_allocator_lock); |
| 35 | return new(tctx_allocator) TCTX(tid); |
| 36 | } |
| 37 | |
| 38 | static const u32 kMaxRegistryThreads = 1000; |
| 39 | static const u32 kRegistryQuarantine = 2; |
| 40 | |
| 41 | static void CheckThreadQuantity(ThreadRegistry *registry, uptr exp_total, |
| 42 | uptr exp_running, uptr exp_alive) { |
| 43 | uptr total, running, alive; |
| 44 | registry->GetNumberOfThreads(total: &total, running: &running, alive: &alive); |
| 45 | EXPECT_EQ(exp_total, total); |
| 46 | EXPECT_EQ(exp_running, running); |
| 47 | EXPECT_EQ(exp_alive, alive); |
| 48 | } |
| 49 | |
| 50 | static bool is_detached(u32 tid) { |
| 51 | return (tid % 2 == 0); |
| 52 | } |
| 53 | |
| 54 | static uptr get_uid(u32 tid) { |
| 55 | return tid * 2; |
| 56 | } |
| 57 | |
| 58 | static bool HasName(ThreadContextBase *tctx, void *arg) { |
| 59 | char *name = (char*)arg; |
| 60 | return (0 == internal_strcmp(s1: tctx->name, s2: name)); |
| 61 | } |
| 62 | |
| 63 | static bool HasUid(ThreadContextBase *tctx, void *arg) { |
| 64 | uptr uid = (uptr)arg; |
| 65 | return (tctx->user_id == uid); |
| 66 | } |
| 67 | |
| 68 | static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) { |
| 69 | bool *arr = (bool*)arg; |
| 70 | arr[tctx->tid] = true; |
| 71 | } |
| 72 | |
| 73 | static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) { |
| 74 | // Create and start a main thread. |
| 75 | EXPECT_EQ(0U, registry->CreateThread(user_id: get_uid(tid: 0), detached: true, parent_tid: -1, stack_tid: 0, arg: nullptr)); |
| 76 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
| 77 | // Create a bunch of threads. |
| 78 | for (u32 i = 1; i <= 10; i++) { |
| 79 | EXPECT_EQ(i, registry->CreateThread(user_id: get_uid(tid: i), detached: is_detached(tid: i), parent_tid: 100 + i, |
| 80 | stack_tid: 200 + i, arg: nullptr)); |
| 81 | } |
| 82 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 1, exp_alive: 11); |
| 83 | // Start some of them. |
| 84 | for (u32 i = 1; i <= 5; i++) { |
| 85 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
| 86 | } |
| 87 | CheckThreadQuantity(registry, exp_total: 11, exp_running: 6, exp_alive: 11); |
| 88 | // Finish, create and start more threads. |
| 89 | for (u32 i = 1; i <= 5; i++) { |
| 90 | registry->FinishThread(tid: i); |
| 91 | if (!is_detached(tid: i)) |
| 92 | registry->JoinThread(tid: i, arg: 0); |
| 93 | } |
| 94 | for (u32 i = 6; i <= 10; i++) { |
| 95 | registry->StartThread(tid: i, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
| 96 | } |
| 97 | std::vector<u32> new_tids; |
| 98 | for (u32 i = 11; i <= 15; i++) { |
| 99 | new_tids.push_back( |
| 100 | registry->CreateThread(get_uid(i), is_detached(i), 0, 0, nullptr)); |
| 101 | } |
| 102 | ASSERT_LE(kRegistryQuarantine, 5U); |
| 103 | u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0); |
| 104 | CheckThreadQuantity(registry, exp_total, exp_running: 6, exp_alive: 11); |
| 105 | // Test SetThreadName and FindThread. |
| 106 | registry->SetThreadName(tid: 6, name: "six" ); |
| 107 | registry->SetThreadName(tid: 7, name: "seven" ); |
| 108 | EXPECT_EQ(7U, registry->FindThread(cb: HasName, arg: (void *)"seven" )); |
| 109 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasName, arg: (void *)"none" )); |
| 110 | EXPECT_EQ(0U, registry->FindThread(cb: HasUid, arg: (void *)get_uid(tid: 0))); |
| 111 | EXPECT_EQ(10U, registry->FindThread(cb: HasUid, arg: (void *)get_uid(tid: 10))); |
| 112 | EXPECT_EQ(kInvalidTid, registry->FindThread(cb: HasUid, arg: (void *)0x1234)); |
| 113 | EXPECT_EQ(7U, |
| 114 | registry->FindThread(cb: [](ThreadContextBase *tctx, |
| 115 | void *) { return tctx->parent_tid == 107; }, |
| 116 | arg: nullptr)); |
| 117 | EXPECT_EQ(8U, |
| 118 | registry->FindThread(cb: [](ThreadContextBase *tctx, |
| 119 | void *) { return tctx->stack_id == 208; }, |
| 120 | arg: nullptr)); |
| 121 | // Detach and finish and join remaining threads. |
| 122 | for (u32 i = 6; i <= 10; i++) { |
| 123 | registry->DetachThread(tid: i, arg: 0); |
| 124 | registry->FinishThread(tid: i); |
| 125 | } |
| 126 | for (u32 i = 0; i < new_tids.size(); i++) { |
| 127 | u32 tid = new_tids[i]; |
| 128 | registry->StartThread(tid, 0, ThreadType::Regular, 0); |
| 129 | registry->DetachThread(tid, 0); |
| 130 | registry->FinishThread(tid); |
| 131 | } |
| 132 | CheckThreadQuantity(registry, exp_total, exp_running: 1, exp_alive: 1); |
| 133 | // Test methods that require the caller to hold a ThreadRegistryLock. |
| 134 | bool has_tid[16]; |
| 135 | internal_memset(s: &has_tid[0], c: 0, n: sizeof(has_tid)); |
| 136 | { |
| 137 | ThreadRegistryLock l(registry); |
| 138 | registry->RunCallbackForEachThreadLocked(cb: MarkUidAsPresent, arg: &has_tid[0]); |
| 139 | } |
| 140 | for (u32 i = 0; i < exp_total; i++) { |
| 141 | EXPECT_TRUE(has_tid[i]); |
| 142 | } |
| 143 | { |
| 144 | ThreadRegistryLock l(registry); |
| 145 | registry->CheckLocked(); |
| 146 | ThreadContextBase *main_thread = registry->GetThreadLocked(tid: 0); |
| 147 | EXPECT_EQ(main_thread, registry->FindThreadContextLocked( |
| 148 | cb: HasUid, arg: (void*)get_uid(tid: 0))); |
| 149 | } |
| 150 | EXPECT_EQ(11U, registry->GetMaxAliveThreads()); |
| 151 | } |
| 152 | |
| 153 | TEST(SanitizerCommon, ThreadRegistryTest) { |
| 154 | ThreadRegistry quarantine_registry(GetThreadContext<ThreadContextBase>, |
| 155 | kMaxRegistryThreads, kRegistryQuarantine, |
| 156 | 0); |
| 157 | TestRegistry(registry: &quarantine_registry, has_quarantine: true); |
| 158 | |
| 159 | ThreadRegistry no_quarantine_registry(GetThreadContext<ThreadContextBase>, |
| 160 | kMaxRegistryThreads, |
| 161 | kMaxRegistryThreads, 0); |
| 162 | TestRegistry(registry: &no_quarantine_registry, has_quarantine: false); |
| 163 | } |
| 164 | |
| 165 | static const int kThreadsPerShard = 20; |
| 166 | static const int kNumShards = 25; |
| 167 | |
| 168 | static int num_created[kNumShards + 1]; |
| 169 | static int num_started[kNumShards + 1]; |
| 170 | static int num_joined[kNumShards + 1]; |
| 171 | |
| 172 | namespace { |
| 173 | |
| 174 | struct RunThreadArgs { |
| 175 | ThreadRegistry *registry; |
| 176 | uptr shard; // started from 1. |
| 177 | }; |
| 178 | |
| 179 | class TestThreadContext final : public ThreadContextBase { |
| 180 | public: |
| 181 | explicit TestThreadContext(int tid) : ThreadContextBase(tid) {} |
| 182 | void OnJoined(void *arg) { |
| 183 | uptr shard = (uptr)arg; |
| 184 | num_joined[shard]++; |
| 185 | } |
| 186 | void OnStarted(void *arg) { |
| 187 | uptr shard = (uptr)arg; |
| 188 | num_started[shard]++; |
| 189 | } |
| 190 | void OnCreated(void *arg) { |
| 191 | uptr shard = (uptr)arg; |
| 192 | num_created[shard]++; |
| 193 | } |
| 194 | }; |
| 195 | |
| 196 | } // namespace |
| 197 | |
| 198 | void *RunThread(void *arg) { |
| 199 | RunThreadArgs *args = static_cast<RunThreadArgs*>(arg); |
| 200 | std::vector<int> tids; |
| 201 | for (int i = 0; i < kThreadsPerShard; i++) |
| 202 | tids.push_back( |
| 203 | args->registry->CreateThread(0, false, 0, (void*)args->shard)); |
| 204 | for (int i = 0; i < kThreadsPerShard; i++) |
| 205 | args->registry->StartThread(tids[i], 0, ThreadType::Regular, |
| 206 | (void*)args->shard); |
| 207 | for (int i = 0; i < kThreadsPerShard; i++) |
| 208 | args->registry->FinishThread(tids[i]); |
| 209 | for (int i = 0; i < kThreadsPerShard; i++) |
| 210 | args->registry->JoinThread(tids[i], (void*)args->shard); |
| 211 | return 0; |
| 212 | } |
| 213 | |
| 214 | static void ThreadedTestRegistry(ThreadRegistry *registry) { |
| 215 | // Create and start a main thread. |
| 216 | EXPECT_EQ(0U, registry->CreateThread(user_id: 0, detached: true, parent_tid: -1, arg: 0)); |
| 217 | registry->StartThread(tid: 0, os_id: 0, thread_type: ThreadType::Regular, arg: 0); |
| 218 | pthread_t threads[kNumShards]; |
| 219 | RunThreadArgs args[kNumShards]; |
| 220 | for (int i = 0; i < kNumShards; i++) { |
| 221 | args[i].registry = registry; |
| 222 | args[i].shard = i + 1; |
| 223 | PTHREAD_CREATE(&threads[i], 0, RunThread, &args[i]); |
| 224 | } |
| 225 | for (int i = 0; i < kNumShards; i++) { |
| 226 | PTHREAD_JOIN(threads[i], 0); |
| 227 | } |
| 228 | // Check that each thread created/started/joined correct amount |
| 229 | // of "threads" in thread_registry. |
| 230 | EXPECT_EQ(1, num_created[0]); |
| 231 | EXPECT_EQ(1, num_started[0]); |
| 232 | EXPECT_EQ(0, num_joined[0]); |
| 233 | for (int i = 1; i <= kNumShards; i++) { |
| 234 | EXPECT_EQ(kThreadsPerShard, num_created[i]); |
| 235 | EXPECT_EQ(kThreadsPerShard, num_started[i]); |
| 236 | EXPECT_EQ(kThreadsPerShard, num_joined[i]); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | TEST(SanitizerCommon, ThreadRegistryThreadedTest) { |
| 241 | memset(&num_created, 0, sizeof(num_created)); |
| 242 | memset(&num_started, 0, sizeof(num_created)); |
| 243 | memset(&num_joined, 0, sizeof(num_created)); |
| 244 | |
| 245 | ThreadRegistry registry(GetThreadContext<TestThreadContext>, |
| 246 | kThreadsPerShard * kNumShards + 1, 10, 0); |
| 247 | ThreadedTestRegistry(registry: ®istry); |
| 248 | } |
| 249 | |
| 250 | TEST(SanitizerCommon, PrintThreadHistory) { |
| 251 | ThreadRegistry registry(GetThreadContext<TestThreadContext>, |
| 252 | kThreadsPerShard * kNumShards + 1, 10, 0); |
| 253 | |
| 254 | UNINITIALIZED BufferedStackTrace stack1; |
| 255 | stack1.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, |
| 256 | /*max_depth=*/1); |
| 257 | |
| 258 | UNINITIALIZED BufferedStackTrace stack2; |
| 259 | stack2.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false, |
| 260 | /*max_depth=*/1); |
| 261 | |
| 262 | EXPECT_EQ(0U, registry.CreateThread(0, true, -1, 0, nullptr)); |
| 263 | for (int i = 0; i < 5; i++) { |
| 264 | registry.CreateThread(0, true, 0, StackDepotPut(stack1), nullptr); |
| 265 | registry.CreateThread(0, true, 0, StackDepotPut(stack2), nullptr); |
| 266 | } |
| 267 | |
| 268 | InternalScopedString out; |
| 269 | PrintThreadHistory(registry, out); |
| 270 | |
| 271 | std::string substrings[] = { |
| 272 | "Thread T0/0 was created by T-1" , |
| 273 | "<empty stack>" , |
| 274 | "" , |
| 275 | "Thread T1/0 was created by T0/0" , |
| 276 | "Thread T3/0 was created by T0/0" , |
| 277 | "Thread T5/0 was created by T0/0" , |
| 278 | "Thread T7/0 was created by T0/0" , |
| 279 | "Thread T9/0 was created by T0/0" , |
| 280 | "#0 0x" , |
| 281 | "" , |
| 282 | "Thread T2/0 was created by T0/0" , |
| 283 | "Thread T4/0 was created by T0/0" , |
| 284 | "Thread T6/0 was created by T0/0" , |
| 285 | "Thread T8/0 was created by T0/0" , |
| 286 | "Thread T10/0 was created by T0/0" , |
| 287 | "#0 0x" , |
| 288 | "" , |
| 289 | }; |
| 290 | |
| 291 | std::stringstream ss(out.data()); |
| 292 | std::string line; |
| 293 | |
| 294 | for (auto substr : substrings) { |
| 295 | std::getline(ss, line); |
| 296 | EXPECT_THAT(line, HasSubstr(substr)) << line; |
| 297 | } |
| 298 | |
| 299 | EXPECT_FALSE(std::getline(ss, line)) << "Unmatched line: " << line; |
| 300 | } |
| 301 | |
| 302 | } // namespace __sanitizer |
| 303 | |