Skip to content

[TSan] Fix deadlocks during TSan error reporting on Apple platforms #151495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_linux.h"
#include "sanitizer_common/sanitizer_placement_new.h"
#include "sanitizer_common/sanitizer_platform_interceptors.h"
#include "sanitizer_common/sanitizer_platform_limits_netbsd.h"
#include "sanitizer_common/sanitizer_platform_limits_posix.h"
Expand Down Expand Up @@ -2141,13 +2142,29 @@ static void ReportErrnoSpoiling(ThreadState *thr, uptr pc, int sig) {
// StackTrace::GetNestInstructionPc(pc) is used because return address is
// expected, OutputReport() will undo this.
ObtainCurrentStack(thr, StackTrace::GetNextInstructionPc(pc), &stack);
ThreadRegistryLock l(&ctx->thread_registry);
ScopedReport rep(ReportTypeErrnoInSignal);
rep.SetSigNum(sig);
if (!IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack)) {
rep.AddStack(stack, true);
OutputReport(thr, rep);
// Use alloca, because malloc during signal handling deadlocks
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
bool suppressed;
// 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(ReportTypeErrnoInSignal);
rep->SetSigNum(sig);
suppressed = IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack);
if (!suppressed)
rep->AddStack(stack, true);
#if SANITIZER_APPLE
} // Close this scope to release the locks before writing report
#endif
if (!suppressed)
OutputReport(thr, *rep);
#if !SANITIZER_APPLE
}
#endif

// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}

static void CallUserSignalHandler(ThreadState *thr, bool sync, bool acquire,
Expand Down
31 changes: 22 additions & 9 deletions compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,16 +437,29 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
}

static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
ThreadRegistryLock l(&ctx->thread_registry);
ScopedReport rep(ReportTypeMutexHeldWrongContext);
for (uptr i = 0; i < thr->mset.Size(); ++i) {
MutexSet::Desc desc = thr->mset.Get(i);
rep.AddMutex(desc.addr, desc.stack_id);
// 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(ReportTypeMutexHeldWrongContext);
for (uptr i = 0; i < thr->mset.Size(); ++i) {
MutexSet::Desc desc = thr->mset.Get(i);
rep->AddMutex(desc.addr, desc.stack_id);
}
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep->AddStack(trace, true);
#if SANITIZER_APPLE
} // Close this scope to release the locks
OutputReport(thr, *rep);
#else
OutputReport(thr, *rep);
}
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep.AddStack(trace, true);
OutputReport(thr, rep);
#endif
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}

INTERFACE_ATTRIBUTE
Expand Down
21 changes: 17 additions & 4 deletions compiler-rt/lib/tsan/rtl/tsan_mman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,23 @@ static void SignalUnsafeCall(ThreadState *thr, uptr pc) {
ObtainCurrentStack(thr, pc, &stack);
if (IsFiredSuppression(ctx, ReportTypeSignalUnsafe, stack))
return;
ThreadRegistryLock l(&ctx->thread_registry);
ScopedReport rep(ReportTypeSignalUnsafe);
rep.AddStack(stack, true);
OutputReport(thr, rep);
// 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(ReportTypeSignalUnsafe);
rep->AddStack(stack, true);
#if SANITIZER_APPLE
} // Close this scope to release the locks
OutputReport(thr, *rep);
#else
OutputReport(thr, *rep);
}
#endif
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}


