Skip to content

Commit 06876a4

Browse files
authored
Merge pull request #11303 from DanBlackwell/cherry-pick-tsan-deadlock-fix-apple
🍒[TSan] Fix deadlocks during TSan error reporting on Apple platforms
2 parents d9aab48 + cc660d0 commit 06876a4

File tree

9 files changed

+328
-140
lines changed

9 files changed

+328
-140
lines changed

compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "sanitizer_common/sanitizer_internal_defs.h"
2323
#include "sanitizer_common/sanitizer_libc.h"
2424
#include "sanitizer_common/sanitizer_linux.h"
25+
#include "sanitizer_common/sanitizer_placement_new.h"
2526
#include "sanitizer_common/sanitizer_platform_interceptors.h"
2627
#include "sanitizer_common/sanitizer_platform_limits_netbsd.h"
2728
#include "sanitizer_common/sanitizer_platform_limits_posix.h"
@@ -2141,13 +2142,29 @@ static void ReportErrnoSpoiling(ThreadState *thr, uptr pc, int sig) {
21412142
// StackTrace::GetNestInstructionPc(pc) is used because return address is
21422143
// expected, OutputReport() will undo this.
21432144
ObtainCurrentStack(thr, StackTrace::GetNextInstructionPc(pc), &stack);
2144-
ThreadRegistryLock l(&ctx->thread_registry);
2145-
ScopedReport rep(ReportTypeErrnoInSignal);
2146-
rep.SetSigNum(sig);
2147-
if (!IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack)) {
2148-
rep.AddStack(stack, true);
2149-
OutputReport(thr, rep);
2145+
// Use alloca, because malloc during signal handling deadlocks
2146+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
2147+
bool suppressed;
2148+
// Take a new scope as Apple platforms require the below locks released
2149+
// before symbolizing in order to avoid a deadlock
2150+
{
2151+
ThreadRegistryLock l(&ctx->thread_registry);
2152+
new (rep) ScopedReport(ReportTypeErrnoInSignal);
2153+
rep->SetSigNum(sig);
2154+
suppressed = IsFiredSuppression(ctx, ReportTypeErrnoInSignal, stack);
2155+
if (!suppressed)
2156+
rep->AddStack(stack, true);
2157+
#if SANITIZER_APPLE
2158+
} // Close this scope to release the locks before writing report
2159+
#endif
2160+
if (!suppressed)
2161+
OutputReport(thr, *rep);
2162+
2163+
// Need to manually destroy this because we used placement new to allocate
2164+
rep->~ScopedReport();
2165+
#if !SANITIZER_APPLE
21502166
}
2167+
#endif
21512168
}
21522169

21532170
static void CallUserSignalHandler(ThreadState *thr, bool sync, bool acquire,

compiler-rt/lib/tsan/rtl/tsan_interface_ann.cpp

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,16 +437,30 @@ void __tsan_mutex_post_divert(void *addr, unsigned flagz) {
437437
}
438438

439439
static void ReportMutexHeldWrongContext(ThreadState *thr, uptr pc) {
440-
ThreadRegistryLock l(&ctx->thread_registry);
441-
ScopedReport rep(ReportTypeMutexHeldWrongContext);
442-
for (uptr i = 0; i < thr->mset.Size(); ++i) {
443-
MutexSet::Desc desc = thr->mset.Get(i);
444-
rep.AddMutex(desc.addr, desc.stack_id);
440+
// Use alloca, because malloc during signal handling deadlocks
441+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
442+
// Take a new scope as Apple platforms require the below locks released
443+
// before symbolizing in order to avoid a deadlock
444+
{
445+
ThreadRegistryLock l(&ctx->thread_registry);
446+
new (rep) ScopedReport(ReportTypeMutexHeldWrongContext);
447+
for (uptr i = 0; i < thr->mset.Size(); ++i) {
448+
MutexSet::Desc desc = thr->mset.Get(i);
449+
rep->AddMutex(desc.addr, desc.stack_id);
450+
}
451+
VarSizeStackTrace trace;
452+
ObtainCurrentStack(thr, pc, &trace);
453+
rep->AddStack(trace, true);
454+
#if SANITIZER_APPLE
455+
} // Close this scope to release the locks
456+
#endif
457+
OutputReport(thr, *rep);
458+
459+
// Need to manually destroy this because we used placement new to allocate
460+
rep->~ScopedReport();
461+
#if !SANITIZER_APPLE
445462
}
446-
VarSizeStackTrace trace;
447-
ObtainCurrentStack(thr, pc, &trace);
448-
rep.AddStack(trace, true);
449-
OutputReport(thr, rep);
463+
#endif
450464
}
451465

452466
INTERFACE_ATTRIBUTE

compiler-rt/lib/tsan/rtl/tsan_mman.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,24 @@ static void SignalUnsafeCall(ThreadState *thr, uptr pc) {
182182
ObtainCurrentStack(thr, pc, &stack);
183183
if (IsFiredSuppression(ctx, ReportTypeSignalUnsafe, stack))
184184
return;
185-
ThreadRegistryLock l(&ctx->thread_registry);
186-
ScopedReport rep(ReportTypeSignalUnsafe);
187-
rep.AddStack(stack, true);
188-
OutputReport(thr, rep);
185+
// Use alloca, because malloc during signal handling deadlocks
186+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
187+
// Take a new scope as Apple platforms require the below locks released
188+
// before symbolizing in order to avoid a deadlock
189+
{
190+
ThreadRegistryLock l(&ctx->thread_registry);
191+
new (rep) ScopedReport(ReportTypeSignalUnsafe);
192+
rep->AddStack(stack, true);
193+
#if SANITIZER_APPLE
194+
} // Close this scope to release the locks
195+
#endif
196+
OutputReport(thr, *rep);
197+
198+
// Need to manually destroy this because we used placement new to allocate
199+
rep->~ScopedReport();
200+
#if !SANITIZER_APPLE
201+
}
202+
#endif
189203
}
190204

