//===-- sanitizer_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 shared between AddressSanitizer and ThreadSanitizer // run-time libraries. //===----------------------------------------------------------------------===// #include "sanitizer_mutex.h" #include "sanitizer_common.h" namespace __sanitizer { void StaticSpinMutex::LockSlow() { for (int i = 0;; i++) { if (i < 100) proc_yield(1); else internal_sched_yield(); if (atomic_load(&state_, memory_order_relaxed) == 0 && atomic_exchange(&state_, 1, memory_order_acquire) == 0) return; } } void Semaphore::Wait() { u32 count = atomic_load(&state_, memory_order_relaxed); for (;;) { if (count == 0) { FutexWait(&state_, 0); count = atomic_load(&state_, memory_order_relaxed); continue; } if (atomic_compare_exchange_weak(&state_, &count, count - 1, memory_order_acquire)) break; } } void Semaphore::Post(u32 count) { CHECK_NE(count, 0); atomic_fetch_add(&state_, count, memory_order_release); FutexWake(&state_, count); } #if SANITIZER_CHECK_DEADLOCKS // An empty mutex meta table, it effectively disables deadlock detection. // Each tool can override the table to define own mutex hierarchy and // enable deadlock detection. // The table defines a static mutex type hierarchy (what mutex types can be locked // under what mutex types). This table is checked to be acyclic and then // actual mutex lock/unlock operations are checked to adhere to this hierarchy. // The checking happens on mutex types rather than on individual mutex instances // because doing it on mutex instances will both significantly complicate // the implementation, worsen performance and memory overhead and is mostly // unnecessary (we almost never lock multiple mutexes of the same type recursively). static constexpr int kMutexTypeMax = 20; SANITIZER_WEAK_ATTRIBUTE MutexMeta mutex_meta[kMutexTypeMax] = {}; SANITIZER_WEAK_ATTRIBUTE void PrintMutexPC(uptr pc) {} static StaticSpinMutex mutex_meta_mtx; static int mutex_type_count = -1; // Adjacency matrix of what mutexes can be locked under what mutexes. static bool mutex_can_lock[kMutexTypeMax][kMutexTypeMax]; // Mutex types with MutexMulti mark. static bool mutex_multi[kMutexTypeMax]; void DebugMutexInit() { // Build adjacency matrix. bool leaf[kMutexTypeMax]; internal_memset(&leaf, 0, sizeof(leaf)); int cnt[kMutexTypeMax]; internal_memset(&cnt, 0, sizeof(cnt)); for (int t = 0; t < kMutexTypeMax; t++) { mutex_type_count = t; if (!mutex_meta[t].name) break; CHECK_EQ(t, mutex_meta[t].type); for (uptr j = 0; j < ARRAY_SIZE(mutex_meta[t].can_lock); j++) { MutexType z = mutex_meta[t].can_lock[j]; if (z == MutexInvalid) break; if (z == MutexLeaf) { CHECK(!leaf[t]); leaf[t] = true; continue; } if (z == MutexMulti) { mutex_multi[t] = true; continue; } CHECK_LT(z, kMutexTypeMax); CHECK(!mutex_can_lock[t][z]); mutex_can_lock[t][z] = true; cnt[t]++; } } // Indicates the array is not properly terminated. CHECK_LT(mutex_type_count, kMutexTypeMax); // Add leaf mutexes. for (int t = 0; t < mutex_type_count; t++) { if (!leaf[t]) continue; CHECK_EQ(cnt[t], 0); for (int z = 0; z < mutex_type_count; z++) { if (z == MutexInvalid || t == z || leaf[z]) continue; CHECK(!mutex_can_lock[z][t]); mutex_can_lock[z][t] = true; } } // Build the transitive closure and check that the graphs is acyclic. u32 trans[kMutexTypeMax]; static_assert(sizeof(trans[0]) * 8 >= kMutexTypeMax, "kMutexTypeMax does not fit into u32, switch to u64"); internal_memset(&trans, 0, sizeof(trans)); for (int i = 0; i < mutex_type_count; i++) { for (int j = 0; j < mutex_type_count; j++) if (mutex_can_lock[i][j]) trans[i] |= 1 << j; } for (int k = 0; k < mutex_type_count; k++) { for (int i = 0; i < mutex_type_count; i++) { if (trans[i] & (1 << k)) trans[i] |= trans[k]; } } for (int i = 0; i < mutex_type_count; i++) { if (trans[i] & (1 << i)) { Printf("Mutex %s participates in a cycle\n", mutex_meta[i].name); Die(); } } } struct InternalDeadlockDetector { struct LockDesc { u64 seq; uptr pc; int recursion; }; int initialized; u64 sequence; LockDesc locked[kMutexTypeMax]; void Lock(MutexType type, uptr pc) { if (!Initialize(type)) return; CHECK_LT(type, mutex_type_count); // Find the last locked mutex type. // This is the type we will use for hierarchy checks. u64 max_seq = 0; MutexType max_idx = MutexInvalid; for (int i = 0; i != mutex_type_count; i++) { if (locked[i].seq == 0) continue; CHECK_NE(locked[i].seq, max_seq); if (max_seq < locked[i].seq) { max_seq = locked[i].seq; max_idx = (MutexType)i; } } if (max_idx == type && mutex_multi[type]) { // Recursive lock of the same type. CHECK_EQ(locked[type].seq, max_seq); CHECK(locked[type].pc); locked[type].recursion++; return; } if (max_idx != MutexInvalid && !mutex_can_lock[max_idx][type]) { Printf("%s: internal deadlock: can't lock %s under %s mutex\n", SanitizerToolName, mutex_meta[type].name, mutex_meta[max_idx].name); PrintMutexPC(locked[max_idx].pc); CHECK(0); } locked[type].seq = ++sequence; locked[type].pc = pc; locked[type].recursion = 1; } void Unlock(MutexType type) { if (!Initialize(type)) return; CHECK_LT(type, mutex_type_count); CHECK(locked[type].seq); CHECK_GT(locked[type].recursion, 0); if (--locked[type].recursion) return; locked[type].seq = 0; locked[type].pc = 0; } void CheckNoLocks() { for (int i = 0; i < mutex_type_count; i++) CHECK_EQ(locked[i].recursion, 0); } bool Initialize(MutexType type) { if (type == MutexUnchecked || type == MutexInvalid) return false; CHECK_GT(type, MutexInvalid); if (initialized != 0) return initialized > 0; initialized = -1; SpinMutexLock lock(&mutex_meta_mtx); if (mutex_type_count < 0) DebugMutexInit(); initialized = mutex_type_count ? 1 : -1; return initialized > 0; } }; static THREADLOCAL InternalDeadlockDetector deadlock_detector; void CheckedMutex::LockImpl(uptr pc) { deadlock_detector.Lock(type_, pc); } void CheckedMutex::UnlockImpl() { deadlock_detector.Unlock(type_); } void CheckedMutex::CheckNoLocksImpl() { deadlock_detector.CheckNoLocks(); } #endif } // namespace __sanitizer