I don't think the stat subsystem was ever used since tsan development in 2012. But it adds lots of code and this effectively dead code needs to be updated if the runtime code changes, which adds maintanance cost for no benefit. Normal profiler usually gives enough info and that info is more trustworthy. Remove the stats subsystem. Reviewed By: vitalybuka Differential Revision: https://reviews.llvm.org/D106276
452 lines
14 KiB
C++
452 lines
14 KiB
C++
//===-- tsan_rtl_thread.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_rtl.h"
|
|
#include "tsan_mman.h"
|
|
#include "tsan_platform.h"
|
|
#include "tsan_report.h"
|
|
#include "tsan_sync.h"
|
|
|
|
namespace __tsan {
|
|
|
|
// ThreadContext implementation.
|
|
|
|
ThreadContext::ThreadContext(int tid)
|
|
: ThreadContextBase(tid)
|
|
, thr()
|
|
, sync()
|
|
, epoch0()
|
|
, epoch1() {
|
|
}
|
|
|
|
#if !SANITIZER_GO
|
|
ThreadContext::~ThreadContext() {
|
|
}
|
|
#endif
|
|
|
|
void ThreadContext::OnDead() {
|
|
CHECK_EQ(sync.size(), 0);
|
|
}
|
|
|
|
void ThreadContext::OnJoined(void *arg) {
|
|
ThreadState *caller_thr = static_cast<ThreadState *>(arg);
|
|
AcquireImpl(caller_thr, 0, &sync);
|
|
sync.Reset(&caller_thr->proc()->clock_cache);
|
|
}
|
|
|
|
struct OnCreatedArgs {
|
|
ThreadState *thr;
|
|
uptr pc;
|
|
};
|
|
|
|
void ThreadContext::OnCreated(void *arg) {
|
|
thr = 0;
|
|
if (tid == kMainTid)
|
|
return;
|
|
OnCreatedArgs *args = static_cast<OnCreatedArgs *>(arg);
|
|
if (!args->thr) // GCD workers don't have a parent thread.
|
|
return;
|
|
args->thr->fast_state.IncrementEpoch();
|
|
// Can't increment epoch w/o writing to the trace as well.
|
|
TraceAddEvent(args->thr, args->thr->fast_state, EventTypeMop, 0);
|
|
ReleaseImpl(args->thr, 0, &sync);
|
|
creation_stack_id = CurrentStackId(args->thr, args->pc);
|
|
}
|
|
|
|
void ThreadContext::OnReset() {
|
|
CHECK_EQ(sync.size(), 0);
|
|
uptr trace_p = GetThreadTrace(tid);
|
|
ReleaseMemoryPagesToOS(trace_p, trace_p + TraceSize() * sizeof(Event));
|
|
//!!! ReleaseMemoryToOS(GetThreadTraceHeader(tid), sizeof(Trace));
|
|
}
|
|
|
|
void ThreadContext::OnDetached(void *arg) {
|
|
ThreadState *thr1 = static_cast<ThreadState*>(arg);
|
|
sync.Reset(&thr1->proc()->clock_cache);
|
|
}
|
|
|
|
struct OnStartedArgs {
|
|
ThreadState *thr;
|
|
uptr stk_addr;
|
|
uptr stk_size;
|
|
uptr tls_addr;
|
|
uptr tls_size;
|
|
};
|
|
|
|
void ThreadContext::OnStarted(void *arg) {
|
|
OnStartedArgs *args = static_cast<OnStartedArgs*>(arg);
|
|
thr = args->thr;
|
|
// RoundUp so that one trace part does not contain events
|
|
// from different threads.
|
|
epoch0 = RoundUp(epoch1 + 1, kTracePartSize);
|
|
epoch1 = (u64)-1;
|
|
new(thr) ThreadState(ctx, tid, unique_id, epoch0, reuse_count,
|
|
args->stk_addr, args->stk_size, args->tls_addr, args->tls_size);
|
|
#if !SANITIZER_GO
|
|
thr->shadow_stack = &ThreadTrace(thr->tid)->shadow_stack[0];
|
|
thr->shadow_stack_pos = thr->shadow_stack;
|
|
thr->shadow_stack_end = thr->shadow_stack + kShadowStackSize;
|
|
#else
|
|
// Setup dynamic shadow stack.
|
|
const int kInitStackSize = 8;
|
|
thr->shadow_stack = (uptr*)internal_alloc(MBlockShadowStack,
|
|
kInitStackSize * sizeof(uptr));
|
|
thr->shadow_stack_pos = thr->shadow_stack;
|
|
thr->shadow_stack_end = thr->shadow_stack + kInitStackSize;
|
|
#endif
|
|
if (common_flags()->detect_deadlocks)
|
|
thr->dd_lt = ctx->dd->CreateLogicalThread(unique_id);
|
|
thr->fast_state.SetHistorySize(flags()->history_size);
|
|
// Commit switch to the new part of the trace.
|
|
// TraceAddEvent will reset stack0/mset0 in the new part for us.
|
|
TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0);
|
|
|
|
thr->fast_synch_epoch = epoch0;
|
|
AcquireImpl(thr, 0, &sync);
|
|
sync.Reset(&thr->proc()->clock_cache);
|
|
thr->is_inited = true;
|
|
DPrintf("#%d: ThreadStart epoch=%zu stk_addr=%zx stk_size=%zx "
|
|
"tls_addr=%zx tls_size=%zx\n",
|
|
tid, (uptr)epoch0, args->stk_addr, args->stk_size,
|
|
args->tls_addr, args->tls_size);
|
|
}
|
|
|
|
void ThreadContext::OnFinished() {
|
|
#if SANITIZER_GO
|
|
internal_free(thr->shadow_stack);
|
|
thr->shadow_stack = nullptr;
|
|
thr->shadow_stack_pos = nullptr;
|
|
thr->shadow_stack_end = nullptr;
|
|
#endif
|
|
if (!detached) {
|
|
thr->fast_state.IncrementEpoch();
|
|
// Can't increment epoch w/o writing to the trace as well.
|
|
TraceAddEvent(thr, thr->fast_state, EventTypeMop, 0);
|
|
ReleaseImpl(thr, 0, &sync);
|
|
}
|
|
epoch1 = thr->fast_state.epoch();
|
|
|
|
if (common_flags()->detect_deadlocks)
|
|
ctx->dd->DestroyLogicalThread(thr->dd_lt);
|
|
thr->clock.ResetCached(&thr->proc()->clock_cache);
|
|
#if !SANITIZER_GO
|
|
thr->last_sleep_clock.ResetCached(&thr->proc()->clock_cache);
|
|
#endif
|
|
#if !SANITIZER_GO
|
|
PlatformCleanUpThreadState(thr);
|
|
#endif
|
|
thr->~ThreadState();
|
|
thr = 0;
|
|
}
|
|
|
|
#if !SANITIZER_GO
|
|
struct ThreadLeak {
|
|
ThreadContext *tctx;
|
|
int count;
|
|
};
|
|
|
|
static void MaybeReportThreadLeak(ThreadContextBase *tctx_base, void *arg) {
|
|
Vector<ThreadLeak> &leaks = *(Vector<ThreadLeak>*)arg;
|
|
ThreadContext *tctx = static_cast<ThreadContext*>(tctx_base);
|
|
if (tctx->detached || tctx->status != ThreadStatusFinished)
|
|
return;
|
|
for (uptr i = 0; i < leaks.Size(); i++) {
|
|
if (leaks[i].tctx->creation_stack_id == tctx->creation_stack_id) {
|
|
leaks[i].count++;
|
|
return;
|
|
}
|
|
}
|
|
ThreadLeak leak = {tctx, 1};
|
|
leaks.PushBack(leak);
|
|
}
|
|
#endif
|
|
|
|
#if !SANITIZER_GO
|
|
static void ReportIgnoresEnabled(ThreadContext *tctx, IgnoreSet *set) {
|
|
if (tctx->tid == kMainTid) {
|
|
Printf("ThreadSanitizer: main thread finished with ignores enabled\n");
|
|
} else {
|
|
Printf("ThreadSanitizer: thread T%d %s finished with ignores enabled,"
|
|
" created at:\n", tctx->tid, tctx->name);
|
|
PrintStack(SymbolizeStackId(tctx->creation_stack_id));
|
|
}
|
|
Printf(" One of the following ignores was not ended"
|
|
" (in order of probability)\n");
|
|
for (uptr i = 0; i < set->Size(); i++) {
|
|
Printf(" Ignore was enabled at:\n");
|
|
PrintStack(SymbolizeStackId(set->At(i)));
|
|
}
|
|
Die();
|
|
}
|
|
|
|
static void ThreadCheckIgnore(ThreadState *thr) {
|
|
if (ctx->after_multithreaded_fork)
|
|
return;
|
|
if (thr->ignore_reads_and_writes)
|
|
ReportIgnoresEnabled(thr->tctx, &thr->mop_ignore_set);
|
|
if (thr->ignore_sync)
|
|
ReportIgnoresEnabled(thr->tctx, &thr->sync_ignore_set);
|
|
}
|
|
#else
|
|
static void ThreadCheckIgnore(ThreadState *thr) {}
|
|
#endif
|
|
|
|
void ThreadFinalize(ThreadState *thr) {
|
|
ThreadCheckIgnore(thr);
|
|
#if !SANITIZER_GO
|
|
if (!ShouldReport(thr, ReportTypeThreadLeak))
|
|
return;
|
|
ThreadRegistryLock l(ctx->thread_registry);
|
|
Vector<ThreadLeak> leaks;
|
|
ctx->thread_registry->RunCallbackForEachThreadLocked(
|
|
MaybeReportThreadLeak, &leaks);
|
|
for (uptr i = 0; i < leaks.Size(); i++) {
|
|
ScopedReport rep(ReportTypeThreadLeak);
|
|
rep.AddThread(leaks[i].tctx, true);
|
|
rep.SetCount(leaks[i].count);
|
|
OutputReport(thr, rep);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int ThreadCount(ThreadState *thr) {
|
|
uptr result;
|
|
ctx->thread_registry->GetNumberOfThreads(0, 0, &result);
|
|
return (int)result;
|
|
}
|
|
|
|
int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) {
|
|
OnCreatedArgs args = { thr, pc };
|
|
u32 parent_tid = thr ? thr->tid : kInvalidTid; // No parent for GCD workers.
|
|
int tid =
|
|
ctx->thread_registry->CreateThread(uid, detached, parent_tid, &args);
|
|
DPrintf("#%d: ThreadCreate tid=%d uid=%zu\n", parent_tid, tid, uid);
|
|
return tid;
|
|
}
|
|
|
|
void ThreadStart(ThreadState *thr, int tid, tid_t os_id,
|
|
ThreadType thread_type) {
|
|
uptr stk_addr = 0;
|
|
uptr stk_size = 0;
|
|
uptr tls_addr = 0;
|
|
uptr tls_size = 0;
|
|
#if !SANITIZER_GO
|
|
if (thread_type != ThreadType::Fiber)
|
|
GetThreadStackAndTls(tid == kMainTid, &stk_addr, &stk_size, &tls_addr,
|
|
&tls_size);
|
|
|
|
if (tid != kMainTid) {
|
|
if (stk_addr && stk_size)
|
|
MemoryRangeImitateWrite(thr, /*pc=*/ 1, stk_addr, stk_size);
|
|
|
|
if (tls_addr && tls_size) ImitateTlsWrite(thr, tls_addr, tls_size);
|
|
}
|
|
#endif
|
|
|
|
ThreadRegistry *tr = ctx->thread_registry;
|
|
OnStartedArgs args = { thr, stk_addr, stk_size, tls_addr, tls_size };
|
|
tr->StartThread(tid, os_id, thread_type, &args);
|
|
|
|
tr->Lock();
|
|
thr->tctx = (ThreadContext*)tr->GetThreadLocked(tid);
|
|
tr->Unlock();
|
|
|
|
#if !SANITIZER_GO
|
|
if (ctx->after_multithreaded_fork) {
|
|
thr->ignore_interceptors++;
|
|
ThreadIgnoreBegin(thr, 0);
|
|
ThreadIgnoreSyncBegin(thr, 0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void ThreadFinish(ThreadState *thr) {
|
|
ThreadCheckIgnore(thr);
|
|
if (thr->stk_addr && thr->stk_size)
|
|
DontNeedShadowFor(thr->stk_addr, thr->stk_size);
|
|
if (thr->tls_addr && thr->tls_size)
|
|
DontNeedShadowFor(thr->tls_addr, thr->tls_size);
|
|
thr->is_dead = true;
|
|
ctx->thread_registry->FinishThread(thr->tid);
|
|
}
|
|
|
|
struct ConsumeThreadContext {
|
|
uptr uid;
|
|
ThreadContextBase *tctx;
|
|
};
|
|
|
|
static bool ConsumeThreadByUid(ThreadContextBase *tctx, void *arg) {
|
|
ConsumeThreadContext *findCtx = (ConsumeThreadContext *)arg;
|
|
if (tctx->user_id == findCtx->uid && tctx->status != ThreadStatusInvalid) {
|
|
if (findCtx->tctx) {
|
|
// Ensure that user_id is unique. If it's not the case we are screwed.
|
|
// Something went wrong before, but now there is no way to recover.
|
|
// Returning a wrong thread is not an option, it may lead to very hard
|
|
// to debug false positives (e.g. if we join a wrong thread).
|
|
Report("ThreadSanitizer: dup thread with used id 0x%zx\n", findCtx->uid);
|
|
Die();
|
|
}
|
|
findCtx->tctx = tctx;
|
|
tctx->user_id = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid) {
|
|
ConsumeThreadContext findCtx = {uid, nullptr};
|
|
ctx->thread_registry->FindThread(ConsumeThreadByUid, &findCtx);
|
|
int tid = findCtx.tctx ? findCtx.tctx->tid : kInvalidTid;
|
|
DPrintf("#%d: ThreadTid uid=%zu tid=%d\n", thr->tid, uid, tid);
|
|
return tid;
|
|
}
|
|
|
|
void ThreadJoin(ThreadState *thr, uptr pc, int tid) {
|
|
CHECK_GT(tid, 0);
|
|
CHECK_LT(tid, kMaxTid);
|
|
DPrintf("#%d: ThreadJoin tid=%d\n", thr->tid, tid);
|
|
ctx->thread_registry->JoinThread(tid, thr);
|
|
}
|
|
|
|
void ThreadDetach(ThreadState *thr, uptr pc, int tid) {
|
|
CHECK_GT(tid, 0);
|
|
CHECK_LT(tid, kMaxTid);
|
|
ctx->thread_registry->DetachThread(tid, thr);
|
|
}
|
|
|
|
void ThreadNotJoined(ThreadState *thr, uptr pc, int tid, uptr uid) {
|
|
CHECK_GT(tid, 0);
|
|
CHECK_LT(tid, kMaxTid);
|
|
ctx->thread_registry->SetThreadUserId(tid, uid);
|
|
}
|
|
|
|
void ThreadSetName(ThreadState *thr, const char *name) {
|
|
ctx->thread_registry->SetThreadName(thr->tid, name);
|
|
}
|
|
|
|
void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr,
|
|
uptr size, bool is_write) {
|
|
if (size == 0)
|
|
return;
|
|
|
|
u64 *shadow_mem = (u64*)MemToShadow(addr);
|
|
DPrintf2("#%d: MemoryAccessRange: @%p %p size=%d is_write=%d\n",
|
|
thr->tid, (void*)pc, (void*)addr,
|
|
(int)size, is_write);
|
|
|
|
#if SANITIZER_DEBUG
|
|
if (!IsAppMem(addr)) {
|
|
Printf("Access to non app mem %zx\n", addr);
|
|
DCHECK(IsAppMem(addr));
|
|
}
|
|
if (!IsAppMem(addr + size - 1)) {
|
|
Printf("Access to non app mem %zx\n", addr + size - 1);
|
|
DCHECK(IsAppMem(addr + size - 1));
|
|
}
|
|
if (!IsShadowMem((uptr)shadow_mem)) {
|
|
Printf("Bad shadow addr %p (%zx)\n", shadow_mem, addr);
|
|
DCHECK(IsShadowMem((uptr)shadow_mem));
|
|
}
|
|
if (!IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))) {
|
|
Printf("Bad shadow addr %p (%zx)\n",
|
|
shadow_mem + size * kShadowCnt / 8 - 1, addr + size - 1);
|
|
DCHECK(IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1)));
|
|
}
|
|
#endif
|
|
|
|
if (*shadow_mem == kShadowRodata) {
|
|
DCHECK(!is_write);
|
|
// Access to .rodata section, no races here.
|
|
// Measurements show that it can be 10-20% of all memory accesses.
|
|
return;
|
|
}
|
|
|
|
FastState fast_state = thr->fast_state;
|
|
if (fast_state.GetIgnoreBit())
|
|
return;
|
|
|
|
fast_state.IncrementEpoch();
|
|
thr->fast_state = fast_state;
|
|
TraceAddEvent(thr, fast_state, EventTypeMop, pc);
|
|
|
|
bool unaligned = (addr % kShadowCell) != 0;
|
|
|
|
// Handle unaligned beginning, if any.
|
|
for (; addr % kShadowCell && size; addr++, size--) {
|
|
int const kAccessSizeLog = 0;
|
|
Shadow cur(fast_state);
|
|
cur.SetWrite(is_write);
|
|
cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog);
|
|
MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, false,
|
|
shadow_mem, cur);
|
|
}
|
|
if (unaligned)
|
|
shadow_mem += kShadowCnt;
|
|
// Handle middle part, if any.
|
|
for (; size >= kShadowCell; addr += kShadowCell, size -= kShadowCell) {
|
|
int const kAccessSizeLog = 3;
|
|
Shadow cur(fast_state);
|
|
cur.SetWrite(is_write);
|
|
cur.SetAddr0AndSizeLog(0, kAccessSizeLog);
|
|
MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, false,
|
|
shadow_mem, cur);
|
|
shadow_mem += kShadowCnt;
|
|
}
|
|
// Handle ending, if any.
|
|
for (; size; addr++, size--) {
|
|
int const kAccessSizeLog = 0;
|
|
Shadow cur(fast_state);
|
|
cur.SetWrite(is_write);
|
|
cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog);
|
|
MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, false,
|
|
shadow_mem, cur);
|
|
}
|
|
}
|
|
|
|
#if !SANITIZER_GO
|
|
void FiberSwitchImpl(ThreadState *from, ThreadState *to) {
|
|
Processor *proc = from->proc();
|
|
ProcUnwire(proc, from);
|
|
ProcWire(proc, to);
|
|
set_cur_thread(to);
|
|
}
|
|
|
|
ThreadState *FiberCreate(ThreadState *thr, uptr pc, unsigned flags) {
|
|
void *mem = internal_alloc(MBlockThreadContex, sizeof(ThreadState));
|
|
ThreadState *fiber = static_cast<ThreadState *>(mem);
|
|
internal_memset(fiber, 0, sizeof(*fiber));
|
|
int tid = ThreadCreate(thr, pc, 0, true);
|
|
FiberSwitchImpl(thr, fiber);
|
|
ThreadStart(fiber, tid, 0, ThreadType::Fiber);
|
|
FiberSwitchImpl(fiber, thr);
|
|
return fiber;
|
|
}
|
|
|
|
void FiberDestroy(ThreadState *thr, uptr pc, ThreadState *fiber) {
|
|
FiberSwitchImpl(thr, fiber);
|
|
ThreadFinish(fiber);
|
|
FiberSwitchImpl(fiber, thr);
|
|
internal_free(fiber);
|
|
}
|
|
|
|
void FiberSwitch(ThreadState *thr, uptr pc,
|
|
ThreadState *fiber, unsigned flags) {
|
|
if (!(flags & FiberSwitchFlagNoSync))
|
|
Release(thr, pc, (uptr)fiber);
|
|
FiberSwitchImpl(thr, fiber);
|
|
if (!(flags & FiberSwitchFlagNoSync))
|
|
Acquire(fiber, pc, (uptr)fiber);
|
|
}
|
|
#endif
|
|
|
|
} // namespace __tsan
|