Files
clang-p2996/compiler-rt/lib/tsan/tests/unit/tsan_trace_test.cpp
Dmitry Vyukov 2eb3e20461 tsan: fix deadlock during race reporting
SlotPairLocker calls SlotLock under ctx->multi_slot_mtx.
SlotLock can invoke global reset DoReset if we are out of slots/epochs.
But DoReset locks ctx->multi_slot_mtx as well, which leads to deadlock.

Resolve the deadlock by removing SlotPairLocker/multi_slot_mtx
and only lock one slot for which we will do RestoreStack.
We need to lock that slot because RestoreStack accesses the slot journal.
But it's unclear why we need to lock the current slot.
Initially I did it just to be on the safer side (but at that time
we dit not lock the second slot, so it was easy just to lock the current slot).

Reviewed By: melver

Differential Revision: https://reviews.llvm.org/D116040
2021-12-20 18:52:48 +01:00

332 lines
11 KiB
C++

//===-- tsan_trace_test.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 "tsan_trace.h"
#include <pthread.h>
#include "gtest/gtest.h"
#include "tsan_rtl.h"
#if SANITIZER_MAC || !defined(__x86_64__)
// These tests are currently crashing on Mac:
// https://reviews.llvm.org/D107911
// and on ppc64: https://reviews.llvm.org/D110546#3025422
// due to the way we create thread contexts
// (but they crashed on Mac with normal pthread_create as well).
// There must be some difference in thread initialization
// between normal execution and unit tests.
# define TRACE_TEST(SUITE, NAME) TEST(SUITE, DISABLED_##NAME)
#else
# define TRACE_TEST(SUITE, NAME) TEST(SUITE, NAME)
#endif
namespace __tsan {
// We need to run all trace tests in a new thread,
// so that the thread trace is empty initially.
template <uptr N>
struct ThreadArray {
ThreadArray() {
for (auto *&thr : threads) {
thr = static_cast<ThreadState *>(
MmapOrDie(sizeof(ThreadState), "ThreadState"));
Tid tid = ThreadCreate(cur_thread(), 0, 0, true);
Processor *proc = ProcCreate();
ProcWire(proc, thr);
ThreadStart(thr, tid, 0, ThreadType::Fiber);
}
}
~ThreadArray() {
for (uptr i = 0; i < N; i++) {
if (threads[i])
Finish(i);
}
}
void Finish(uptr i) {
auto *thr = threads[i];
threads[i] = nullptr;
Processor *proc = thr->proc();
ThreadFinish(thr);
ProcUnwire(proc, thr);
ProcDestroy(proc);
UnmapOrDie(thr, sizeof(ThreadState));
}
ThreadState *threads[N];
ThreadState *operator[](uptr i) { return threads[i]; }
ThreadState *operator->() { return threads[0]; }
operator ThreadState *() { return threads[0]; }
};
TRACE_TEST(Trace, RestoreAccess) {
// A basic test with some function entry/exit events,
// some mutex lock/unlock events and some other distracting
// memory events.
ThreadArray<1> thr;
TraceFunc(thr, 0x1000);
TraceFunc(thr, 0x1001);
TraceMutexLock(thr, EventType::kLock, 0x4000, 0x5000, 0x6000);
TraceMutexLock(thr, EventType::kLock, 0x4001, 0x5001, 0x6001);
TraceMutexUnlock(thr, 0x5000);
TraceFunc(thr);
CHECK(TryTraceMemoryAccess(thr, 0x2001, 0x3001, 8, kAccessRead));
TraceMutexLock(thr, EventType::kRLock, 0x4002, 0x5002, 0x6002);
TraceFunc(thr, 0x1002);
CHECK(TryTraceMemoryAccess(thr, 0x2000, 0x3000, 8, kAccessRead));
// This is the access we want to find.
// The previous one is equivalent, but RestoreStack must prefer
// the last of the matchig accesses.
CHECK(TryTraceMemoryAccess(thr, 0x2002, 0x3000, 8, kAccessRead));
Lock slot_lock(&ctx->slots[static_cast<uptr>(thr->fast_state.sid())].mtx);
ThreadRegistryLock lock1(&ctx->thread_registry);
Lock lock2(&ctx->slot_mtx);
Tid tid = kInvalidTid;
VarSizeStackTrace stk;
MutexSet mset;
uptr tag = kExternalTagNone;
bool res = RestoreStack(EventType::kAccessExt, thr->fast_state.sid(),
thr->fast_state.epoch(), 0x3000, 8, kAccessRead, &tid,
&stk, &mset, &tag);
CHECK(res);
CHECK_EQ(tid, thr->tid);
CHECK_EQ(stk.size, 3);
CHECK_EQ(stk.trace[0], 0x1000);
CHECK_EQ(stk.trace[1], 0x1002);
CHECK_EQ(stk.trace[2], 0x2002);
CHECK_EQ(mset.Size(), 2);
CHECK_EQ(mset.Get(0).addr, 0x5001);
CHECK_EQ(mset.Get(0).stack_id, 0x6001);
CHECK_EQ(mset.Get(0).write, true);
CHECK_EQ(mset.Get(1).addr, 0x5002);
CHECK_EQ(mset.Get(1).stack_id, 0x6002);
CHECK_EQ(mset.Get(1).write, false);
CHECK_EQ(tag, kExternalTagNone);
}
TRACE_TEST(Trace, MemoryAccessSize) {
// Test tracing and matching of accesses of different sizes.
struct Params {
uptr access_size, offset, size;
bool res;
};
Params tests[] = {
{1, 0, 1, true}, {4, 0, 2, true},
{4, 2, 2, true}, {8, 3, 1, true},
{2, 1, 1, true}, {1, 1, 1, false},
{8, 5, 4, false}, {4, static_cast<uptr>(-1l), 4, false},
};
for (auto params : tests) {
for (int type = 0; type < 3; type++) {
ThreadArray<1> thr;
Printf("access_size=%zu, offset=%zu, size=%zu, res=%d, type=%d\n",
params.access_size, params.offset, params.size, params.res, type);
TraceFunc(thr, 0x1000);
switch (type) {
case 0:
// This should emit compressed event.
CHECK(TryTraceMemoryAccess(thr, 0x2000, 0x3000, params.access_size,
kAccessRead));
break;
case 1:
// This should emit full event.
CHECK(TryTraceMemoryAccess(thr, 0x2000000, 0x3000, params.access_size,
kAccessRead));
break;
case 2:
TraceMemoryAccessRange(thr, 0x2000000, 0x3000, params.access_size,
kAccessRead);
break;
}
Lock slot_lock(&ctx->slots[static_cast<uptr>(thr->fast_state.sid())].mtx);
ThreadRegistryLock lock1(&ctx->thread_registry);
Lock lock2(&ctx->slot_mtx);
Tid tid = kInvalidTid;
VarSizeStackTrace stk;
MutexSet mset;
uptr tag = kExternalTagNone;
bool res =
RestoreStack(EventType::kAccessExt, thr->fast_state.sid(),
thr->fast_state.epoch(), 0x3000 + params.offset,
params.size, kAccessRead, &tid, &stk, &mset, &tag);
CHECK_EQ(res, params.res);
if (params.res) {
CHECK_EQ(stk.size, 2);
CHECK_EQ(stk.trace[0], 0x1000);
CHECK_EQ(stk.trace[1], type ? 0x2000000 : 0x2000);
}
}
}
}
TRACE_TEST(Trace, RestoreMutexLock) {
// Check of restoration of a mutex lock event.
ThreadArray<1> thr;
TraceFunc(thr, 0x1000);
TraceMutexLock(thr, EventType::kLock, 0x4000, 0x5000, 0x6000);
TraceMutexLock(thr, EventType::kRLock, 0x4001, 0x5001, 0x6001);
TraceMutexLock(thr, EventType::kRLock, 0x4002, 0x5001, 0x6002);
Lock slot_lock(&ctx->slots[static_cast<uptr>(thr->fast_state.sid())].mtx);
ThreadRegistryLock lock1(&ctx->thread_registry);
Lock lock2(&ctx->slot_mtx);
Tid tid = kInvalidTid;
VarSizeStackTrace stk;
MutexSet mset;
uptr tag = kExternalTagNone;
bool res = RestoreStack(EventType::kLock, thr->fast_state.sid(),
thr->fast_state.epoch(), 0x5001, 0, 0, &tid, &stk,
&mset, &tag);
CHECK(res);
CHECK_EQ(stk.size, 2);
CHECK_EQ(stk.trace[0], 0x1000);
CHECK_EQ(stk.trace[1], 0x4002);
CHECK_EQ(mset.Size(), 2);
CHECK_EQ(mset.Get(0).addr, 0x5000);
CHECK_EQ(mset.Get(0).stack_id, 0x6000);
CHECK_EQ(mset.Get(0).write, true);
CHECK_EQ(mset.Get(1).addr, 0x5001);
CHECK_EQ(mset.Get(1).stack_id, 0x6001);
CHECK_EQ(mset.Get(1).write, false);
}
TRACE_TEST(Trace, MultiPart) {
// Check replay of a trace with multiple parts.
ThreadArray<1> thr;
FuncEntry(thr, 0x1000);
FuncEntry(thr, 0x2000);
MutexPreLock(thr, 0x4000, 0x5000, 0);
MutexPostLock(thr, 0x4000, 0x5000, 0);
MutexPreLock(thr, 0x4000, 0x5000, 0);
MutexPostLock(thr, 0x4000, 0x5000, 0);
const uptr kEvents = 3 * sizeof(TracePart) / sizeof(Event);
for (uptr i = 0; i < kEvents; i++) {
FuncEntry(thr, 0x3000);
MutexPreLock(thr, 0x4002, 0x5002, 0);
MutexPostLock(thr, 0x4002, 0x5002, 0);
MutexUnlock(thr, 0x4003, 0x5002, 0);
FuncExit(thr);
}
FuncEntry(thr, 0x4000);
TraceMutexLock(thr, EventType::kRLock, 0x4001, 0x5001, 0x6001);
CHECK(TryTraceMemoryAccess(thr, 0x2002, 0x3000, 8, kAccessRead));
Lock slot_lock(&ctx->slots[static_cast<uptr>(thr->fast_state.sid())].mtx);
ThreadRegistryLock lock1(&ctx->thread_registry);
Lock lock2(&ctx->slot_mtx);
Tid tid = kInvalidTid;
VarSizeStackTrace stk;
MutexSet mset;
uptr tag = kExternalTagNone;
bool res = RestoreStack(EventType::kAccessExt, thr->fast_state.sid(),
thr->fast_state.epoch(), 0x3000, 8, kAccessRead, &tid,
&stk, &mset, &tag);
CHECK(res);
CHECK_EQ(tid, thr->tid);
CHECK_EQ(stk.size, 4);
CHECK_EQ(stk.trace[0], 0x1000);
CHECK_EQ(stk.trace[1], 0x2000);
CHECK_EQ(stk.trace[2], 0x4000);
CHECK_EQ(stk.trace[3], 0x2002);
CHECK_EQ(mset.Size(), 2);
CHECK_EQ(mset.Get(0).addr, 0x5000);
CHECK_EQ(mset.Get(0).write, true);
CHECK_EQ(mset.Get(0).count, 2);
CHECK_EQ(mset.Get(1).addr, 0x5001);
CHECK_EQ(mset.Get(1).write, false);
CHECK_EQ(mset.Get(1).count, 1);
}
void CheckTraceState(uptr count, uptr finished, uptr excess, uptr recycle) {
Lock l(&ctx->slot_mtx);
Printf("CheckTraceState(%zu/%zu, %zu/%zu, %zu/%zu, %zu/%zu)\n",
ctx->trace_part_total_allocated, count,
ctx->trace_part_recycle_finished, finished,
ctx->trace_part_finished_excess, excess,
ctx->trace_part_recycle.Size(), recycle);
CHECK_EQ(ctx->trace_part_total_allocated, count);
CHECK_EQ(ctx->trace_part_recycle_finished, finished);
CHECK_EQ(ctx->trace_part_finished_excess, excess);
CHECK_EQ(ctx->trace_part_recycle.Size(), recycle);
}
TRACE_TEST(TraceAlloc, SingleThread) {
TraceResetForTesting();
auto check_thread = [&](ThreadState *thr, uptr size, uptr count,
uptr finished, uptr excess, uptr recycle) {
CHECK_EQ(thr->tctx->trace.parts.Size(), size);
CheckTraceState(count, finished, excess, recycle);
};
ThreadArray<2> threads;
check_thread(threads[0], 0, 0, 0, 0, 0);
TraceSwitchPartImpl(threads[0]);
check_thread(threads[0], 1, 1, 0, 0, 0);
TraceSwitchPartImpl(threads[0]);
check_thread(threads[0], 2, 2, 0, 0, 0);
TraceSwitchPartImpl(threads[0]);
check_thread(threads[0], 3, 3, 0, 0, 1);
TraceSwitchPartImpl(threads[0]);
check_thread(threads[0], 3, 3, 0, 0, 1);
threads.Finish(0);
CheckTraceState(3, 3, 0, 3);
threads.Finish(1);
CheckTraceState(3, 3, 0, 3);
}
TRACE_TEST(TraceAlloc, FinishedThreadReuse) {
TraceResetForTesting();
constexpr uptr Hi = Trace::kFinishedThreadHi;
constexpr uptr kThreads = 4 * Hi;
ThreadArray<kThreads> threads;
for (uptr i = 0; i < kThreads; i++) {
Printf("thread %zu\n", i);
TraceSwitchPartImpl(threads[i]);
if (i <= Hi)
CheckTraceState(i + 1, i, 0, i);
else if (i <= 2 * Hi)
CheckTraceState(Hi + 1, Hi, i - Hi, Hi);
else
CheckTraceState(Hi + 1, Hi, Hi, Hi);
threads.Finish(i);
if (i < Hi)
CheckTraceState(i + 1, i + 1, 0, i + 1);
else if (i < 2 * Hi)
CheckTraceState(Hi + 1, Hi + 1, i - Hi + 1, Hi + 1);
else
CheckTraceState(Hi + 1, Hi + 1, Hi + 1, Hi + 1);
}
}
TRACE_TEST(TraceAlloc, FinishedThreadReuse2) {
TraceResetForTesting();
// constexpr uptr Lo = Trace::kFinishedThreadLo;
// constexpr uptr Hi = Trace::kFinishedThreadHi;
constexpr uptr Min = Trace::kMinParts;
constexpr uptr kThreads = 10;
constexpr uptr kParts = 2 * Min;
ThreadArray<kThreads> threads;
for (uptr i = 0; i < kThreads; i++) {
Printf("thread %zu\n", i);
for (uptr j = 0; j < kParts; j++) TraceSwitchPartImpl(threads[i]);
if (i == 0)
CheckTraceState(Min, 0, 0, 1);
else
CheckTraceState(2 * Min, 0, Min, Min + 1);
threads.Finish(i);
if (i == 0)
CheckTraceState(Min, Min, 0, Min);
else
CheckTraceState(2 * Min, 2 * Min, Min, 2 * Min);
}
}
} // namespace __tsan