blob: fc5088c336cd7a71027998dbf485f2d868c33297 [file] [log] [blame]
//===-- 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(Tid tid) : ThreadContextBase(tid), thr(), sync() {}
#if !SANITIZER_GO
ThreadContext::~ThreadContext() {
}
#endif
void ThreadContext::OnReset() { CHECK(!sync); }
#if !SANITIZER_GO
struct ThreadLeak {
ThreadContext *tctx;
int count;
};
static void CollectThreadLeaks(ThreadContextBase *tctx_base, void *arg) {
auto &leaks = *static_cast<Vector<ThreadLeak> *>(arg);
auto *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;
}
}
leaks.PushBack({tctx, 1});
}
#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(CollectThreadLeaks,
&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;
}
struct OnCreatedArgs {
VectorClock *sync;
uptr sync_epoch;
StackID stack;
};
Tid ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) {
// The main thread and GCD workers don't have a parent thread.
Tid parent = kInvalidTid;
OnCreatedArgs arg = {nullptr, 0, kInvalidStackID};
if (thr) {
parent = thr->tid;
arg.stack = CurrentStackId(thr, pc);
if (!thr->ignore_sync) {
SlotLocker locker(thr);
thr->clock.ReleaseStore(&arg.sync);
arg.sync_epoch = ctx->global_epoch;
IncrementEpoch(thr);
}
}
Tid tid = ctx->thread_registry.CreateThread(uid, detached, parent, &arg);
DPrintf("#%d: ThreadCreate tid=%d uid=%zu\n", parent, tid, uid);
return tid;
}
void ThreadContext::OnCreated(void *arg) {
OnCreatedArgs *args = static_cast<OnCreatedArgs *>(arg);
sync = args->sync;
sync_epoch = args->sync_epoch;
creation_stack_id = args->stack;
}
extern "C" void __tsan_stack_initialization() {}
struct OnStartedArgs {
ThreadState *thr;
uptr stk_addr;
uptr stk_size;
uptr tls_addr;
uptr tls_size;
};
void ThreadStart(ThreadState *thr, Tid tid, tid_t os_id,
ThreadType thread_type) {
ctx->thread_registry.StartThread(tid, os_id, thread_type, thr);
if (!thr->ignore_sync) {
SlotAttachAndLock(thr);
if (thr->tctx->sync_epoch == ctx->global_epoch)
thr->clock.Acquire(thr->tctx->sync);
SlotUnlock(thr);
}
Free(thr->tctx->sync);
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);
#endif
thr->stk_addr = stk_addr;
thr->stk_size = stk_size;
thr->tls_addr = tls_addr;
thr->tls_size = tls_size;
#if !SANITIZER_GO
if (ctx->after_multithreaded_fork) {
thr->ignore_interceptors++;
ThreadIgnoreBegin(thr, 0);
ThreadIgnoreSyncBegin(thr, 0);
}
#endif
#if !SANITIZER_GO
// Don't imitate stack/TLS writes for the main thread,
// because its initialization is synchronized with all
// subsequent threads anyway.
if (tid != kMainTid) {
if (stk_addr && stk_size) {
const uptr pc = StackTrace::GetNextInstructionPc(
reinterpret_cast<uptr>(__tsan_stack_initialization));
MemoryRangeImitateWrite(thr, pc, stk_addr, stk_size);
}
if (tls_addr && tls_size)
ImitateTlsWrite(thr, tls_addr, tls_size);
}
#endif
}
void ThreadContext::OnStarted(void *arg) {
thr = static_cast<ThreadState *>(arg);
DPrintf("#%d: ThreadStart\n", tid);
new (thr) ThreadState(tid);
if (common_flags()->detect_deadlocks)
thr->dd_lt = ctx->dd->CreateLogicalThread(tid);
thr->tctx = this;
#if !SANITIZER_GO
thr->is_inited = true;
#endif
}
void ThreadFinish(ThreadState *thr) {
DPrintf("#%d: ThreadFinish\n", thr->tid);
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;
#if !SANITIZER_GO
thr->is_inited = false;
thr->ignore_interceptors++;
PlatformCleanUpThreadState(thr);
#endif
if (!thr->ignore_sync) {
SlotLocker locker(thr);
ThreadRegistryLock lock(&ctx->thread_registry);
// Note: detached is protected by the thread registry mutex,
// the thread may be detaching concurrently in another thread.
if (!thr->tctx->detached) {
thr->clock.ReleaseStore(&thr->tctx->sync);
thr->tctx->sync_epoch = ctx->global_epoch;
IncrementEpoch(thr);
}
}
#if !SANITIZER_GO
UnmapOrDie(thr->shadow_stack, kShadowStackSize * sizeof(uptr));
#else
Free(thr->shadow_stack);
#endif
thr->shadow_stack = nullptr;
thr->shadow_stack_pos = nullptr;
thr->shadow_stack_end = nullptr;
if (common_flags()->detect_deadlocks)
ctx->dd->DestroyLogicalThread(thr->dd_lt);
SlotDetach(thr);
ctx->thread_registry.FinishThread(thr->tid);
thr->~ThreadState();
}
void ThreadContext::OnFinished() {
Lock lock(&ctx->slot_mtx);
Lock lock1(&trace.mtx);
// Queue all trace parts into the global recycle queue.
auto parts = &trace.parts;
while (trace.local_head) {
CHECK(parts->Queued(trace.local_head));
ctx->trace_part_recycle.PushBack(trace.local_head);
trace.local_head = parts->Next(trace.local_head);
}
ctx->trace_part_recycle_finished += parts->Size();
if (ctx->trace_part_recycle_finished > Trace::kFinishedThreadHi) {
ctx->trace_part_finished_excess += parts->Size();
trace.parts_allocated = 0;
} else if (ctx->trace_part_recycle_finished > Trace::kFinishedThreadLo &&
parts->Size() > 1) {
ctx->trace_part_finished_excess += parts->Size() - 1;
trace.parts_allocated = 1;
}
// From now on replay will use trace->final_pos.
trace.final_pos = (Event *)atomic_load_relaxed(&thr->trace_pos);
atomic_store_relaxed(&thr->trace_pos, 0);
thr->tctx = nullptr;
thr = nullptr;
}
struct ConsumeThreadContext {
uptr uid;
ThreadContextBase *tctx;
};
Tid ThreadConsumeTid(ThreadState *thr, uptr pc, uptr uid) {
return ctx->thread_registry.ConsumeThreadUserId(uid);
}
struct JoinArg {
VectorClock *sync;
uptr sync_epoch;
};
void ThreadJoin(ThreadState *thr, uptr pc, Tid tid) {
CHECK_GT(tid, 0);
DPrintf("#%d: ThreadJoin tid=%d\n", thr->tid, tid);
JoinArg arg = {};
ctx->thread_registry.JoinThread(tid, &arg);
if (!thr->ignore_sync) {
SlotLocker locker(thr);
if (arg.sync_epoch == ctx->global_epoch)
thr->clock.Acquire(arg.sync);
}
Free(arg.sync);
}
void ThreadContext::OnJoined(void *ptr) {
auto arg = static_cast<JoinArg *>(ptr);
arg->sync = sync;
arg->sync_epoch = sync_epoch;
sync = nullptr;
sync_epoch = 0;
}
void ThreadContext::OnDead() { CHECK_EQ(sync, nullptr); }
void ThreadDetach(ThreadState *thr, uptr pc, Tid tid) {
CHECK_GT(tid, 0);
ctx->thread_registry.DetachThread(tid, thr);
}
void ThreadContext::OnDetached(void *arg) { Free(sync); }
void ThreadNotJoined(ThreadState *thr, uptr pc, Tid tid, uptr uid) {
CHECK_GT(tid, 0);
ctx->thread_registry.SetThreadUserId(tid, uid);
}
void ThreadSetName(ThreadState *thr, const char *name) {
ctx->thread_registry.SetThreadName(thr->tid, name);
}
#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 = Alloc(sizeof(ThreadState));
ThreadState *fiber = static_cast<ThreadState *>(mem);
internal_memset(fiber, 0, sizeof(*fiber));
Tid 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);
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