1 | //===-- tsan_rtl_thread.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 ThreadSanitizer (TSan), a race detector. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "sanitizer_common/sanitizer_placement_new.h" |
14 | #include "tsan_rtl.h" |
15 | #include "tsan_mman.h" |
16 | #include "tsan_platform.h" |
17 | #include "tsan_report.h" |
18 | #include "tsan_sync.h" |
19 | |
20 | namespace __tsan { |
21 | |
22 | // ThreadContext implementation. |
23 | |
24 | ThreadContext::ThreadContext(Tid tid) : ThreadContextBase(tid), thr(), sync() {} |
25 | |
26 | #if !SANITIZER_GO |
27 | ThreadContext::~ThreadContext() { |
28 | } |
29 | #endif |
30 | |
31 | void ThreadContext::OnReset() { CHECK(!sync); } |
32 | |
33 | #if !SANITIZER_GO |
34 | struct ThreadLeak { |
35 | ThreadContext *tctx; |
36 | int count; |
37 | }; |
38 | |
39 | static void CollectThreadLeaks(ThreadContextBase *tctx_base, void *arg) { |
40 | auto &leaks = *static_cast<Vector<ThreadLeak> *>(arg); |
41 | auto *tctx = static_cast<ThreadContext *>(tctx_base); |
42 | if (tctx->detached || tctx->status != ThreadStatusFinished) |
43 | return; |
44 | for (uptr i = 0; i < leaks.Size(); i++) { |
45 | if (leaks[i].tctx->creation_stack_id == tctx->creation_stack_id) { |
46 | leaks[i].count++; |
47 | return; |
48 | } |
49 | } |
50 | leaks.PushBack(v: {.tctx: tctx, .count: 1}); |
51 | } |
52 | #endif |
53 | |
54 | // Disabled on Mac because lldb test TestTsanBasic fails: |
55 | // https://reviews.llvm.org/D112603#3163158 |
56 | #if !SANITIZER_GO && !SANITIZER_APPLE |
57 | static void ReportIgnoresEnabled(ThreadContext *tctx, IgnoreSet *set) { |
58 | if (tctx->tid == kMainTid) { |
59 | Printf(format: "ThreadSanitizer: main thread finished with ignores enabled\n" ); |
60 | } else { |
61 | Printf(format: "ThreadSanitizer: thread T%d %s finished with ignores enabled," |
62 | " created at:\n" , tctx->tid, tctx->name); |
63 | PrintStack(stack: SymbolizeStackId(stack_id: tctx->creation_stack_id)); |
64 | } |
65 | Printf(format: " One of the following ignores was not ended" |
66 | " (in order of probability)\n" ); |
67 | for (uptr i = 0; i < set->Size(); i++) { |
68 | Printf(format: " Ignore was enabled at:\n" ); |
69 | PrintStack(stack: SymbolizeStackId(stack_id: set->At(i))); |
70 | } |
71 | Die(); |
72 | } |
73 | |
74 | static void ThreadCheckIgnore(ThreadState *thr) { |
75 | if (ctx->after_multithreaded_fork) |
76 | return; |
77 | if (thr->ignore_reads_and_writes) |
78 | ReportIgnoresEnabled(tctx: thr->tctx, set: &thr->mop_ignore_set); |
79 | if (thr->ignore_sync) |
80 | ReportIgnoresEnabled(tctx: thr->tctx, set: &thr->sync_ignore_set); |
81 | } |
82 | #else |
83 | static void ThreadCheckIgnore(ThreadState *thr) {} |
84 | #endif |
85 | |
86 | void ThreadFinalize(ThreadState *thr) { |
87 | ThreadCheckIgnore(thr); |
88 | #if !SANITIZER_GO |
89 | if (!ShouldReport(thr, typ: ReportTypeThreadLeak)) |
90 | return; |
91 | ThreadRegistryLock l(&ctx->thread_registry); |
92 | Vector<ThreadLeak> leaks; |
93 | ctx->thread_registry.RunCallbackForEachThreadLocked(cb: CollectThreadLeaks, |
94 | arg: &leaks); |
95 | for (uptr i = 0; i < leaks.Size(); i++) { |
96 | ScopedReport rep(ReportTypeThreadLeak); |
97 | rep.AddThread(tctx: leaks[i].tctx, suppressable: true); |
98 | rep.SetCount(leaks[i].count); |
99 | OutputReport(thr, srep: rep); |
100 | } |
101 | #endif |
102 | } |
103 | |
104 | int ThreadCount(ThreadState *thr) { |
105 | uptr result; |
106 | ctx->thread_registry.GetNumberOfThreads(total: 0, running: 0, alive: &result); |
107 | return (int)result; |
108 | } |
109 | |
110 | struct OnCreatedArgs { |
111 | VectorClock *sync; |
112 | uptr sync_epoch; |
113 | StackID stack; |
114 | }; |
115 | |
116 | Tid ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { |
117 | // The main thread and GCD workers don't have a parent thread. |
118 | Tid parent = kInvalidTid; |
119 | OnCreatedArgs arg = {.sync: nullptr, .sync_epoch: 0, .stack: kInvalidStackID}; |
120 | if (thr) { |
121 | parent = thr->tid; |
122 | arg.stack = CurrentStackId(thr, pc); |
123 | if (!thr->ignore_sync) { |
124 | SlotLocker locker(thr); |
125 | thr->clock.ReleaseStore(dstp: &arg.sync); |
126 | arg.sync_epoch = ctx->global_epoch; |
127 | IncrementEpoch(thr); |
128 | } |
129 | } |
130 | Tid tid = ctx->thread_registry.CreateThread(user_id: uid, detached, parent_tid: parent, arg: &arg); |
131 | DPrintf("#%d: ThreadCreate tid=%d uid=%zu\n" , parent, tid, uid); |
132 | return tid; |
133 | } |
134 | |
135 | void ThreadContext::OnCreated(void *arg) { |
136 | OnCreatedArgs *args = static_cast<OnCreatedArgs *>(arg); |
137 | sync = args->sync; |
138 | sync_epoch = args->sync_epoch; |
139 | creation_stack_id = args->stack; |
140 | } |
141 | |
142 | extern "C" void __tsan_stack_initialization() {} |
143 | |
144 | struct OnStartedArgs { |
145 | ThreadState *thr; |
146 | uptr stk_addr; |
147 | uptr stk_size; |
148 | uptr tls_addr; |
149 | uptr tls_size; |
150 | }; |
151 | |
152 | void ThreadStart(ThreadState *thr, Tid tid, tid_t os_id, |
153 | ThreadType thread_type) { |
154 | ctx->thread_registry.StartThread(tid, os_id, thread_type, arg: thr); |
155 | if (!thr->ignore_sync) { |
156 | SlotAttachAndLock(thr); |
157 | if (thr->tctx->sync_epoch == ctx->global_epoch) |
158 | thr->clock.Acquire(src: thr->tctx->sync); |
159 | SlotUnlock(thr); |
160 | } |
161 | Free(p&: thr->tctx->sync); |
162 | |
163 | #if !SANITIZER_GO |
164 | thr->is_inited = true; |
165 | #endif |
166 | |
167 | uptr stk_addr = 0; |
168 | uptr stk_size = 0; |
169 | uptr tls_addr = 0; |
170 | uptr tls_size = 0; |
171 | #if !SANITIZER_GO |
172 | if (thread_type != ThreadType::Fiber) |
173 | GetThreadStackAndTls(main: tid == kMainTid, stk_addr: &stk_addr, stk_size: &stk_size, tls_addr: &tls_addr, |
174 | tls_size: &tls_size); |
175 | #endif |
176 | thr->stk_addr = stk_addr; |
177 | thr->stk_size = stk_size; |
178 | thr->tls_addr = tls_addr; |
179 | thr->tls_size = tls_size; |
180 | |
181 | #if !SANITIZER_GO |
182 | if (ctx->after_multithreaded_fork) { |
183 | thr->ignore_interceptors++; |
184 | ThreadIgnoreBegin(thr, pc: 0); |
185 | ThreadIgnoreSyncBegin(thr, pc: 0); |
186 | } |
187 | #endif |
188 | |
189 | #if !SANITIZER_GO |
190 | // Don't imitate stack/TLS writes for the main thread, |
191 | // because its initialization is synchronized with all |
192 | // subsequent threads anyway. |
193 | if (tid != kMainTid) { |
194 | if (stk_addr && stk_size) { |
195 | const uptr pc = StackTrace::GetNextInstructionPc( |
196 | pc: reinterpret_cast<uptr>(__tsan_stack_initialization)); |
197 | MemoryRangeImitateWrite(thr, pc, addr: stk_addr, size: stk_size); |
198 | } |
199 | |
200 | if (tls_addr && tls_size) |
201 | ImitateTlsWrite(thr, tls_addr, tls_size); |
202 | } |
203 | #endif |
204 | } |
205 | |
206 | void ThreadContext::OnStarted(void *arg) { |
207 | DPrintf("#%d: ThreadStart\n" , tid); |
208 | thr = new (arg) ThreadState(tid); |
209 | if (common_flags()->detect_deadlocks) |
210 | thr->dd_lt = ctx->dd->CreateLogicalThread(ctx: tid); |
211 | thr->tctx = this; |
212 | } |
213 | |
214 | void ThreadFinish(ThreadState *thr) { |
215 | DPrintf("#%d: ThreadFinish\n" , thr->tid); |
216 | ThreadCheckIgnore(thr); |
217 | if (thr->stk_addr && thr->stk_size) |
218 | DontNeedShadowFor(addr: thr->stk_addr, size: thr->stk_size); |
219 | if (thr->tls_addr && thr->tls_size) |
220 | DontNeedShadowFor(addr: thr->tls_addr, size: thr->tls_size); |
221 | thr->is_dead = true; |
222 | #if !SANITIZER_GO |
223 | thr->is_inited = false; |
224 | thr->ignore_interceptors++; |
225 | PlatformCleanUpThreadState(thr); |
226 | #endif |
227 | if (!thr->ignore_sync) { |
228 | SlotLocker locker(thr); |
229 | ThreadRegistryLock lock(&ctx->thread_registry); |
230 | // Note: detached is protected by the thread registry mutex, |
231 | // the thread may be detaching concurrently in another thread. |
232 | if (!thr->tctx->detached) { |
233 | thr->clock.ReleaseStore(dstp: &thr->tctx->sync); |
234 | thr->tctx->sync_epoch = ctx->global_epoch; |
235 | IncrementEpoch(thr); |
236 | } |
237 | } |
238 | #if !SANITIZER_GO |
239 | UnmapOrDie(addr: thr->shadow_stack, size: kShadowStackSize * sizeof(uptr)); |
240 | #else |
241 | Free(thr->shadow_stack); |
242 | #endif |
243 | thr->shadow_stack = nullptr; |
244 | thr->shadow_stack_pos = nullptr; |
245 | thr->shadow_stack_end = nullptr; |
246 | if (common_flags()->detect_deadlocks) |
247 | ctx->dd->DestroyLogicalThread(lt: thr->dd_lt); |
248 | SlotDetach(thr); |
249 | ctx->thread_registry.FinishThread(tid: thr->tid); |
250 | thr->~ThreadState(); |
251 | } |
252 | |
253 | void ThreadContext::OnFinished() { |
254 | Lock lock(&ctx->slot_mtx); |
255 | Lock lock1(&trace.mtx); |
256 | // Queue all trace parts into the global recycle queue. |
257 | auto parts = &trace.parts; |
258 | while (trace.local_head) { |
259 | CHECK(parts->Queued(trace.local_head)); |
260 | ctx->trace_part_recycle.PushBack(e: trace.local_head); |
261 | trace.local_head = parts->Next(e: trace.local_head); |
262 | } |
263 | ctx->trace_part_recycle_finished += parts->Size(); |
264 | if (ctx->trace_part_recycle_finished > Trace::kFinishedThreadHi) { |
265 | ctx->trace_part_finished_excess += parts->Size(); |
266 | trace.parts_allocated = 0; |
267 | } else if (ctx->trace_part_recycle_finished > Trace::kFinishedThreadLo && |
268 | parts->Size() > 1) { |
269 | ctx->trace_part_finished_excess += parts->Size() - 1; |
270 | trace.parts_allocated = 1; |
271 | } |
272 | // From now on replay will use trace->final_pos. |
273 | trace.final_pos = (Event *)atomic_load_relaxed(a: &thr->trace_pos); |
274 | atomic_store_relaxed(a: &thr->trace_pos, v: 0); |
275 | thr->tctx = nullptr; |
276 | thr = nullptr; |
277 | } |
278 | |
279 | struct ConsumeThreadContext { |
280 | uptr uid; |
281 | ThreadContextBase *tctx; |
282 | }; |
283 | |
284 | Tid ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid) { |
285 | return ctx->thread_registry.ConsumeThreadUserId(user_id: uid); |
286 | } |
287 | |
288 | struct JoinArg { |
289 | VectorClock *sync; |
290 | uptr sync_epoch; |
291 | }; |
292 | |
293 | void ThreadJoin(ThreadState *thr, uptr pc, Tid tid) { |
294 | CHECK_GT(tid, 0); |
295 | DPrintf("#%d: ThreadJoin tid=%d\n" , thr->tid, tid); |
296 | JoinArg arg = {}; |
297 | ctx->thread_registry.JoinThread(tid, arg: &arg); |
298 | if (!thr->ignore_sync) { |
299 | SlotLocker locker(thr); |
300 | if (arg.sync_epoch == ctx->global_epoch) |
301 | thr->clock.Acquire(src: arg.sync); |
302 | } |
303 | Free(p&: arg.sync); |
304 | } |
305 | |
306 | void ThreadContext::OnJoined(void *ptr) { |
307 | auto arg = static_cast<JoinArg *>(ptr); |
308 | arg->sync = sync; |
309 | arg->sync_epoch = sync_epoch; |
310 | sync = nullptr; |
311 | sync_epoch = 0; |
312 | } |
313 | |
314 | void ThreadContext::OnDead() { CHECK_EQ(sync, nullptr); } |
315 | |
316 | void ThreadDetach(ThreadState *thr, uptr pc, Tid tid) { |
317 | CHECK_GT(tid, 0); |
318 | ctx->thread_registry.DetachThread(tid, arg: thr); |
319 | } |
320 | |
321 | void ThreadContext::OnDetached(void *arg) { Free(p&: sync); } |
322 | |
323 | void ThreadNotJoined(ThreadState *thr, uptr pc, Tid tid, uptr uid) { |
324 | CHECK_GT(tid, 0); |
325 | ctx->thread_registry.SetThreadUserId(tid, user_id: uid); |
326 | } |
327 | |
328 | void ThreadSetName(ThreadState *thr, const char *name) { |
329 | ctx->thread_registry.SetThreadName(tid: thr->tid, name); |
330 | } |
331 | |
332 | #if !SANITIZER_GO |
333 | void FiberSwitchImpl(ThreadState *from, ThreadState *to) { |
334 | Processor *proc = from->proc(); |
335 | ProcUnwire(proc, thr: from); |
336 | ProcWire(proc, thr: to); |
337 | set_cur_thread(to); |
338 | } |
339 | |
340 | ThreadState *FiberCreate(ThreadState *thr, uptr pc, unsigned flags) { |
341 | void *mem = Alloc(sz: sizeof(ThreadState)); |
342 | ThreadState *fiber = static_cast<ThreadState *>(mem); |
343 | internal_memset(s: fiber, c: 0, n: sizeof(*fiber)); |
344 | Tid tid = ThreadCreate(thr, pc, uid: 0, detached: true); |
345 | FiberSwitchImpl(from: thr, to: fiber); |
346 | ThreadStart(thr: fiber, tid, os_id: 0, thread_type: ThreadType::Fiber); |
347 | FiberSwitchImpl(from: fiber, to: thr); |
348 | return fiber; |
349 | } |
350 | |
351 | void FiberDestroy(ThreadState *thr, uptr pc, ThreadState *fiber) { |
352 | FiberSwitchImpl(from: thr, to: fiber); |
353 | ThreadFinish(thr: fiber); |
354 | FiberSwitchImpl(from: fiber, to: thr); |
355 | Free(p&: fiber); |
356 | } |
357 | |
358 | void FiberSwitch(ThreadState *thr, uptr pc, |
359 | ThreadState *fiber, unsigned flags) { |
360 | if (!(flags & FiberSwitchFlagNoSync)) |
361 | Release(thr, pc, addr: (uptr)fiber); |
362 | FiberSwitchImpl(from: thr, to: fiber); |
363 | if (!(flags & FiberSwitchFlagNoSync)) |
364 | Acquire(thr: fiber, pc, addr: (uptr)fiber); |
365 | } |
366 | #endif |
367 | |
368 | } // namespace __tsan |
369 | |