191205

compiler-rt/lib/tsan/rtl/tsan_report.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#ifndef TSAN_REPORT_H
1313
#define TSAN_REPORT_H
1414

15+
#include "sanitizer_common/sanitizer_internal_defs.h"
16+
#include "sanitizer_common/sanitizer_stacktrace.h"
1517
#include "sanitizer_common/sanitizer_symbolizer.h"
1618
#include "sanitizer_common/sanitizer_thread_registry.h"
1719
#include "sanitizer_common/sanitizer_vector.h"
@@ -56,6 +58,7 @@ struct ReportMop {
5658
bool atomic;
5759
uptr external_tag;
5860
Vector<ReportMopMutex> mset;
61+
StackTrace stack_trace;
5962
ReportStack *stack;
6063

6164
ReportMop();
@@ -79,6 +82,7 @@ struct ReportLocation {
7982
int fd = 0;
8083
bool fd_closed = false;
8184
bool suppressable = false;
85+
StackID stack_id = 0;
8286
ReportStack *stack = nullptr;
8387
};
8488

@@ -89,22 +93,31 @@ struct ReportThread {
8993
ThreadType thread_type;
9094
char *name;
9195
Tid parent_tid;
96+
StackID stack_id;
9297
ReportStack *stack;
98+
bool suppressable;
9399
};
94100

95101
struct ReportMutex {
96102
int id;
97103
uptr addr;
104+
StackID stack_id;
98105
ReportStack *stack;
99106
};
100107

108+
struct AddedLocationAddr {
109+
uptr addr;
110+
usize locs_idx;
111+
};
112+
101113
class ReportDesc {
102114
public:
103115
ReportType typ;
104116
uptr tag;
105117
Vector<ReportStack*> stacks;
106118
Vector<ReportMop*> mops;
107119
Vector<ReportLocation*> locs;
120+
Vector<AddedLocationAddr> added_location_addrs;
108121
Vector<ReportMutex*> mutexes;
109122
Vector<ReportThread*> threads;
110123
Vector<Tid> unique_tids;

compiler-rt/lib/tsan/rtl/tsan_rtl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ class ScopedReportBase {
420420
void AddSleep(StackID stack_id);
421421
void SetCount(int count);
422422
void SetSigNum(int sig);
423+
void SymbolizeStackElems(void);
423424

424425
const ReportDesc *GetReport() const;
425426

@@ -498,7 +499,7 @@ void ForkChildAfter(ThreadState *thr, uptr pc, bool start_thread);
498499

499500
void ReportRace(ThreadState *thr, RawShadow *shadow_mem, Shadow cur, Shadow old,
500501
AccessType typ);
501-
bool OutputReport(ThreadState *thr, const ScopedReport &srep);
502+
bool OutputReport(ThreadState *thr, ScopedReport &srep);
502503
bool IsFiredSuppression(Context *ctx, ReportType type, StackTrace trace);
503504
bool IsExpectedReport(uptr addr, uptr size);
504505

compiler-rt/lib/tsan/rtl/tsan_rtl_access.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ NOINLINE void TraceRestartMemoryAccess(ThreadState* thr, uptr pc, uptr addr,
419419

420420
ALWAYS_INLINE USED void MemoryAccess(ThreadState* thr, uptr pc, uptr addr,
421421
uptr size, AccessType typ) {
422+
#if SANITIZER_APPLE && !SANITIZER_GO
423+
// Swift symbolizer can be intercepted and deadlock without this
424+
if (thr->in_symbolizer)
425+
return;
426+
#endif
422427
RawShadow* shadow_mem = MemToShadow(addr);
423428
UNUSED char memBuf[4][64];
424429
DPrintf2("#%d: Access: %d@%d %p/%zd typ=0x%x {%s, %s, %s, %s}\n", thr->tid,

compiler-rt/lib/tsan/rtl/tsan_rtl_mutex.cpp

Lines changed: 94 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include <sanitizer_common/sanitizer_deadlock_detector_interface.h>
14+
#include <sanitizer_common/sanitizer_placement_new.h>
1415
#include <sanitizer_common/sanitizer_stackdepot.h>
1516

16-
#include "tsan_rtl.h"
1717
#include "tsan_flags.h"
18-
#include "tsan_sync.h"
18+
#include "tsan_platform.h"
1919
#include "tsan_report.h"
20+
#include "tsan_rtl.h"
2021
#include "tsan_symbolize.h"
21-
#include "tsan_platform.h"
22+
#include "tsan_sync.h"
2223

2324
namespace __tsan {
2425

@@ -55,14 +56,28 @@ static void ReportMutexMisuse(ThreadState *thr, uptr pc, ReportType typ,
5556
return;
5657
if (!ShouldReport(thr, typ))
5758
return;
58-
ThreadRegistryLock l(&ctx->thread_registry);
59-
ScopedReport rep(typ);
60-
rep.AddMutex(addr, creation_stack_id);
61-
VarSizeStackTrace trace;
62-
ObtainCurrentStack(thr, pc, &trace);
63-
rep.AddStack(trace, true);
64-
rep.AddLocation(addr, 1);
65-
OutputReport(thr, rep);
59+
// Use alloca, because malloc during signal handling deadlocks
60+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
61+
// Take a new scope as Apple platforms require the below locks released
62+
// before symbolizing in order to avoid a deadlock
63+
{
64+
ThreadRegistryLock l(&ctx->thread_registry);
65+
new (rep) ScopedReport(typ);
66+
rep->AddMutex(addr, creation_stack_id);
67+
VarSizeStackTrace trace;
68+
ObtainCurrentStack(thr, pc, &trace);
69+
rep->AddStack(trace, true);
70+
rep->AddLocation(addr, 1);
71+
#if SANITIZER_APPLE
72+
} // Close this scope to release the locks
73+
#endif
74+
OutputReport(thr, *rep);
75+
76+
// Need to manually destroy this because we used placement new to allocate
77+
rep->~ScopedReport();
78+
#if !SANITIZER_APPLE
79+
}
80+
#endif
6681
}
6782

6883
static void RecordMutexLock(ThreadState *thr, uptr pc, uptr addr,
@@ -528,51 +543,81 @@ void AfterSleep(ThreadState *thr, uptr pc) {
528543
void ReportDeadlock(ThreadState *thr, uptr pc, DDReport *r) {
529544
if (r == 0 || !ShouldReport(thr, ReportTypeDeadlock))
530545
return;
531-
ThreadRegistryLock l(&ctx->thread_registry);
532-
ScopedReport rep(ReportTypeDeadlock);
533-
for (int i = 0; i < r->n; i++) {
534-
rep.AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
535-
rep.AddUniqueTid((int)r->loop[i].thr_ctx);
536-
rep.AddThread((int)r->loop[i].thr_ctx);
537-
}
538-
uptr dummy_pc = 0x42;
539-
for (int i = 0; i < r->n; i++) {
540-
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
541-
u32 stk = r->loop[i].stk[j];
542-
if (stk && stk != kInvalidStackID) {
543-
rep.AddStack(StackDepotGet(stk), true);
544-
} else {
545-
// Sometimes we fail to extract the stack trace (FIXME: investigate),
546-
// but we should still produce some stack trace in the report.
547-
rep.AddStack(StackTrace(&dummy_pc, 1), true);
546+
// Use alloca, because malloc during signal handling deadlocks
547+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
548+
// Take a new scope as Apple platforms require the below locks released
549+
// before symbolizing in order to avoid a deadlock
550+
{
551+
ThreadRegistryLock l(&ctx->thread_registry);
552+
new (rep) ScopedReport(ReportTypeDeadlock);
553+
for (int i = 0; i < r->n; i++) {
554+
rep->AddMutex(r->loop[i].mtx_ctx0, r->loop[i].stk[0]);
555+
rep->AddUniqueTid((int)r->loop[i].thr_ctx);
556+
rep->AddThread((int)r->loop[i].thr_ctx);
557+
}
558+
uptr dummy_pc = 0x42;
559+
for (int i = 0; i < r->n; i++) {
560+
for (int j = 0; j < (flags()->second_deadlock_stack ? 2 : 1); j++) {
561+
u32 stk = r->loop[i].stk[j];
562+
StackTrace stack;
563+
if (stk && stk != kInvalidStackID) {
564+
stack = StackDepotGet(stk);
565+
} else {
566+
// Sometimes we fail to extract the stack trace (FIXME: investigate),
567+
// but we should still produce some stack trace in the report.
568+
stack = StackTrace(&dummy_pc, 1);
569+
}
570+
rep->AddStack(stack, true);
548571
}
549572
}
573+
#if SANITIZER_APPLE
574+
} // Close this scope to release the locks
575+
#endif
576+
OutputReport(thr, *rep);
577+
578+
// Need to manually destroy this because we used placement new to allocate
579+
rep->~ScopedReport();
580+
#if !SANITIZER_APPLE
550581
}
551-
OutputReport(thr, rep);
582+
#endif
552583
}
553584

554585
void ReportDestroyLocked(ThreadState *thr, uptr pc, uptr addr,
555586
FastState last_lock, StackID creation_stack_id) {
556-
// We need to lock the slot during RestoreStack because it protects
557-
// the slot journal.
558-
Lock slot_lock(&ctx->slots[static_cast<uptr>(last_lock.sid())].mtx);
559-
ThreadRegistryLock l0(&ctx->thread_registry);
560-
Lock slots_lock(&ctx->slot_mtx);
561-
ScopedReport rep(ReportTypeMutexDestroyLocked);
562-
rep.AddMutex(addr, creation_stack_id);
563-
VarSizeStackTrace trace;
564-
ObtainCurrentStack(thr, pc, &trace);
565-
rep.AddStack(trace, true);
566-
567-
Tid tid;
568-
DynamicMutexSet mset;
569-
uptr tag;
570-
if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(), addr,
571-
0, kAccessWrite, &tid, &trace, mset, &tag))
572-
return;
573-
rep.AddStack(trace, true);
574-
rep.AddLocation(addr, 1);
575-
OutputReport(thr, rep);
587+
// Use alloca, because malloc during signal handling deadlocks
588+
ScopedReport *rep = (ScopedReport *)__builtin_alloca(sizeof(ScopedReport));
589+
// Take a new scope as Apple platforms require the below locks released
590+
// before symbolizing in order to avoid a deadlock
591+
{
592+
// We need to lock the slot during RestoreStack because it protects
593+
// the slot journal.
594+
Lock slot_lock(&ctx->slots[static_cast<uptr>(last_lock.sid())].mtx);
595+
ThreadRegistryLock l0(&ctx->thread_registry);
596+
Lock slots_lock(&ctx->slot_mtx);
597+
new (rep) ScopedReport(ReportTypeMutexDestroyLocked);
598+
rep->AddMutex(addr, creation_stack_id);
599+
VarSizeStackTrace trace;
600+
ObtainCurrentStack(thr, pc, &trace);
601+
rep->AddStack(trace, true);
602+
603+
Tid tid;
604+
DynamicMutexSet mset;
605+
uptr tag;
606+
if (!RestoreStack(EventType::kLock, last_lock.sid(), last_lock.epoch(),
607+
addr, 0, kAccessWrite, &tid, &trace, mset, &tag))
608+
return;
609+
rep->AddStack(trace, true);
610+
rep->AddLocation(addr, 1);
611+
#if SANITIZER_APPLE
612+
} // Close this scope to release the locks
613+
#endif
614+
OutputReport(thr, *rep);
615+
616+
// Need to manually destroy this because we used placement new to allocate
617+
rep->~ScopedReport();
618+
#if !SANITIZER_APPLE
619+
}
620+
#endif
576621
}
577622

578623
} // namespace __tsan

0 commit comments

Comments
 (0)