llvm-project/compiler-rt/lib/tsan/rtl/tsan_rtl_thread.cpp
Fei Peng 208231d197
[compiler-rt][TSan] Add support for Android (#147580)
1. Fixed Android setjmp issue. The root cause is that TSan initializes
before longjmp_xor_key is set up. During __libc_init_vdso, a call to
strcmp triggers TSan initialization, which occurs before
__libc_init_setjmp_cookie. The solution is to call
InitializeLongjmpXorKey on the first use of longjmp_xor_key.
Additionally, correct LONG_JMP_SP_ENV_SLOT by following the bionic
source code.
2. Skip thr object range check on Android. On Android, thr is allocated
on the heap, causing the check to fail.
3. Disable intercepting clone on Android. pthread_create internally
calls clone. Disabling the interception of clone resolves the issue in
most scenarios.
4. Use a workaround to recover the thr pointer stored in
TLS_SLOT_SANITIZER slot, whose value was modified by Skia.

This PR solved the issue from NDK
https://github.com/android/ndk/issues/1041.

Test project: https://github.com/bytedance/android_tsan_sample/
2025-10-06 12:51:26 -04:00

393 lines
11 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(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
// Disabled on Mac because lldb test TestTsanBasic fails:
// https://reviews.llvm.org/D112603#3163158
#if !SANITIZER_GO && !SANITIZER_APPLE
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;
Vector<ThreadLeak> leaks;
{
ThreadRegistryLock l(&ctx->thread_registry);
ctx->thread_registry.RunCallbackForEachThreadLocked(CollectThreadLeaks,
&leaks);
}
for (uptr i = 0; i < leaks.Size(); i++) {
// Use alloca, because malloc during signal handling deadlocks
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
// Take a new scope as Apple platforms require the below locks released
// before symbolizing in order to avoid a deadlock
{
ThreadRegistryLock l(&ctx->thread_registry);
new (rep) ScopedReport(ReportTypeThreadLeak);
rep->AddThread(leaks[i].tctx, true);
rep->SetCount(leaks[i].count);
# if SANITIZER_APPLE
} // Close this scope to release the locks
# endif
OutputReport(thr, *rep);
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
# if !SANITIZER_APPLE
}
# endif
}
#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, ThreadID 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);
#if !SANITIZER_GO
thr->is_inited = true;
#endif
uptr stk_addr = 0;
uptr stk_end = 0;
uptr tls_addr = 0;
uptr tls_end = 0;
#if !SANITIZER_GO
if (thread_type != ThreadType::Fiber)
GetThreadStackAndTls(tid == kMainTid, &stk_addr, &stk_end, &tls_addr,
&tls_end);
#endif
uptr stk_size = stk_end - stk_addr;
uptr tls_size = tls_end - tls_addr;
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 && !SANITIZER_ANDROID
// Don't imitate stack/TLS writes for the main thread,
// because its initialization is synchronized with all
// subsequent threads anyway.
// Because thr is created by MmapOrDie, the thr object
// is not in tls, the pointer to the thr object is in
// TLS_SLOT_SANITIZER slot. So skip this check on
// Android platform.
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) {
DPrintf("#%d: ThreadStart\n", tid);
thr = new (arg) ThreadState(tid);
if (common_flags()->detect_deadlocks)
thr->dd_lt = ctx->dd->CreateLogicalThread(tid);
thr->tctx = this;
}
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