Expand Down
5 changes: 5 additions & 0 deletions compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,11 @@ NOINLINE void TraceRestartMemoryAccess(ThreadState* thr, uptr pc, uptr addr,

ALWAYS_INLINE USED void MemoryAccess(ThreadState* thr, uptr pc, uptr addr,
uptr size, AccessType typ) {
#if SANITIZER_APPLE && !SANITIZER_GO
// Swift symbolizer can be intercepted and deadlock without this
if (thr->in_symbolizer)
return;
#endif
RawShadow* shadow_mem = MemToShadow(addr);
UNUSED char memBuf[4][64];
DPrintf2("#%d: Access: %d@%d %p/%zd typ=0x%x {%s, %s, %s, %s}\n", thr->tid,
Expand Down
142 changes: 91 additions & 51 deletions compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
//===----------------------------------------------------------------------===//

#include <sanitizer_common/sanitizer_deadlock_detector_interface.h>
#include <sanitizer_common/sanitizer_placement_new.h>
#include <sanitizer_common/sanitizer_stackdepot.h>

#include "tsan_rtl.h"
#include "tsan_flags.h"
#include "tsan_sync.h"
#include "tsan_platform.h"
#include "tsan_report.h"
#include "tsan_rtl.h"
#include "tsan_symbolize.h"
#include "tsan_platform.h"
#include "tsan_sync.h"

namespace __tsan {

Expand Down Expand Up @@ -55,14 +56,27 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
return;
if (!ShouldReport(thr, typ))
return;
ThreadRegistryLock l(&ctx->thread_registry);
ScopedReport rep(typ);
rep.AddMutex(addr, creation_stack_id);
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep.AddStack(trace, true);
rep.AddLocation(addr, 1);
OutputReport(thr, rep);
// 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(typ);
rep->AddMutex(addr, creation_stack_id);
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep->AddStack(trace, true);
rep->AddLocation(addr, 1);
#if SANITIZER_APPLE
} // Close this scope to release the locks
OutputReport(thr, *rep);
#else
OutputReport(thr, *rep);
}
#endif
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}

static void RecordMutexLock(ThreadState *thr, uptr pc, uptr addr,
Expand Down Expand Up @@ -528,53 +542,79 @@ void AfterSleep(ThreadState *thr, uptr pc) {
void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock))
return;
ThreadRegistryLock l(&ctx->thread_registry);
ScopedReport rep(ReportTypeDeadlock);
for (int i = 0; i < r->n; i++) {
rep.AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
rep.AddUniqueTid((int)r->loop[i].thr_ctx);
rep.AddThread((int)r->loop[i].thr_ctx);
}
uptr dummy_pc = 0x42;
for (int i = 0; i < r->n; i++) {
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
u32 stk = r->loop[i].stk[j];
StackTrace stack;
if (stk && stk != kInvalidStackID) {
stack = StackDepotGet(stk);
} else {
// Sometimes we fail to extract the stack trace (FIXME: investigate),
// but we should still produce some stack trace in the report.
stack = StackTrace(&dummy_pc, 1);
// 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(ReportTypeDeadlock);
for (int i = 0; i < r->n; i++) {
rep->AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
rep->AddUniqueTid((int)r->loop[i].thr_ctx);
rep->AddThread((int)r->loop[i].thr_ctx);
}
uptr dummy_pc = 0x42;
for (int i = 0; i < r->n; i++) {
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
u32 stk = r->loop[i].stk[j];
StackTrace stack;
if (stk && stk != kInvalidStackID) {
stack = StackDepotGet(stk);
} else {
// Sometimes we fail to extract the stack trace (FIXME: investigate),
// but we should still produce some stack trace in the report.
stack = StackTrace(&dummy_pc, 1);
}
rep->AddStack(stack, true);
}
rep.AddStack(stack, true);
}
#if SANITIZER_APPLE
} // Close this scope to release the locks
OutputReport(thr, *rep);
#else
OutputReport(thr, *rep);
}
OutputReport(thr, rep);
#endif
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}

void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
FastState last_lock, StackID creation_stack_id) {
// We need to lock the slot during RestoreStack because it protects
// the slot journal.
Lock slot_lock(&ctx->slots[static_cast<uptr>(last_lock.sid())].mtx);
ThreadRegistryLock l0(&ctx->thread_registry);
Lock slots_lock(&ctx->slot_mtx);
ScopedReport rep(ReportTypeMutexDestroyLocked);
rep.AddMutex(addr, creation_stack_id);
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep.AddStack(trace, true);

Tid tid;
DynamicMutexSet mset;
uptr tag;
if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(), addr,
0, kAccessWrite, &tid, &trace, mset, &tag))
return;
rep.AddStack(trace, true);
rep.AddLocation(addr, 1);
OutputReport(thr, rep);
// 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
{
// We need to lock the slot during RestoreStack because it protects
// the slot journal.
Lock slot_lock(&ctx->slots[static_cast<uptr>(last_lock.sid())].mtx);
ThreadRegistryLock l0(&ctx->thread_registry);
Lock slots_lock(&ctx->slot_mtx);
new (rep) ScopedReport(ReportTypeMutexDestroyLocked);
rep->AddMutex(addr, creation_stack_id);
VarSizeStackTrace trace;
ObtainCurrentStack(thr, pc, &trace);
rep->AddStack(trace, true);

Tid tid;
DynamicMutexSet mset;
uptr tag;
if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(),
addr, 0, kAccessWrite, &tid, &trace, mset, &tag))
return;
rep->AddStack(trace, true);
rep->AddLocation(addr, 1);
#if SANITIZER_APPLE
} // Close this scope to release the locks
OutputReport(thr, *rep);
#else
OutputReport(thr, *rep);
}
#endif
// Need to manually destroy this because we used placement new to allocate
rep->~ScopedReport();
}

} // namespace __tsan
Loading
Loading