//===-- tsan_sync.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 "sanitizer_common/sanitizer_placement_new.h" #include "tsan_sync.h" #include "tsan_rtl.h" #include "tsan_mman.h" namespace __tsan { void DDMutexInit(ThreadState *thr, uptr pc, SyncVar *s); SyncVar::SyncVar() : mtx(MutexTypeSyncVar) { Reset(); } void SyncVar::Init(ThreadState *thr, uptr pc, uptr addr, bool save_stack) { Reset(); this->addr = addr; next = 0; if (save_stack && !SANITIZER_GO) // Go does not use them creation_stack_id = CurrentStackId(thr, pc); if (common_flags()->detect_deadlocks) DDMutexInit(thr, pc, this); } void SyncVar::Reset() { CHECK(!ctx->resetting); creation_stack_id = kInvalidStackID; owner_tid = kInvalidTid; last_lock.Reset(); recursion = 0; atomic_store_relaxed(&flags, 0); Free(clock); Free(read_clock); } MetaMap::MetaMap() : block_alloc_("heap block allocator"), sync_alloc_("sync allocator") {} void MetaMap::AllocBlock(ThreadState *thr, uptr pc, uptr p, uptr sz) { u32 idx = block_alloc_.Alloc(&thr->proc()->block_cache); MBlock *b = block_alloc_.Map(idx); b->siz = sz; b->tag = 0; b->tid = thr->tid; b->stk = CurrentStackId(thr, pc); u32 *meta = MemToMeta(p); DCHECK_EQ(*meta, 0); *meta = idx | kFlagBlock; } uptr MetaMap::FreeBlock(Processor *proc, uptr p, bool reset) { MBlock* b = GetBlock(p); if (b == 0) return 0; uptr sz = RoundUpTo(b->siz, kMetaShadowCell); FreeRange(proc, p, sz, reset); return sz; } bool MetaMap::FreeRange(Processor *proc, uptr p, uptr sz, bool reset) { bool has_something = false; u32 *meta = MemToMeta(p); u32 *end = MemToMeta(p + sz); if (end == meta) end++; for (; meta < end; meta++) { u32 idx = *meta; if (idx == 0) { // Note: don't write to meta in this case -- the block can be huge. continue; } *meta = 0; has_something = true; while (idx != 0) { if (idx & kFlagBlock) { block_alloc_.Free(&proc->block_cache, idx & ~kFlagMask); break; } else if (idx & kFlagSync) { DCHECK(idx & kFlagSync); SyncVar *s = sync_alloc_.Map(idx & ~kFlagMask); u32 next = s->next; if (reset) s->Reset(); sync_alloc_.Free(&proc->sync_cache, idx & ~kFlagMask); idx = next; } else { CHECK(0); } } } return has_something; } // ResetRange removes all meta objects from the range. // It is called for large mmap-ed regions. The function is best-effort wrt // freeing of meta objects, because we don't want to page in the whole range // which can be huge. The function probes pages one-by-one until it finds a page // without meta objects, at this point it stops freeing meta objects. Because // thread stacks grow top-down, we do the same starting from end as well. void MetaMap::ResetRange(Processor *proc, uptr p, uptr sz, bool reset) { if (SANITIZER_GO) { // UnmapOrDie/MmapFixedNoReserve does not work on Windows, // so we do the optimization only for C/C++. FreeRange(proc, p, sz, reset); return; } const uptr kMetaRatio = kMetaShadowCell / kMetaShadowSize; const uptr kPageSize = GetPageSizeCached() * kMetaRatio; if (sz <= 4 * kPageSize) { // If the range is small, just do the normal free procedure. FreeRange(proc, p, sz, reset); return; } // First, round both ends of the range to page size. uptr diff = RoundUp(p, kPageSize) - p; if (diff != 0) { FreeRange(proc, p, diff, reset); p += diff; sz -= diff; } diff = p + sz - RoundDown(p + sz, kPageSize); if (diff != 0) { FreeRange(proc, p + sz - diff, diff, reset); sz -= diff; } // Now we must have a non-empty page-aligned range. CHECK_GT(sz, 0); CHECK_EQ(p, RoundUp(p, kPageSize)); CHECK_EQ(sz, RoundUp(sz, kPageSize)); const uptr p0 = p; const uptr sz0 = sz; // Probe start of the range. for (uptr checked = 0; sz > 0; checked += kPageSize) { bool has_something = FreeRange(proc, p, kPageSize, reset); p += kPageSize; sz -= kPageSize; if (!has_something && checked > (128 << 10)) break; } // Probe end of the range. for (uptr checked = 0; sz > 0; checked += kPageSize) { bool has_something = FreeRange(proc, p + sz - kPageSize, kPageSize, reset); sz -= kPageSize; // Stacks grow down, so sync object are most likely at the end of the region // (if it is a stack). The very end of the stack is TLS and tsan increases // TLS by at least 256K, so check at least 512K. if (!has_something && checked > (512 << 10)) break; } // Finally, page out the whole range (including the parts that we've just // freed). Note: we can't simply madvise, because we need to leave a zeroed // range (otherwise __tsan_java_move can crash if it encounters a left-over // meta objects in java heap). uptr metap = (uptr)MemToMeta(p0); uptr metasz = sz0 / kMetaRatio; UnmapOrDie((void*)metap, metasz); if (!MmapFixedSuperNoReserve(metap, metasz)) Die(); } void MetaMap::ResetClocks() { // This can be called from the background thread // which does not have proc/cache. // The cache is too large for stack. static InternalAllocatorCache cache; internal_memset(&cache, 0, sizeof(cache)); internal_allocator()->InitCache(&cache); sync_alloc_.ForEach([&](SyncVar *s) { if (s->clock) { InternalFree(s->clock, &cache); s->clock = nullptr; } if (s->read_clock) { InternalFree(s->read_clock, &cache); s->read_clock = nullptr; } s->last_lock.Reset(); }); internal_allocator()->DestroyCache(&cache); } MBlock* MetaMap::GetBlock(uptr p) { u32 *meta = MemToMeta(p); u32 idx = *meta; for (;;) { if (idx == 0) return 0; if (idx & kFlagBlock) return block_alloc_.Map(idx & ~kFlagMask); DCHECK(idx & kFlagSync); SyncVar * s = sync_alloc_.Map(idx & ~kFlagMask); idx = s->next; } } SyncVar *MetaMap::GetSync(ThreadState *thr, uptr pc, uptr addr, bool create, bool save_stack) { DCHECK(!create || thr->slot_locked); u32 *meta = MemToMeta(addr); u32 idx0 = *meta; u32 myidx = 0; SyncVar *mys = nullptr; for (;;) { for (u32 idx = idx0; idx && !(idx & kFlagBlock);) { DCHECK(idx & kFlagSync); SyncVar * s = sync_alloc_.Map(idx & ~kFlagMask); if (LIKELY(s->addr == addr)) { if (UNLIKELY(myidx != 0)) { mys->Reset(); sync_alloc_.Free(&thr->proc()->sync_cache, myidx); } return s; } idx = s->next; } if (!create) return nullptr; if (UNLIKELY(*meta != idx0)) { idx0 = *meta; continue; } if (LIKELY(myidx == 0)) { myidx = sync_alloc_.Alloc(&thr->proc()->sync_cache); mys = sync_alloc_.Map(myidx); mys->Init(thr, pc, addr, save_stack); } mys->next = idx0; if (atomic_compare_exchange_strong((atomic_uint32_t*)meta, &idx0, myidx | kFlagSync, memory_order_release)) { return mys; } } } void MetaMap::MoveMemory(uptr src, uptr dst, uptr sz) { // src and dst can overlap, // there are no concurrent accesses to the regions (e.g. stop-the-world). CHECK_NE(src, dst); CHECK_NE(sz, 0); uptr diff = dst - src; u32 *src_meta = MemToMeta(src); u32 *dst_meta = MemToMeta(dst); u32 *src_meta_end = MemToMeta(src + sz); uptr inc = 1; if (dst > src) { src_meta = MemToMeta(src + sz) - 1; dst_meta = MemToMeta(dst + sz) - 1; src_meta_end = MemToMeta(src) - 1; inc = -1; } for (; src_meta != src_meta_end; src_meta += inc, dst_meta += inc) { CHECK_EQ(*dst_meta, 0); u32 idx = *src_meta; *src_meta = 0; *dst_meta = idx; // Patch the addresses in sync objects. while (idx != 0) { if (idx & kFlagBlock) break; CHECK(idx & kFlagSync); SyncVar *s = sync_alloc_.Map(idx & ~kFlagMask); s->addr += diff; idx = s->next; } } } void MetaMap::OnProcIdle(Processor *proc) { block_alloc_.FlushCache(&proc->block_cache); sync_alloc_.FlushCache(&proc->sync_cache); } MetaMap::MemoryStats MetaMap::GetMemoryStats() const { MemoryStats stats; stats.mem_block = block_alloc_.AllocatedMemory(); stats.sync_obj = sync_alloc_.AllocatedMemory(); return stats; } } // namespace __tsan