1//===-- tsan_rtl.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// Main file (entry points) for the TSan run-time.
12//===----------------------------------------------------------------------===//
13
14#include "tsan_rtl.h"
15
16#include "sanitizer_common/sanitizer_atomic.h"
17#include "sanitizer_common/sanitizer_common.h"
18#include "sanitizer_common/sanitizer_file.h"
19#include "sanitizer_common/sanitizer_interface_internal.h"
20#include "sanitizer_common/sanitizer_libc.h"
21#include "sanitizer_common/sanitizer_placement_new.h"
22#include "sanitizer_common/sanitizer_stackdepot.h"
23#include "sanitizer_common/sanitizer_symbolizer.h"
24#include "tsan_adaptive_delay.h"
25#include "tsan_defs.h"
26#include "tsan_interface.h"
27#include "tsan_mman.h"
28#include "tsan_platform.h"
29#include "tsan_suppressions.h"
30#include "tsan_symbolize.h"
31#include "ubsan/ubsan_init.h"
32
33volatile int __tsan_resumed = 0;
34
35extern "C" void __tsan_resume() {
36 __tsan_resumed = 1;
37}
38
39#if SANITIZER_APPLE
40SANITIZER_WEAK_DEFAULT_IMPL
41void __tsan_test_only_on_fork() {}
42#endif
43
44#if SANITIZER_APPLE && !SANITIZER_GO
45// Override weak symbol from sanitizer_common
46extern void __tsan_set_in_internal_write_call(bool value) {
47 __tsan::cur_thread_init()->in_internal_write_call = value;
48}
49#endif
50
51namespace __tsan {
52
53#if !SANITIZER_GO
54void (*on_initialize)(void);
55int (*on_finalize)(int);
56#endif
57
58#if !SANITIZER_GO && !SANITIZER_APPLE
59alignas(SANITIZER_CACHE_LINE_SIZE) THREADLOCAL __attribute__((tls_model(
60 "initial-exec"))) char cur_thread_placeholder[sizeof(ThreadState)];
61#endif
62alignas(SANITIZER_CACHE_LINE_SIZE) static char ctx_placeholder[sizeof(Context)];
63Context *ctx;
64
65// Can be overriden by a front-end.
66#ifdef TSAN_EXTERNAL_HOOKS
67bool OnFinalize(bool failed);
68void OnInitialize();
69#else
70SANITIZER_WEAK_CXX_DEFAULT_IMPL
71bool OnFinalize(bool failed) {
72# if !SANITIZER_GO
73 if (on_finalize)
74 return on_finalize(failed);
75# endif
76 return failed;
77}
78
79SANITIZER_WEAK_CXX_DEFAULT_IMPL
80void OnInitialize() {
81# if !SANITIZER_GO
82 if (on_initialize)
83 on_initialize();
84# endif
85}
86#endif
87
88static TracePart* TracePartAlloc(ThreadState* thr) {
89 TracePart* part = nullptr;
90 {
91 Lock lock(&ctx->slot_mtx);
92 uptr max_parts = Trace::kMinParts + flags()->history_size;
93 Trace* trace = &thr->tctx->trace;
94 if (trace->parts_allocated == max_parts ||
95 ctx->trace_part_finished_excess) {
96 part = ctx->trace_part_recycle.PopFront();
97 DPrintf("#%d: TracePartAlloc: part=%p\n", thr->tid, part);
98 if (part && part->trace) {
99 Trace* trace1 = part->trace;
100 Lock trace_lock(&trace1->mtx);
101 part->trace = nullptr;
102 TracePart* part1 = trace1->parts.PopFront();
103 CHECK_EQ(part, part1);
104 if (trace1->parts_allocated > trace1->parts.Size()) {
105 ctx->trace_part_finished_excess +=
106 trace1->parts_allocated - trace1->parts.Size();
107 trace1->parts_allocated = trace1->parts.Size();
108 }
109 }
110 }
111 if (trace->parts_allocated < max_parts) {
112 trace->parts_allocated++;
113 if (ctx->trace_part_finished_excess)
114 ctx->trace_part_finished_excess--;
115 }
116 if (!part)
117 ctx->trace_part_total_allocated++;
118 else if (ctx->trace_part_recycle_finished)
119 ctx->trace_part_recycle_finished--;
120 }
121 if (!part)
122 part = new (MmapOrDie(size: sizeof(*part), mem_type: "TracePart")) TracePart();
123 return part;
124}
125
126static void TracePartFree(TracePart* part) SANITIZER_REQUIRES(ctx->slot_mtx) {
127 DCHECK(part->trace);
128 part->trace = nullptr;
129 ctx->trace_part_recycle.PushFront(e: part);
130}
131
132void TraceResetForTesting() {
133 Lock lock(&ctx->slot_mtx);
134 while (auto* part = ctx->trace_part_recycle.PopFront()) {
135 if (auto trace = part->trace)
136 CHECK_EQ(trace->parts.PopFront(), part);
137 UnmapOrDie(addr: part, size: sizeof(*part));
138 }
139 ctx->trace_part_total_allocated = 0;
140 ctx->trace_part_recycle_finished = 0;
141 ctx->trace_part_finished_excess = 0;
142}
143
144static void DoResetImpl(uptr epoch) {
145 ThreadRegistryLock lock0(&ctx->thread_registry);
146 Lock lock1(&ctx->slot_mtx);
147 CHECK_EQ(ctx->global_epoch, epoch);
148 ctx->global_epoch++;
149 CHECK(!ctx->resetting);
150 ctx->resetting = true;
151 for (u32 i = ctx->thread_registry.NumThreadsLocked(); i--;) {
152 ThreadContext* tctx = (ThreadContext*)ctx->thread_registry.GetThreadLocked(
153 tid: static_cast<Tid>(i));
154 // Potentially we could purge all ThreadStatusDead threads from the
155 // registry. Since we reset all shadow, they can't race with anything
156 // anymore. However, their tid's can still be stored in some aux places
157 // (e.g. tid of thread that created something).
158 auto trace = &tctx->trace;
159 Lock lock(&trace->mtx);
160 bool attached = tctx->thr && tctx->thr->slot;
161 auto parts = &trace->parts;
162 bool local = false;
163 while (!parts->Empty()) {
164 auto part = parts->Front();
165 local = local || part == trace->local_head;
166 if (local)
167 CHECK(!ctx->trace_part_recycle.Queued(part));
168 else
169 ctx->trace_part_recycle.Remove(e: part);
170 if (attached && parts->Size() == 1) {
171 // The thread is running and this is the last/current part.
172 // Set the trace position to the end of the current part
173 // to force the thread to call SwitchTracePart and re-attach
174 // to a new slot and allocate a new trace part.
175 // Note: the thread is concurrently modifying the position as well,
176 // so this is only best-effort. The thread can only modify position
177 // within this part, because switching parts is protected by
178 // slot/trace mutexes that we hold here.
179 atomic_store_relaxed(
180 a: &tctx->thr->trace_pos,
181 v: reinterpret_cast<uptr>(&part->events[TracePart::kSize]));
182 break;
183 }
184 parts->Remove(e: part);
185 TracePartFree(part);
186 }
187 CHECK_LE(parts->Size(), 1);
188 trace->local_head = parts->Front();
189 if (tctx->thr && !tctx->thr->slot) {
190 atomic_store_relaxed(a: &tctx->thr->trace_pos, v: 0);
191 tctx->thr->trace_prev_pc = 0;
192 }
193 if (trace->parts_allocated > trace->parts.Size()) {
194 ctx->trace_part_finished_excess +=
195 trace->parts_allocated - trace->parts.Size();
196 trace->parts_allocated = trace->parts.Size();
197 }
198 }
199 while (ctx->slot_queue.PopFront()) {
200 }
201 for (auto& slot : ctx->slots) {
202 slot.SetEpoch(kEpochZero);
203 slot.journal.Reset();
204 slot.thr = nullptr;
205 ctx->slot_queue.PushBack(e: &slot);
206 }
207
208 DPrintf("Resetting shadow...\n");
209 auto shadow_begin = ShadowBeg();
210 auto shadow_end = ShadowEnd();
211#if SANITIZER_GO
212 CHECK_NE(0, ctx->mapped_shadow_begin);
213 shadow_begin = ctx->mapped_shadow_begin;
214 shadow_end = ctx->mapped_shadow_end;
215 VPrintf(2, "shadow_begin-shadow_end: (0x%zx-0x%zx)\n",
216 shadow_begin, shadow_end);
217#endif
218
219#if SANITIZER_WINDOWS
220 auto resetFailed =
221 !ZeroMmapFixedRegion(shadow_begin, shadow_end - shadow_begin);
222#else
223 auto resetFailed =
224 !MmapFixedSuperNoReserve(fixed_addr: shadow_begin, size: shadow_end-shadow_begin, name: "shadow");
225# if !SANITIZER_GO
226 DontDumpShadow(addr: shadow_begin, size: shadow_end - shadow_begin);
227# endif
228#endif
229 if (resetFailed) {
230 Printf(format: "failed to reset shadow memory\n");
231 Die();
232 }
233 DPrintf("Resetting meta shadow...\n");
234 ctx->metamap.ResetClocks();
235 StoreShadow(sp: &ctx->last_spurious_race, s: Shadow::kEmpty);
236 ctx->resetting = false;
237}
238
239// Clang does not understand locking all slots in the loop:
240// error: expecting mutex 'slot.mtx' to be held at start of each loop
241void DoReset(ThreadState* thr, uptr epoch) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
242 for (auto& slot : ctx->slots) {
243 slot.mtx.Lock();
244 if (UNLIKELY(epoch == 0))
245 epoch = ctx->global_epoch;
246 if (UNLIKELY(epoch != ctx->global_epoch)) {
247 // Epoch can't change once we've locked the first slot.
248 CHECK_EQ(slot.sid, 0);
249 slot.mtx.Unlock();
250 return;
251 }
252 }
253 DPrintf("#%d: DoReset epoch=%lu\n", thr ? thr->tid : -1, epoch);
254 DoResetImpl(epoch);
255 for (auto& slot : ctx->slots) slot.mtx.Unlock();
256}
257
258void FlushShadowMemory() { DoReset(thr: nullptr, epoch: 0); }
259
260static TidSlot* FindSlotAndLock(ThreadState* thr)
261 SANITIZER_ACQUIRE(thr->slot->mtx) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
262 CHECK(!thr->slot);
263 TidSlot* slot = nullptr;
264 for (;;) {
265 uptr epoch;
266 {
267 Lock lock(&ctx->slot_mtx);
268 epoch = ctx->global_epoch;
269 if (slot) {
270 // This is an exhausted slot from the previous iteration.
271 if (ctx->slot_queue.Queued(e: slot))
272 ctx->slot_queue.Remove(e: slot);
273 thr->slot_locked = false;
274 slot->mtx.Unlock();
275 }
276 for (;;) {
277 slot = ctx->slot_queue.PopFront();
278 if (!slot)
279 break;
280 if (slot->epoch() != kEpochLast) {
281 ctx->slot_queue.PushBack(e: slot);
282 break;
283 }
284 }
285 }
286 if (!slot) {
287 DoReset(thr, epoch);
288 continue;
289 }
290 slot->mtx.Lock();
291 CHECK(!thr->slot_locked);
292 thr->slot_locked = true;
293 if (slot->thr) {
294 DPrintf("#%d: preempting sid=%d tid=%d\n", thr->tid, (u32)slot->sid,
295 slot->thr->tid);
296 slot->SetEpoch(slot->thr->fast_state.epoch());
297 slot->thr = nullptr;
298 }
299 if (slot->epoch() != kEpochLast)
300 return slot;
301 }
302}
303
304void SlotAttachAndLock(ThreadState* thr) {
305 TidSlot* slot = FindSlotAndLock(thr);
306 DPrintf("#%d: SlotAttach: slot=%u\n", thr->tid, static_cast<int>(slot->sid));
307 CHECK(!slot->thr);
308 CHECK(!thr->slot);
309 slot->thr = thr;
310 thr->slot = slot;
311 Epoch epoch = EpochInc(epoch: slot->epoch());
312 CHECK(!EpochOverflow(epoch));
313 slot->SetEpoch(epoch);
314 thr->fast_state.SetSid(slot->sid);
315 thr->fast_state.SetEpoch(epoch);
316 if (thr->slot_epoch != ctx->global_epoch) {
317 thr->slot_epoch = ctx->global_epoch;
318 thr->clock.Reset();
319#if !SANITIZER_GO
320 thr->last_sleep_stack_id = kInvalidStackID;
321 thr->last_sleep_clock.Reset();
322#endif
323 }
324 thr->clock.Set(sid: slot->sid, v: epoch);
325 slot->journal.PushBack(v: {.tid: thr->tid, .epoch: epoch});
326}
327
328static void SlotDetachImpl(ThreadState* thr, bool exiting) {
329 TidSlot* slot = thr->slot;
330 thr->slot = nullptr;
331 if (thr != slot->thr) {
332 slot = nullptr; // we don't own the slot anymore
333 if (thr->slot_epoch != ctx->global_epoch) {
334 TracePart* part = nullptr;
335 auto* trace = &thr->tctx->trace;
336 {
337 Lock l(&trace->mtx);
338 auto* parts = &trace->parts;
339 // The trace can be completely empty in an unlikely event
340 // the thread is preempted right after it acquired the slot
341 // in ThreadStart and did not trace any events yet.
342 CHECK_LE(parts->Size(), 1);
343 part = parts->PopFront();
344 thr->tctx->trace.local_head = nullptr;
345 atomic_store_relaxed(a: &thr->trace_pos, v: 0);
346 thr->trace_prev_pc = 0;
347 }
348 if (part) {
349 Lock l(&ctx->slot_mtx);
350 TracePartFree(part);
351 }
352 }
353 return;
354 }
355 CHECK(exiting || thr->fast_state.epoch() == kEpochLast);
356 slot->SetEpoch(thr->fast_state.epoch());
357 slot->thr = nullptr;
358}
359
360void SlotDetach(ThreadState* thr) {
361 Lock lock(&thr->slot->mtx);
362 SlotDetachImpl(thr, exiting: true);
363}
364
365void SlotLock(ThreadState* thr) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
366 DCHECK(!thr->slot_locked);
367#if SANITIZER_DEBUG
368 // Check these mutexes are not locked.
369 // We can call DoReset from SlotAttachAndLock, which will lock
370 // these mutexes, but it happens only every once in a while.
371 { ThreadRegistryLock lock(&ctx->thread_registry); }
372 { Lock lock(&ctx->slot_mtx); }
373#endif
374 TidSlot* slot = thr->slot;
375 slot->mtx.Lock();
376 thr->slot_locked = true;
377 if (LIKELY(thr == slot->thr && thr->fast_state.epoch() != kEpochLast))
378 return;
379 SlotDetachImpl(thr, exiting: false);
380 thr->slot_locked = false;
381 slot->mtx.Unlock();
382 SlotAttachAndLock(thr);
383}
384
385void SlotUnlock(ThreadState* thr) {
386 DCHECK(thr->slot_locked);
387 thr->slot_locked = false;
388 thr->slot->mtx.Unlock();
389}
390
391Context::Context()
392 : initialized(),
393 nreported(),
394 thread_registry([](Tid tid) -> ThreadContextBase* {
395 return new (Alloc(sz: sizeof(ThreadContext))) ThreadContext(tid);
396 }),
397 racy_mtx(MutexTypeRacy),
398 racy_stacks(),
399 fired_suppressions_mtx(MutexTypeFired),
400 slot_mtx(MutexTypeSlots),
401 resetting() {
402 fired_suppressions.reserve(new_size: 8);
403 for (uptr i = 0; i < ARRAY_SIZE(slots); i++) {
404 TidSlot* slot = &slots[i];
405 slot->sid = static_cast<Sid>(i);
406 slot_queue.PushBack(e: slot);
407 }
408 global_epoch = 1;
409}
410
411TidSlot::TidSlot() : mtx(MutexTypeSlot) {}
412
413// The objects are allocated in TLS, so one may rely on zero-initialization.
414ThreadState::ThreadState(Tid tid)
415 // Do not touch these, rely on zero initialization,
416 // they may be accessed before the ctor.
417 // ignore_reads_and_writes()
418 // ignore_interceptors()
419 : tid(tid) {
420 CHECK_EQ(reinterpret_cast<uptr>(this) % SANITIZER_CACHE_LINE_SIZE, 0);
421#if !SANITIZER_GO
422 // C/C++ uses fixed size shadow stack.
423 const int kInitStackSize = kShadowStackSize;
424 shadow_stack = static_cast<uptr*>(
425 MmapNoReserveOrDie(size: kInitStackSize * sizeof(uptr), mem_type: "shadow stack"));
426 SetShadowRegionHugePageMode(addr: reinterpret_cast<uptr>(shadow_stack),
427 length: kInitStackSize * sizeof(uptr));
428#else
429 // Go uses malloc-allocated shadow stack with dynamic size.
430 const int kInitStackSize = 8;
431 shadow_stack = static_cast<uptr*>(Alloc(kInitStackSize * sizeof(uptr)));
432#endif
433 shadow_stack_pos = shadow_stack;
434 shadow_stack_end = shadow_stack + kInitStackSize;
435}
436
437#if !SANITIZER_GO
438void MemoryProfiler(u64 uptime) {
439 if (ctx->memprof_fd == kInvalidFd)
440 return;
441 InternalMmapVector<char> buf(4096);
442 WriteMemoryProfile(buf: buf.data(), buf_size: buf.size(), uptime_ns: uptime);
443 WriteToFile(fd: ctx->memprof_fd, buff: buf.data(), buff_size: internal_strlen(s: buf.data()));
444}
445
446static bool InitializeMemoryProfiler() {
447 ctx->memprof_fd = kInvalidFd;
448 const char *fname = flags()->profile_memory;
449 if (!fname || !fname[0])
450 return false;
451 if (internal_strcmp(s1: fname, s2: "stdout") == 0) {
452 ctx->memprof_fd = 1;
453 } else if (internal_strcmp(s1: fname, s2: "stderr") == 0) {
454 ctx->memprof_fd = 2;
455 } else {
456 InternalScopedString filename;
457 filename.AppendF(format: "%s.%d", fname, (int)internal_getpid());
458 ctx->memprof_fd = OpenFile(filename: filename.data(), mode: WrOnly);
459 if (ctx->memprof_fd == kInvalidFd) {
460 Printf(format: "ThreadSanitizer: failed to open memory profile file '%s'\n",
461 filename.data());
462 return false;
463 }
464 }
465 MemoryProfiler(uptime: 0);
466 return true;
467}
468
469static void *BackgroundThread(void *arg) {
470 // This is a non-initialized non-user thread, nothing to see here.
471 // We don't use ScopedIgnoreInterceptors, because we want ignores to be
472 // enabled even when the thread function exits (e.g. during pthread thread
473 // shutdown code).
474 cur_thread_init()->ignore_interceptors++;
475 const u64 kMs2Ns = 1000 * 1000;
476 const u64 start = NanoTime();
477
478 u64 last_flush = start;
479 uptr last_rss = 0;
480 while (!atomic_load_relaxed(a: &ctx->stop_background_thread)) {
481 SleepForMillis(millis: 100);
482 u64 now = NanoTime();
483
484 // Flush memory if requested.
485 if (flags()->flush_memory_ms > 0) {
486 if (last_flush + flags()->flush_memory_ms * kMs2Ns < now) {
487 VReport(1, "ThreadSanitizer: periodic memory flush\n");
488 FlushShadowMemory();
489 now = last_flush = NanoTime();
490 }
491 }
492 if (flags()->memory_limit_mb > 0) {
493 uptr rss = GetRSS();
494 uptr limit = uptr(flags()->memory_limit_mb) << 20;
495 VReport(1,
496 "ThreadSanitizer: memory flush check"
497 " RSS=%llu LAST=%llu LIMIT=%llu\n",
498 (u64)rss >> 20, (u64)last_rss >> 20, (u64)limit >> 20);
499 if (2 * rss > limit + last_rss) {
500 VReport(1, "ThreadSanitizer: flushing memory due to RSS\n");
501 FlushShadowMemory();
502 rss = GetRSS();
503 now = NanoTime();
504 VReport(1, "ThreadSanitizer: memory flushed RSS=%llu\n",
505 (u64)rss >> 20);
506 }
507 last_rss = rss;
508 }
509
510 MemoryProfiler(uptime: now - start);
511
512 // Flush symbolizer cache if requested.
513 if (flags()->flush_symbolizer_ms > 0) {
514 u64 last = atomic_load(a: &ctx->last_symbolize_time_ns,
515 mo: memory_order_relaxed);
516 if (last != 0 && last + flags()->flush_symbolizer_ms * kMs2Ns < now) {
517 ScopedErrorReportLock l;
518 SymbolizeFlush();
519 atomic_store(a: &ctx->last_symbolize_time_ns, v: 0, mo: memory_order_relaxed);
520 }
521 }
522 }
523 return nullptr;
524}
525
526static void StartBackgroundThread() {
527 ctx->background_thread = internal_start_thread(func: &BackgroundThread, arg: 0);
528}
529
530#ifndef __mips__
531static void StopBackgroundThread() {
532 atomic_store(a: &ctx->stop_background_thread, v: 1, mo: memory_order_relaxed);
533 internal_join_thread(th: ctx->background_thread);
534 ctx->background_thread = 0;
535}
536#endif
537#endif
538
539void DontNeedShadowFor(uptr addr, uptr size) {
540 ReleaseMemoryPagesToOS(beg: reinterpret_cast<uptr>(MemToShadow(x: addr)),
541 end: reinterpret_cast<uptr>(MemToShadow(x: addr + size)));
542}
543
544#if !SANITIZER_GO
545// We call UnmapShadow before the actual munmap, at that point we don't yet
546// know if the provided address/size are sane. We can't call UnmapShadow
547// after the actual munmap becuase at that point the memory range can
548// already be reused for something else, so we can't rely on the munmap
549// return value to understand is the values are sane.
550// While calling munmap with insane values (non-canonical address, negative
551// size, etc) is an error, the kernel won't crash. We must also try to not
552// crash as the failure mode is very confusing (paging fault inside of the
553// runtime on some derived shadow address).
554static bool IsValidMmapRange(uptr addr, uptr size) {
555 if (size == 0)
556 return true;
557 if (static_cast<sptr>(size) < 0)
558 return false;
559 if (!IsAppMem(mem: addr) || !IsAppMem(mem: addr + size - 1))
560 return false;
561 // Check that if the start of the region belongs to one of app ranges,
562 // end of the region belongs to the same region.
563 const uptr ranges[][2] = {
564 {LoAppMemBeg(), LoAppMemEnd()},
565 {MidAppMemBeg(), MidAppMemEnd()},
566 {HiAppMemBeg(), HiAppMemEnd()},
567 };
568 for (auto range : ranges) {
569 if (addr >= range[0] && addr < range[1])
570 return addr + size <= range[1];
571 }
572 return false;
573}
574
575void UnmapShadow(ThreadState* thr, uptr addr, uptr size) {
576 if (size == 0 || !IsValidMmapRange(addr, size))
577 return;
578 // unmap shadow is related to semantic of mmap/munmap, so we
579 // should clear the whole shadow range, including the tail shadow
580 // while addr + size % kShadowCell != 0.
581 uptr rounded_size_shadow = RoundUp(p: addr + size, align: kShadowCell) - addr;
582 DontNeedShadowFor(addr, size: rounded_size_shadow);
583 ScopedGlobalProcessor sgp;
584 SlotLocker locker(thr, true);
585 uptr rounded_size_meta = RoundUp(p: addr + size, align: kMetaShadowCell) - addr;
586 ctx->metamap.ResetRange(proc: thr->proc(), p: addr, sz: rounded_size_meta, reset: true);
587}
588#endif
589
590void MapShadow(uptr addr, uptr size) {
591 // Although named MapShadow, this function's semantic is unrelated to
592 // UnmapShadow. This function currently only used for Go's lazy allocation
593 // of shadow, whose targets are program section (e.g., bss, data, etc.).
594 // Therefore, we can guarantee that the addr and size align to kShadowCell
595 // and kMetaShadowCell by the following assertions.
596 DCHECK_EQ(addr % kShadowCell, 0);
597 DCHECK_EQ(size % kShadowCell, 0);
598 DCHECK_EQ(addr % kMetaShadowCell, 0);
599 DCHECK_EQ(size % kMetaShadowCell, 0);
600
601 // Ensure thead registry lock held, so as to synchronize
602 // with DoReset, which also access the mapped_shadow_* ctxt fields.
603 ThreadRegistryLock lock0(&ctx->thread_registry);
604 static bool data_mapped = false;
605
606#if !SANITIZER_GO
607 // Global data is not 64K aligned, but there are no adjacent mappings,
608 // so we can get away with unaligned mapping.
609 // CHECK_EQ(addr, addr & ~((64 << 10) - 1)); // windows wants 64K alignment
610 const uptr kPageSize = GetPageSizeCached();
611 uptr shadow_begin = RoundDownTo(x: (uptr)MemToShadow(x: addr), boundary: kPageSize);
612 uptr shadow_end = RoundUpTo(size: (uptr)MemToShadow(x: addr + size), boundary: kPageSize);
613 if (!MmapFixedNoReserve(fixed_addr: shadow_begin, size: shadow_end - shadow_begin, name: "shadow"))
614 Die();
615#else
616 uptr shadow_begin = RoundDownTo((uptr)MemToShadow(addr), (64 << 10));
617 uptr shadow_end = RoundUpTo((uptr)MemToShadow(addr + size), (64 << 10));
618 VPrintf(2, "MapShadow for (0x%zx-0x%zx), begin/end: (0x%zx-0x%zx)\n",
619 addr, addr + size, shadow_begin, shadow_end);
620
621 if (!data_mapped) {
622 // First call maps data+bss.
623 if (!MmapFixedSuperNoReserve(shadow_begin, shadow_end - shadow_begin, "shadow"))
624 Die();
625 } else {
626 VPrintf(2, "ctx->mapped_shadow_{begin,end} = (0x%zx-0x%zx)\n",
627 ctx->mapped_shadow_begin, ctx->mapped_shadow_end);
628 // Second and subsequent calls map heap.
629 if (shadow_end <= ctx->mapped_shadow_end)
630 return;
631 if (!ctx->mapped_shadow_begin || ctx->mapped_shadow_begin > shadow_begin)
632 ctx->mapped_shadow_begin = shadow_begin;
633 if (shadow_begin < ctx->mapped_shadow_end)
634 shadow_begin = ctx->mapped_shadow_end;
635 VPrintf(2, "MapShadow begin/end = (0x%zx-0x%zx)\n",
636 shadow_begin, shadow_end);
637 if (!MmapFixedSuperNoReserve(shadow_begin, shadow_end - shadow_begin,
638 "shadow"))
639 Die();
640 ctx->mapped_shadow_end = shadow_end;
641 }
642#endif
643
644 // Meta shadow is 2:1, so tread carefully.
645 static uptr mapped_meta_end = 0;
646 uptr meta_begin = (uptr)MemToMeta(x: addr);
647 uptr meta_end = (uptr)MemToMeta(x: addr + size);
648 // Windows wants 64K alignment.
649 meta_begin = RoundDownTo(x: meta_begin, boundary: 64 << 10);
650 meta_end = RoundUpTo(size: meta_end, boundary: 64 << 10);
651 if (!data_mapped) {
652 // First call maps data+bss.
653 data_mapped = true;
654 if (!MmapFixedSuperNoReserve(fixed_addr: meta_begin, size: meta_end - meta_begin,
655 name: "meta shadow"))
656 Die();
657 } else {
658 // Mapping continuous heap.
659 CHECK_GT(meta_end, mapped_meta_end);
660 if (meta_begin < mapped_meta_end)
661 meta_begin = mapped_meta_end;
662 if (!MmapFixedSuperNoReserve(fixed_addr: meta_begin, size: meta_end - meta_begin,
663 name: "meta shadow"))
664 Die();
665 mapped_meta_end = meta_end;
666 }
667 VPrintf(2, "mapped meta shadow for (0x%zx-0x%zx) at (0x%zx-0x%zx)\n", addr,
668 addr + size, meta_begin, meta_end);
669}
670
671#if !SANITIZER_GO
672static void OnStackUnwind(const SignalContext &sig, const void *,
673 BufferedStackTrace *stack) {
674 stack->Unwind(pc: StackTrace::GetNextInstructionPc(pc: sig.pc), bp: sig.bp, context: sig.context,
675 request_fast: common_flags()->fast_unwind_on_fatal);
676}
677
678static void TsanOnDeadlySignal(int signo, void *siginfo, void *context) {
679 HandleDeadlySignal(siginfo, context, tid: GetTid(), unwind: &OnStackUnwind, unwind_context: nullptr);
680}
681#endif
682
683void CheckUnwind() {
684 // There is high probability that interceptors will check-fail as well,
685 // on the other hand there is no sense in processing interceptors
686 // since we are going to die soon.
687 ScopedIgnoreInterceptors ignore;
688#if !SANITIZER_GO
689 ThreadState* thr = cur_thread();
690 thr->nomalloc = false;
691 thr->ignore_sync++;
692 thr->ignore_reads_and_writes++;
693 atomic_store_relaxed(a: &thr->in_signal_handler, v: 0);
694#endif
695 PrintCurrentStack(pc: StackTrace::GetCurrentPc(),
696 fast: common_flags()->fast_unwind_on_fatal);
697}
698
699bool is_initialized;
700
701// Symbolization indirectly calls dl_iterate_phdr. If a CHECK() fails early on
702// (prior to the dl_iterate_phdr interceptor setup), resulting in an attempted
703// symbolization, it will segfault.
704// dl_iterate_phdr is not intercepted for Android.
705bool ready_to_symbolize = SANITIZER_ANDROID;
706
707void Initialize(ThreadState *thr) {
708 // Thread safe because done before all threads exist.
709 if (is_initialized)
710 return;
711 is_initialized = true;
712 // We are not ready to handle interceptors yet.
713 ScopedIgnoreInterceptors ignore;
714 SanitizerToolName = "ThreadSanitizer";
715 // Install tool-specific callbacks in sanitizer_common.
716 SetCheckUnwindCallback(CheckUnwind);
717
718 ctx = new(ctx_placeholder) Context;
719 const char *env_name = SANITIZER_GO ? "GORACE" : "TSAN_OPTIONS";
720 const char *options = GetEnv(name: env_name);
721 CacheBinaryName();
722 CheckASLR();
723 InitializeFlags(flags: &ctx->flags, env: options, env_option_name: env_name);
724 AvoidCVE_2016_2143();
725 __sanitizer::InitializePlatformEarly();
726 __tsan::InitializePlatformEarly();
727
728#if !SANITIZER_GO
729 InitializeAllocator();
730 ReplaceSystemMalloc();
731#endif
732 if (common_flags()->detect_deadlocks)
733 ctx->dd = DDetector::Create(flags: flags());
734 Processor *proc = ProcCreate();
735 ProcWire(proc, thr);
736 InitializeInterceptors();
737 InitializePlatform();
738 InitializeDynamicAnnotations();
739#if !SANITIZER_GO
740 InitializeShadowMemory();
741 InitializeAllocatorLate();
742 InstallDeadlySignalHandlers(handler: TsanOnDeadlySignal);
743#endif
744 // Setup correct file descriptor for error reports.
745 __sanitizer_set_report_path(path: common_flags()->log_path);
746 InitializeSuppressions();
747#if !SANITIZER_GO
748 InitializeLibIgnore();
749 Symbolizer::GetOrInit()->AddHooks(start_hook: EnterSymbolizer, end_hook: ExitSymbolizer);
750#endif
751
752 VPrintf(1, "***** Running under ThreadSanitizer v3 (pid %d) *****\n",
753 (int)internal_getpid());
754
755 // Initialize thread 0.
756 Tid tid = ThreadCreate(thr: nullptr, pc: 0, uid: 0, detached: true);
757 CHECK_EQ(tid, kMainTid);
758 ThreadStart(thr, tid, os_id: GetTid(), thread_type: ThreadType::Regular);
759#if TSAN_CONTAINS_UBSAN
760 __ubsan::InitAsPlugin();
761#endif
762
763#if !SANITIZER_GO
764 Symbolizer::LateInitialize();
765 if (InitializeMemoryProfiler() || flags()->force_background_thread)
766 MaybeSpawnBackgroundThread();
767#endif
768 ctx->initialized = true;
769
770 if (flags()->stop_on_start) {
771 Printf(format: "ThreadSanitizer is suspended at startup (pid %d)."
772 " Call __tsan_resume().\n",
773 (int)internal_getpid());
774 while (__tsan_resumed == 0) {}
775 }
776
777#if !SANITIZER_GO
778 AdaptiveDelay::Init();
779#endif
780
781 OnInitialize();
782}
783
784void MaybeSpawnBackgroundThread() {
785 // On MIPS, TSan initialization is run before
786 // __pthread_initialize_minimal_internal() is finished, so we can not spawn
787 // new threads.
788#if !SANITIZER_GO && !defined(__mips__)
789 static atomic_uint32_t bg_thread = {};
790 if (atomic_load(a: &bg_thread, mo: memory_order_relaxed) == 0 &&
791 atomic_exchange(a: &bg_thread, v: 1, mo: memory_order_relaxed) == 0) {
792 StartBackgroundThread();
793 SetSandboxingCallback(StopBackgroundThread);
794 }
795#endif
796}
797
798int Finalize(ThreadState *thr) {
799 bool failed = false;
800
801#if !SANITIZER_GO
802 if (common_flags()->print_module_map == 1)
803 DumpProcessMap();
804#endif
805
806 if (flags()->atexit_sleep_ms > 0 && ThreadCount(thr) > 1)
807 internal_usleep(useconds: u64(flags()->atexit_sleep_ms) * 1000);
808
809 {
810 // Wait for pending reports.
811 ScopedErrorReportLock lock;
812 }
813
814#if !SANITIZER_GO
815 if (Verbosity()) AllocatorPrintStats();
816#endif
817
818 ThreadFinalize(thr);
819
820 if (u32 nreported = atomic_load_relaxed(a: &ctx->nreported)) {
821 failed = true;
822#if !SANITIZER_GO
823 Printf(format: "ThreadSanitizer: reported %u warnings\n", nreported);
824#else
825 Printf("Found %u data race(s)\n", nreported);
826#endif
827 }
828
829 if (common_flags()->print_suppressions)
830 PrintMatchedSuppressions();
831
832 failed = OnFinalize(failed);
833
834 return failed ? common_flags()->exitcode : 0;
835}
836
837#if !SANITIZER_GO
838void ForkBefore(ThreadState* thr, uptr pc) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
839 VReport(2, "BeforeFork tid: %llu\n", GetTid());
840 GlobalProcessorLock();
841 // Detaching from the slot makes OnUserFree skip writing to the shadow.
842 // The slot will be locked so any attempts to use it will deadlock anyway.
843 SlotDetach(thr);
844 ScopedErrorReportLock::Lock();
845 for (auto& slot : ctx->slots) slot.mtx.Lock();
846 ctx->thread_registry.Lock();
847 ctx->slot_mtx.Lock();
848 AllocatorLockBeforeFork();
849 // Suppress all reports in the pthread_atfork callbacks.
850 // Reports may deadlock.
851 // We could ignore sync operations as well,
852 // but so far it's unclear if it will do more good or harm.
853 // Unnecessarily ignoring things can lead to false positives later.
854 thr->suppress_reports++;
855 // On OS X, REAL(fork) can call intercepted functions (OSSpinLockLock), and
856 // we'll assert in CheckNoLocks() unless we ignore interceptors.
857 // On OS X libSystem_atfork_prepare/parent/child callbacks are called
858 // after/before our callbacks and they call free.
859 thr->ignore_interceptors++;
860 // Disables memory write in OnUserAlloc/Free.
861 thr->ignore_reads_and_writes++;
862
863# if SANITIZER_APPLE
864 __tsan_test_only_on_fork();
865# endif
866}
867
868static void ForkAfter(ThreadState* thr,
869 bool child) SANITIZER_NO_THREAD_SAFETY_ANALYSIS {
870 thr->suppress_reports--; // Enabled in ForkBefore.
871 thr->ignore_interceptors--;
872 thr->ignore_reads_and_writes--;
873 AllocatorUnlockAfterFork(child);
874 ctx->slot_mtx.Unlock();
875 ctx->thread_registry.Unlock();
876 for (auto& slot : ctx->slots) slot.mtx.Unlock();
877 ScopedErrorReportLock::Unlock();
878 SlotAttachAndLock(thr);
879 SlotUnlock(thr);
880 GlobalProcessorUnlock();
881 VReport(2, "AfterFork tid: %llu\n", GetTid());
882}
883
884void ForkParentAfter(ThreadState* thr, uptr pc) { ForkAfter(thr, child: false); }
885
886void ForkChildAfter(ThreadState* thr, uptr pc, bool start_thread) {
887 ForkAfter(thr, child: true);
888 u32 nthread = ctx->thread_registry.OnFork(tid: thr->tid);
889 VPrintf(1,
890 "ThreadSanitizer: forked new process with pid %d,"
891 " parent had %d threads\n",
892 (int)internal_getpid(), (int)nthread);
893 if (nthread == 1) {
894 if (start_thread)
895 StartBackgroundThread();
896 } else {
897 // We've just forked a multi-threaded process. We cannot reasonably function
898 // after that (some mutexes may be locked before fork). So just enable
899 // ignores for everything in the hope that we will exec soon.
900 ctx->after_multithreaded_fork = true;
901 thr->ignore_interceptors++;
902 thr->suppress_reports++;
903 ThreadIgnoreBegin(thr, pc);
904 ThreadIgnoreSyncBegin(thr, pc);
905 }
906
907# if SANITIZER_APPLE && !SANITIZER_GO
908 // This flag can have inheritance disabled - we are the child so act
909 // accordingly
910 if (flags()->lock_during_write == kNoLockDuringWritesCurrentProcess)
911 flags()->lock_during_write = kLockDuringAllWrites;
912# endif
913}
914#endif
915
916#if SANITIZER_GO
917NOINLINE
918void GrowShadowStack(ThreadState *thr) {
919 const int sz = thr->shadow_stack_end - thr->shadow_stack;
920 const int newsz = 2 * sz;
921 auto *newstack = (uptr *)Alloc(newsz * sizeof(uptr));
922 internal_memcpy(newstack, thr->shadow_stack, sz * sizeof(uptr));
923 Free(thr->shadow_stack);
924 thr->shadow_stack = newstack;
925 thr->shadow_stack_pos = newstack + sz;
926 thr->shadow_stack_end = newstack + newsz;
927}
928#endif
929
930StackID CurrentStackId(ThreadState *thr, uptr pc) {
931#if !SANITIZER_GO
932 if (!thr->is_inited) // May happen during bootstrap.
933 return kInvalidStackID;
934#endif
935 if (pc != 0) {
936#if !SANITIZER_GO
937 DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end);
938#else
939 if (thr->shadow_stack_pos == thr->shadow_stack_end)
940 GrowShadowStack(thr);
941#endif
942 thr->shadow_stack_pos[0] = pc;
943 thr->shadow_stack_pos++;
944 }
945 StackID id = StackDepotPut(
946 stack: StackTrace(thr->shadow_stack, thr->shadow_stack_pos - thr->shadow_stack));
947 if (pc != 0)
948 thr->shadow_stack_pos--;
949 return id;
950}
951
952static bool TraceSkipGap(ThreadState* thr) {
953 Trace *trace = &thr->tctx->trace;
954 Event *pos = reinterpret_cast<Event *>(atomic_load_relaxed(a: &thr->trace_pos));
955 DCHECK_EQ(reinterpret_cast<uptr>(pos + 1) & TracePart::kAlignment, 0);
956 auto *part = trace->parts.Back();
957 DPrintf("#%d: TraceSwitchPart enter trace=%p parts=%p-%p pos=%p\n", thr->tid,
958 trace, trace->parts.Front(), part, pos);
959 if (!part)
960 return false;
961 // We can get here when we still have space in the current trace part.
962 // The fast-path check in TraceAcquire has false positives in the middle of
963 // the part. Check if we are indeed at the end of the current part or not,
964 // and fill any gaps with NopEvent's.
965 Event* end = &part->events[TracePart::kSize];
966 DCHECK_GE(pos, &part->events[0]);
967 DCHECK_LE(pos, end);
968 if (pos + 1 < end) {
969 if ((reinterpret_cast<uptr>(pos) & TracePart::kAlignment) ==
970 TracePart::kAlignment)
971 *pos++ = NopEvent;
972 *pos++ = NopEvent;
973 DCHECK_LE(pos + 2, end);
974 atomic_store_relaxed(a: &thr->trace_pos, v: reinterpret_cast<uptr>(pos));
975 return true;
976 }
977 // We are indeed at the end.
978 for (; pos < end; pos++) *pos = NopEvent;
979 return false;
980}
981
982NOINLINE
983void TraceSwitchPart(ThreadState* thr) {
984 if (TraceSkipGap(thr))
985 return;
986#if !SANITIZER_GO
987 if (ctx->after_multithreaded_fork) {
988 // We just need to survive till exec.
989 TracePart* part = thr->tctx->trace.parts.Back();
990 if (part) {
991 atomic_store_relaxed(a: &thr->trace_pos,
992 v: reinterpret_cast<uptr>(&part->events[0]));
993 return;
994 }
995 }
996#endif
997 TraceSwitchPartImpl(thr);
998}
999
1000void TraceSwitchPartImpl(ThreadState* thr) {
1001 SlotLocker locker(thr, true);
1002 Trace* trace = &thr->tctx->trace;
1003 TracePart* part = TracePartAlloc(thr);
1004 part->trace = trace;
1005 thr->trace_prev_pc = 0;
1006 TracePart* recycle = nullptr;
1007 // Keep roughly half of parts local to the thread
1008 // (not queued into the recycle queue).
1009 uptr local_parts = (Trace::kMinParts + flags()->history_size + 1) / 2;
1010 {
1011 Lock lock(&trace->mtx);
1012 if (trace->parts.Empty())
1013 trace->local_head = part;
1014 if (trace->parts.Size() >= local_parts) {
1015 recycle = trace->local_head;
1016 trace->local_head = trace->parts.Next(e: recycle);
1017 }
1018 trace->parts.PushBack(e: part);
1019 atomic_store_relaxed(a: &thr->trace_pos,
1020 v: reinterpret_cast<uptr>(&part->events[0]));
1021 }
1022 // Make this part self-sufficient by restoring the current stack
1023 // and mutex set in the beginning of the trace.
1024 TraceTime(thr);
1025 {
1026 // Pathologically large stacks may not fit into the part.
1027 // In these cases we log only fixed number of top frames.
1028 const uptr kMaxFrames = 1000;
1029 // Check that kMaxFrames won't consume the whole part.
1030 static_assert(kMaxFrames < TracePart::kSize / 2, "kMaxFrames is too big");
1031 uptr* pos = Max(a: &thr->shadow_stack[0], b: thr->shadow_stack_pos - kMaxFrames);
1032 for (; pos < thr->shadow_stack_pos; pos++) {
1033 if (TryTraceFunc(thr, pc: *pos))
1034 continue;
1035 CHECK(TraceSkipGap(thr));
1036 CHECK(TryTraceFunc(thr, *pos));
1037 }
1038 }
1039 for (uptr i = 0; i < thr->mset.Size(); i++) {
1040 MutexSet::Desc d = thr->mset.Get(i);
1041 for (uptr i = 0; i < d.count; i++)
1042 TraceMutexLock(thr, type: d.write ? EventType::kLock : EventType::kRLock, pc: 0,
1043 addr: d.addr, stk: d.stack_id);
1044 }
1045 // Callers of TraceSwitchPart expect that TraceAcquire will always succeed
1046 // after the call. It's possible that TryTraceFunc/TraceMutexLock above
1047 // filled the trace part exactly up to the TracePart::kAlignment gap
1048 // and the next TraceAcquire won't succeed. Skip the gap to avoid that.
1049 EventFunc *ev;
1050 if (!TraceAcquire(thr, ev: &ev)) {
1051 CHECK(TraceSkipGap(thr));
1052 CHECK(TraceAcquire(thr, &ev));
1053 }
1054 {
1055 Lock lock(&ctx->slot_mtx);
1056 // There is a small chance that the slot may be not queued at this point.
1057 // This can happen if the slot has kEpochLast epoch and another thread
1058 // in FindSlotAndLock discovered that it's exhausted and removed it from
1059 // the slot queue. kEpochLast can happen in 2 cases: (1) if TraceSwitchPart
1060 // was called with the slot locked and epoch already at kEpochLast,
1061 // or (2) if we've acquired a new slot in SlotLock in the beginning
1062 // of the function and the slot was at kEpochLast - 1, so after increment
1063 // in SlotAttachAndLock it become kEpochLast.
1064 if (ctx->slot_queue.Queued(e: thr->slot)) {
1065 ctx->slot_queue.Remove(e: thr->slot);
1066 ctx->slot_queue.PushBack(e: thr->slot);
1067 }
1068 if (recycle)
1069 ctx->trace_part_recycle.PushBack(e: recycle);
1070 }
1071 DPrintf("#%d: TraceSwitchPart exit parts=%p-%p pos=0x%zx\n", thr->tid,
1072 trace->parts.Front(), trace->parts.Back(),
1073 atomic_load_relaxed(&thr->trace_pos));
1074}
1075
1076void ThreadIgnoreBegin(ThreadState* thr, uptr pc) {
1077 DPrintf("#%d: ThreadIgnoreBegin\n", thr->tid);
1078 thr->ignore_reads_and_writes++;
1079 CHECK_GT(thr->ignore_reads_and_writes, 0);
1080 thr->fast_state.SetIgnoreBit();
1081#if !SANITIZER_GO
1082 if (pc && !ctx->after_multithreaded_fork)
1083 thr->mop_ignore_set.Add(stack_id: CurrentStackId(thr, pc));
1084#endif
1085}
1086
1087void ThreadIgnoreEnd(ThreadState *thr) {
1088 DPrintf("#%d: ThreadIgnoreEnd\n", thr->tid);
1089 CHECK_GT(thr->ignore_reads_and_writes, 0);
1090 thr->ignore_reads_and_writes--;
1091 if (thr->ignore_reads_and_writes == 0) {
1092 thr->fast_state.ClearIgnoreBit();
1093#if !SANITIZER_GO
1094 thr->mop_ignore_set.Reset();
1095#endif
1096 }
1097}
1098
1099#if !SANITIZER_GO
1100extern "C" SANITIZER_INTERFACE_ATTRIBUTE
1101uptr __tsan_testonly_shadow_stack_current_size() {
1102 ThreadState *thr = cur_thread();
1103 return thr->shadow_stack_pos - thr->shadow_stack;
1104}
1105#endif
1106
1107void ThreadIgnoreSyncBegin(ThreadState *thr, uptr pc) {
1108 DPrintf("#%d: ThreadIgnoreSyncBegin\n", thr->tid);
1109 thr->ignore_sync++;
1110 CHECK_GT(thr->ignore_sync, 0);
1111#if !SANITIZER_GO
1112 if (pc && !ctx->after_multithreaded_fork)
1113 thr->sync_ignore_set.Add(stack_id: CurrentStackId(thr, pc));
1114#endif
1115}
1116
1117void ThreadIgnoreSyncEnd(ThreadState *thr) {
1118 DPrintf("#%d: ThreadIgnoreSyncEnd\n", thr->tid);
1119 CHECK_GT(thr->ignore_sync, 0);
1120 thr->ignore_sync--;
1121#if !SANITIZER_GO
1122 if (thr->ignore_sync == 0)
1123 thr->sync_ignore_set.Reset();
1124#endif
1125}
1126
1127bool MD5Hash::operator==(const MD5Hash &other) const {
1128 return hash[0] == other.hash[0] && hash[1] == other.hash[1];
1129}
1130
1131#if SANITIZER_DEBUG
1132void build_consistency_debug() {}
1133#else
1134void build_consistency_release() {}
1135#endif
1136} // namespace __tsan
1137
1138#if SANITIZER_CHECK_DEADLOCKS
1139namespace __sanitizer {
1140using namespace __tsan;
1141MutexMeta mutex_meta[] = {
1142 {MutexInvalid, "Invalid", {}},
1143 {MutexThreadRegistry, "ThreadRegistry", {MutexTypeSlots, MutexTypeTrace}},
1144 {MutexTypeSyncVar, "SyncVar", {MutexTypeTrace}},
1145 {MutexTypeAnnotations, "Annotations", {}},
1146 {MutexTypeAtExit, "AtExit", {}},
1147 {MutexTypeFired, "Fired", {MutexLeaf}},
1148 {MutexTypeRacy, "Racy", {MutexLeaf}},
1149 {MutexTypeGlobalProc, "GlobalProc", {MutexTypeSlot, MutexTypeSlots}},
1150 {MutexTypeInternalAlloc, "InternalAlloc", {MutexLeaf}},
1151 {MutexTypeTrace, "Trace", {}},
1152 {MutexTypeSlot,
1153 "Slot",
1154 {MutexMulti, MutexTypeTrace, MutexTypeSyncVar, MutexThreadRegistry,
1155 MutexTypeSlots}},
1156 {MutexTypeSlots, "Slots", {MutexTypeTrace}},
1157 {},
1158};
1159
1160void PrintMutexPC(uptr pc) { StackTrace(&pc, 1).Print(); }
1161
1162} // namespace __sanitizer
1163#endif
1164