//===-- tsan_rtl_mutex.cpp ------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file is a part of ThreadSanitizer (TSan), a race detector. // //===----------------------------------------------------------------------===// #include #include #include "tsan_rtl.h" #include "tsan_flags.h" #include "tsan_sync.h" #include "tsan_report.h" #include "tsan_symbolize.h" #include "tsan_platform.h" namespace __tsan { void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r); void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr, FastState last_lock, StackID creation_stack_id); struct Callback final : public DDCallback { ThreadState *thr; uptr pc; Callback(ThreadState *thr, uptr pc) : thr(thr) , pc(pc) { DDCallback::pt = thr->proc()->dd_pt; DDCallback::lt = thr->dd_lt; } StackID Unwind() override { return CurrentStackId(thr, pc); } int UniqueTid() override { return thr->tid; } }; void DDMutexInit(ThreadState *thr, uptr pc, SyncVar *s) { Callback cb(thr, pc); ctx->dd->MutexInit(&cb, &s->dd); s->dd.ctx = s->addr; } static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ, uptr addr, StackID creation_stack_id) { // In Go, these misuses are either impossible, or detected by std lib, // or false positives (e.g. unlock in a different thread). if (SANITIZER_GO) return; if (!ShouldReport(thr, typ)) return; ThreadRegistryLock l(&ctx->thread_registry); ScopedReport rep(typ); rep.AddMutex(addr, creation_stack_id); VarSizeStackTrace trace; ObtainCurrentStack(thr, pc, &trace); rep.AddStack(trace, true); rep.AddLocation(addr, 1); OutputReport(thr, rep); } static void RecordMutexLock(ThreadState *thr, uptr pc, uptr addr, StackID stack_id, bool write) { auto typ = write ? EventType::kLock : EventType::kRLock; // Note: it's important to trace before modifying mutex set // because tracing can switch trace part and we write the current // mutex set in the beginning of each part. // If we do it in the opposite order, we will write already reduced // mutex set in the beginning of the part and then trace unlock again. TraceMutexLock(thr, typ, pc, addr, stack_id); thr->mset.AddAddr(addr, stack_id, write); } static void RecordMutexUnlock(ThreadState *thr, uptr addr) { // See the comment in RecordMutexLock re order of operations. TraceMutexUnlock(thr, addr); thr->mset.DelAddr(addr); } void MutexCreate(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexCreate %zx flagz=0x%x\n", thr->tid, addr, flagz); if (!(flagz & MutexFlagLinkerInit) && pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessWrite); SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); s->SetFlags(flagz & MutexCreationFlagMask); // Save stack in the case the sync object was created before as atomic. if (!SANITIZER_GO && s->creation_stack_id == kInvalidStackID) s->creation_stack_id = CurrentStackId(thr, pc); } void MutexDestroy(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexDestroy %zx\n", thr->tid, addr); bool unlock_locked = false; StackID creation_stack_id; FastState last_lock; { auto s = ctx->metamap.GetSyncIfExists(addr); if (!s) return; SlotLocker locker(thr); { Lock lock(&s->mtx); creation_stack_id = s->creation_stack_id; last_lock = s->last_lock; if ((flagz & MutexFlagLinkerInit) || s->IsFlagSet(MutexFlagLinkerInit) || ((flagz & MutexFlagNotStatic) && !s->IsFlagSet(MutexFlagNotStatic))) { // Destroy is no-op for linker-initialized mutexes. return; } if (common_flags()->detect_deadlocks) { Callback cb(thr, pc); ctx->dd->MutexDestroy(&cb, &s->dd); ctx->dd->MutexInit(&cb, &s->dd); } if (flags()->report_destroy_locked && s->owner_tid != kInvalidTid && !s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); unlock_locked = true; } s->Reset(); } // Imitate a memory write to catch unlock-destroy races. if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessWrite | kAccessFree); } if (unlock_locked && ShouldReport(thr, ReportTypeMutexDestroyLocked)) ReportDestroyLocked(thr, pc, addr, last_lock, creation_stack_id); thr->mset.DelAddr(addr, true); // s will be destroyed and freed in MetaMap::FreeBlock. } void MutexPreLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexPreLock %zx flagz=0x%x\n", thr->tid, addr, flagz); if (flagz & MutexFlagTryLock) return; if (!common_flags()->detect_deadlocks) return; Callback cb(thr, pc); { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); ReadLock lock(&s->mtx); s->UpdateFlags(flagz); if (s->owner_tid != thr->tid) ctx->dd->MutexBeforeLock(&cb, &s->dd, true); } ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } void MutexPostLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz, int rec) { DPrintf("#%d: MutexPostLock %zx flag=0x%x rec=%d\n", thr->tid, addr, flagz, rec); if (flagz & MutexFlagRecursiveLock) CHECK_GT(rec, 0); else rec = 1; if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); bool report_double_lock = false; bool pre_lock = false; bool first = false; StackID creation_stack_id = kInvalidStackID; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); creation_stack_id = s->creation_stack_id; RecordMutexLock(thr, pc, addr, creation_stack_id, true); { Lock lock(&s->mtx); first = s->recursion == 0; s->UpdateFlags(flagz); if (s->owner_tid == kInvalidTid) { CHECK_EQ(s->recursion, 0); s->owner_tid = thr->tid; s->last_lock = thr->fast_state; } else if (s->owner_tid == thr->tid) { CHECK_GT(s->recursion, 0); } else if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); report_double_lock = true; } s->recursion += rec; if (first) { if (!thr->ignore_sync) { thr->clock.Acquire(s->clock); thr->clock.Acquire(s->read_clock); } } if (first && common_flags()->detect_deadlocks) { pre_lock = (flagz & MutexFlagDoPreLockOnPostLock) && !(flagz & MutexFlagTryLock); Callback cb(thr, pc); if (pre_lock) ctx->dd->MutexBeforeLock(&cb, &s->dd, true); ctx->dd->MutexAfterLock(&cb, &s->dd, true, flagz & MutexFlagTryLock); } } } if (report_double_lock) ReportMutexMisuse(thr, pc, ReportTypeMutexDoubleLock, addr, creation_stack_id); if (first && pre_lock && common_flags()->detect_deadlocks) { Callback cb(thr, pc); ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } } int MutexUnlock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexUnlock %zx flagz=0x%x\n", thr->tid, addr, flagz); if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); StackID creation_stack_id; RecordMutexUnlock(thr, addr); bool report_bad_unlock = false; int rec = 0; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); bool released = false; { Lock lock(&s->mtx); creation_stack_id = s->creation_stack_id; if (!SANITIZER_GO && (s->recursion == 0 || s->owner_tid != thr->tid)) { if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); report_bad_unlock = true; } } else { rec = (flagz & MutexFlagRecursiveUnlock) ? s->recursion : 1; s->recursion -= rec; if (s->recursion == 0) { s->owner_tid = kInvalidTid; if (!thr->ignore_sync) { thr->clock.ReleaseStore(&s->clock); released = true; } } } if (common_flags()->detect_deadlocks && s->recursion == 0 && !report_bad_unlock) { Callback cb(thr, pc); ctx->dd->MutexBeforeUnlock(&cb, &s->dd, true); } } if (released) IncrementEpoch(thr); } if (report_bad_unlock) ReportMutexMisuse(thr, pc, ReportTypeMutexBadUnlock, addr, creation_stack_id); if (common_flags()->detect_deadlocks && !report_bad_unlock) { Callback cb(thr, pc); ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } return rec; } void MutexPreReadLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexPreReadLock %zx flagz=0x%x\n", thr->tid, addr, flagz); if ((flagz & MutexFlagTryLock) || !common_flags()->detect_deadlocks) return; Callback cb(thr, pc); { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); ReadLock lock(&s->mtx); s->UpdateFlags(flagz); ctx->dd->MutexBeforeLock(&cb, &s->dd, false); } ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } void MutexPostReadLock(ThreadState *thr, uptr pc, uptr addr, u32 flagz) { DPrintf("#%d: MutexPostReadLock %zx flagz=0x%x\n", thr->tid, addr, flagz); if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); bool report_bad_lock = false; bool pre_lock = false; StackID creation_stack_id = kInvalidStackID; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); creation_stack_id = s->creation_stack_id; RecordMutexLock(thr, pc, addr, creation_stack_id, false); { ReadLock lock(&s->mtx); s->UpdateFlags(flagz); if (s->owner_tid != kInvalidTid) { if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); report_bad_lock = true; } } if (!thr->ignore_sync) thr->clock.Acquire(s->clock); s->last_lock = thr->fast_state; if (common_flags()->detect_deadlocks) { pre_lock = (flagz & MutexFlagDoPreLockOnPostLock) && !(flagz & MutexFlagTryLock); Callback cb(thr, pc); if (pre_lock) ctx->dd->MutexBeforeLock(&cb, &s->dd, false); ctx->dd->MutexAfterLock(&cb, &s->dd, false, flagz & MutexFlagTryLock); } } } if (report_bad_lock) ReportMutexMisuse(thr, pc, ReportTypeMutexBadReadLock, addr, creation_stack_id); if (pre_lock && common_flags()->detect_deadlocks) { Callback cb(thr, pc); ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } } void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexReadUnlock %zx\n", thr->tid, addr); if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); RecordMutexUnlock(thr, addr); StackID creation_stack_id; bool report_bad_unlock = false; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); bool released = false; { Lock lock(&s->mtx); creation_stack_id = s->creation_stack_id; if (s->owner_tid != kInvalidTid) { if (flags()->report_mutex_bugs && !s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); report_bad_unlock = true; } } if (!thr->ignore_sync) { thr->clock.Release(&s->read_clock); released = true; } if (common_flags()->detect_deadlocks && s->recursion == 0) { Callback cb(thr, pc); ctx->dd->MutexBeforeUnlock(&cb, &s->dd, false); } } if (released) IncrementEpoch(thr); } if (report_bad_unlock) ReportMutexMisuse(thr, pc, ReportTypeMutexBadReadUnlock, addr, creation_stack_id); if (common_flags()->detect_deadlocks) { Callback cb(thr, pc); ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } } void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr); if (pc && IsAppMem(addr)) MemoryAccess(thr, pc, addr, 1, kAccessRead | kAccessAtomic); RecordMutexUnlock(thr, addr); StackID creation_stack_id; bool report_bad_unlock = false; bool write = true; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); bool released = false; { Lock lock(&s->mtx); creation_stack_id = s->creation_stack_id; if (s->owner_tid == kInvalidTid) { // Seems to be read unlock. write = false; if (!thr->ignore_sync) { thr->clock.Release(&s->read_clock); released = true; } } else if (s->owner_tid == thr->tid) { // Seems to be write unlock. CHECK_GT(s->recursion, 0); s->recursion--; if (s->recursion == 0) { s->owner_tid = kInvalidTid; if (!thr->ignore_sync) { thr->clock.ReleaseStore(&s->clock); released = true; } } } else if (!s->IsFlagSet(MutexFlagBroken)) { s->SetFlags(MutexFlagBroken); report_bad_unlock = true; } if (common_flags()->detect_deadlocks && s->recursion == 0) { Callback cb(thr, pc); ctx->dd->MutexBeforeUnlock(&cb, &s->dd, write); } } if (released) IncrementEpoch(thr); } if (report_bad_unlock) ReportMutexMisuse(thr, pc, ReportTypeMutexBadUnlock, addr, creation_stack_id); if (common_flags()->detect_deadlocks) { Callback cb(thr, pc); ReportDeadlock(thr, pc, ctx->dd->GetReport(&cb)); } } void MutexRepair(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexRepair %zx\n", thr->tid, addr); SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); Lock lock(&s->mtx); s->owner_tid = kInvalidTid; s->recursion = 0; } void MutexInvalidAccess(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexInvalidAccess %zx\n", thr->tid, addr); StackID creation_stack_id = kInvalidStackID; { SlotLocker locker(thr); auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, true); if (s) creation_stack_id = s->creation_stack_id; } ReportMutexMisuse(thr, pc, ReportTypeMutexInvalidAccess, addr, creation_stack_id); } void Acquire(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: Acquire %zx\n", thr->tid, addr); if (thr->ignore_sync) return; auto s = ctx->metamap.GetSyncIfExists(addr); if (!s) return; SlotLocker locker(thr); if (!s->clock) return; ReadLock lock(&s->mtx); thr->clock.Acquire(s->clock); } void AcquireGlobal(ThreadState *thr) { DPrintf("#%d: AcquireGlobal\n", thr->tid); if (thr->ignore_sync) return; SlotLocker locker(thr); for (auto &slot : ctx->slots) thr->clock.Set(slot.sid, slot.epoch()); } void Release(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: Release %zx\n", thr->tid, addr); if (thr->ignore_sync) return; SlotLocker locker(thr); { auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false); Lock lock(&s->mtx); thr->clock.Release(&s->clock); } IncrementEpoch(thr); } void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr); if (thr->ignore_sync) return; SlotLocker locker(thr); { auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false); Lock lock(&s->mtx); thr->clock.ReleaseStore(&s->clock); } IncrementEpoch(thr); } void ReleaseStoreAcquire(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: ReleaseStoreAcquire %zx\n", thr->tid, addr); if (thr->ignore_sync) return; SlotLocker locker(thr); { auto s = ctx->metamap.GetSyncOrCreate(thr, pc, addr, false); Lock lock(&s->mtx); thr->clock.ReleaseStoreAcquire(&s->clock); } IncrementEpoch(thr); } void IncrementEpoch(ThreadState *thr) { DCHECK(!thr->ignore_sync); DCHECK(thr->slot_locked); Epoch epoch = EpochInc(thr->fast_state.epoch()); if (!EpochOverflow(epoch)) { Sid sid = thr->fast_state.sid(); thr->clock.Set(sid, epoch); thr->fast_state.SetEpoch(epoch); thr->slot->SetEpoch(epoch); TraceTime(thr); } } #if !SANITIZER_GO void AfterSleep(ThreadState *thr, uptr pc) { DPrintf("#%d: AfterSleep\n", thr->tid); if (thr->ignore_sync) return; thr->last_sleep_stack_id = CurrentStackId(thr, pc); thr->last_sleep_clock.Reset(); SlotLocker locker(thr); for (auto &slot : ctx->slots) thr->last_sleep_clock.Set(slot.sid, slot.epoch()); } #endif void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) { if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock)) return; ThreadRegistryLock l(&ctx->thread_registry); ScopedReport rep(ReportTypeDeadlock); for (int i = 0; i < r->n; i++) { rep.AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]); rep.AddUniqueTid((int)r->loop[i].thr_ctx); rep.AddThread((int)r->loop[i].thr_ctx); } uptr dummy_pc = 0x42; for (int i = 0; i < r->n; i++) { for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) { u32 stk = r->loop[i].stk[j]; if (stk && stk != kInvalidStackID) { rep.AddStack(StackDepotGet(stk), true); } else { // Sometimes we fail to extract the stack trace (FIXME: investigate), // but we should still produce some stack trace in the report. rep.AddStack(StackTrace(&dummy_pc, 1), true); } } } OutputReport(thr, rep); } void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr, FastState last_lock, StackID creation_stack_id) { // We need to lock the slot during RestoreStack because it protects // the slot journal. Lock slot_lock(&ctx->slots[static_cast(last_lock.sid())].mtx); ThreadRegistryLock l0(&ctx->thread_registry); Lock slots_lock(&ctx->slot_mtx); ScopedReport rep(ReportTypeMutexDestroyLocked); rep.AddMutex(addr, creation_stack_id); VarSizeStackTrace trace; ObtainCurrentStack(thr, pc, &trace); rep.AddStack(trace, true); Tid tid; DynamicMutexSet mset; uptr tag; if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(), addr, 0, kAccessWrite, &tid, &trace, mset, &tag)) return; rep.AddStack(trace, true); rep.AddLocation(addr, 1); OutputReport(thr, rep); } } // namespace __tsan