Skip to content

Commit f5781a0

Browse files
authored
Merge pull request #11706 from DanBlackwell/pick-tsan-lock-during-write
[🍒][TSan] Add in lock_during_write flag on Apple platforms to avoid deadlock
2 parents 93d5c7c + c6554d9 commit f5781a0

File tree

9 files changed

+161
-1
lines changed

9 files changed

+161
-1
lines changed

compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ extern "C" {
105105
mach_msg_type_number_t *infoCnt);
106106
}
107107

108+
# if !SANITIZER_GO
109+
// Weak symbol no-op when TSan is not linked
110+
SANITIZER_WEAK_ATTRIBUTE extern void __tsan_set_in_internal_write_call(
111+
bool value) {}
112+
# endif
113+
108114
namespace __sanitizer {
109115

110116
#include "sanitizer_syscall_generic.inc"
@@ -175,7 +181,15 @@ uptr internal_read(fd_t fd, void *buf, uptr count) {
175181
}
176182

177183
uptr internal_write(fd_t fd, const void *buf, uptr count) {
184+
# if SANITIZER_GO
178185
return write(fd, buf, count);
186+
# else
187+
// We need to disable interceptors when writing in TSan
188+
__tsan_set_in_internal_write_call(true);
189+
uptr res = write(fd, buf, count);
190+
__tsan_set_in_internal_write_call(false);
191+
return res;
192+
# endif
179193
}
180194

181195
uptr internal_stat(const char *path, void *buf) {

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,43 @@
2020
#include "tsan_rtl.h"
2121
#include "ubsan/ubsan_flags.h"
2222

23+
#if SANITIZER_APPLE && !SANITIZER_GO
24+
namespace __sanitizer {
25+
26+
template <>
27+
inline bool FlagHandler<LockDuringWriteSetting>::Parse(const char *value) {
28+
if (internal_strcmp(value, "on") == 0) {
29+
*t_ = kLockDuringAllWrites;
30+
return true;
31+
}
32+
if (internal_strcmp(value, "disable_for_current_process") == 0) {
33+
*t_ = kNoLockDuringWritesCurrentProcess;
34+
return true;
35+
}
36+
if (internal_strcmp(value, "disable_for_all_processes") == 0) {
37+
*t_ = kNoLockDuringWritesAllProcesses;
38+
return true;
39+
}
40+
Printf("ERROR: Invalid value for signal handler option: '%s'\n", value);
41+
return false;
42+
}
43+
44+
template <>
45+
inline bool FlagHandler<LockDuringWriteSetting>::Format(char *buffer,
46+
uptr size) {
47+
switch (*t_) {
48+
case kLockDuringAllWrites:
49+
return FormatString(buffer, size, "on");
50+
case kNoLockDuringWritesCurrentProcess:
51+
return FormatString(buffer, size, "disable_for_current_process");
52+
case kNoLockDuringWritesAllProcesses:
53+
return FormatString(buffer, size, "disable_for_all_processes");
54+
}
55+
}
56+
57+
} // namespace __sanitizer
58+
#endif // SANITIZER_APPLE && !SANITIZER_GO
59+
2360
namespace __tsan {
2461

2562
// Can be overriden in frontend.

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@
1616
#include "sanitizer_common/sanitizer_flags.h"
1717
#include "sanitizer_common/sanitizer_deadlock_detector_interface.h"
1818

19+
#if SANITIZER_APPLE && !SANITIZER_GO
20+
enum LockDuringWriteSetting {
21+
kLockDuringAllWrites,
22+
kNoLockDuringWritesCurrentProcess,
23+
kNoLockDuringWritesAllProcesses,
24+
};
25+
#endif
26+
1927
namespace __tsan {
2028

2129
struct Flags : DDFlags {

compiler-rt/lib/tsan/rtl/tsan_flags.inc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,15 @@ TSAN_FLAG(bool, shared_ptr_interceptor, true,
8080
TSAN_FLAG(bool, print_full_thread_history, false,
8181
"If set, prints thread creation stacks for the threads involved in "
8282
"the report and their ancestors up to the main thread.")
83+
84+
#if SANITIZER_APPLE && !SANITIZER_GO
85+
TSAN_FLAG(LockDuringWriteSetting, lock_during_write, kLockDuringAllWrites,
86+
"Determines whether to obtain a lock while writing logs or error "
87+
"reports. "
88+
"\"on\" - [default] lock during all writes. "
89+
"\"disable_for_current_process\" - don't lock during all writes in "
90+
"the current process, but do lock for all writes in child "
91+
"processes."
92+
"\"disable_for_all_processes\" - don't lock during all writes in "
93+
"the current process and it's children processes.")
94+
#endif

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#ifndef TSAN_INTERCEPTORS_H
22
#define TSAN_INTERCEPTORS_H
33

4+
#if SANITIZER_APPLE && !SANITIZER_GO
5+
# include "sanitizer_common/sanitizer_mac.h"
6+
#endif
47
#include "sanitizer_common/sanitizer_stacktrace.h"
58
#include "tsan_rtl.h"
69

@@ -43,7 +46,12 @@ inline bool in_symbolizer() {
4346
#endif
4447

4548
inline bool MustIgnoreInterceptor(ThreadState *thr) {
46-
return !thr->is_inited || thr->ignore_interceptors || thr->in_ignored_lib;
49+
return !thr->is_inited || thr->ignore_interceptors || thr->in_ignored_lib
50+
#if SANITIZER_APPLE && !SANITIZER_GO
51+
|| (flags()->lock_during_write != kLockDuringAllWrites &&
52+
thr->in_internal_write_call)
53+
#endif
54+
;
4755
}
4856

4957
} // namespace __tsan

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#include "sanitizer_common/sanitizer_tls_get_addr.h"
3232
#include "sanitizer_common/sanitizer_vector.h"
3333
#include "tsan_fd.h"
34+
#if SANITIZER_APPLE && !SANITIZER_GO
35+
# include "tsan_flags.h"
36+
#endif
3437
#include "tsan_interceptors.h"
3538
#include "tsan_interface.h"
3639
#include "tsan_mman.h"
@@ -1665,6 +1668,14 @@ TSAN_INTERCEPTOR(int, pthread_barrier_wait, void *b) {
16651668

16661669
TSAN_INTERCEPTOR(int, pthread_once, void *o, void (*f)()) {
16671670
SCOPED_INTERCEPTOR_RAW(pthread_once, o, f);
1671+
#if SANITIZER_APPLE && !SANITIZER_GO
1672+
if (flags()->lock_during_write != kLockDuringAllWrites &&
1673+
cur_thread_init()->in_internal_write_call) {
1674+
// This is needed to make it through process launch without hanging
1675+
f();
1676+
return 0;
1677+
}
1678+
#endif
16681679
if (o == 0 || f == 0)
16691680
return errno_EINVAL;
16701681
atomic_uint32_t *a;

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ SANITIZER_WEAK_DEFAULT_IMPL
4040
void __tsan_test_only_on_fork() {}
4141
#endif
4242

43+
#if SANITIZER_APPLE && !SANITIZER_GO
44+
// Override weak symbol from sanitizer_common
45+
extern void __tsan_set_in_internal_write_call(bool value) {
46+
__tsan::cur_thread_init()->in_internal_write_call = value;
47+
}
48+
#endif
49+
4350
namespace __tsan {
4451

4552
#if !SANITIZER_GO
@@ -893,6 +900,13 @@ void ForkChildAfter(ThreadState* thr, uptr pc, bool start_thread) {
893900
ThreadIgnoreBegin(thr, pc);
894901
ThreadIgnoreSyncBegin(thr, pc);
895902
}
903+
904+
# if SANITIZER_APPLE && !SANITIZER_GO
905+
// This flag can have inheritance disabled - we are the child so act
906+
// accordingly
907+
if (flags()->lock_during_write == kNoLockDuringWritesCurrentProcess)
908+
flags()->lock_during_write = kLockDuringAllWrites;
909+
# endif
896910
}
897911
#endif
898912

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ struct alignas(SANITIZER_CACHE_LINE_SIZE) ThreadState {
236236

237237
const ReportDesc *current_report;
238238

239+
#if SANITIZER_APPLE && !SANITIZER_GO
240+
bool in_internal_write_call;
241+
#endif
242+
239243
explicit ThreadState(Tid tid);
240244
};
241245

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Test that dylibs interposing write, and then calling functions intercepted
2+
// by TSan don't deadlock (self-lock)
3+
4+
// RUN: %clang_tsan %s -o %t
5+
// RUN: %clang_tsan %s -o %t.dylib -fno-sanitize=thread -dynamiclib -DSHARED_LIB
6+
7+
// Note that running the below command with out `lock_during_write` should
8+
// deadlock (self-lock)
9+
// RUN: env DYLD_INSERT_LIBRARIES=%t.dylib TSAN_OPTIONS=verbosity=2:lock_during_write=disable_for_current_process %run %t 2>&1 | FileCheck %s
10+
//
11+
// UNSUPPORTED: ios
12+
13+
#include <stdio.h>
14+
15+
#if defined(SHARED_LIB)
16+
17+
// dylib implementation - interposes write() calls
18+
# include <os/lock.h>
19+
# include <unistd.h>
20+
21+
struct interpose_substitution {
22+
const void *replacement;
23+
const void *original;
24+
};
25+
26+
# define INTERPOSE(replacement, original) \
27+
__attribute__((used)) static const struct interpose_substitution \
28+
substitution_##original[] \
29+
__attribute__((section("__DATA, __interpose"))) = { \
30+
{(const void *)(replacement), (const void *)(original)}}
31+
32+
static ssize_t my_write(int fd, const void *buf, size_t count) {
33+
struct os_unfair_lock_s lock = OS_UNFAIR_LOCK_INIT;
34+
os_unfair_lock_lock(&lock);
35+
printf("Interposed write called: fd=%d, count=%zu\n", fd, count);
36+
ssize_t res = write(fd, buf, count);
37+
os_unfair_lock_unlock(&lock);
38+
return res;
39+
}
40+
INTERPOSE(my_write, write);
41+
42+
#else // defined(SHARED_LIB)
43+
44+
int main() {
45+
printf("Write test completed\n");
46+
return 0;
47+
}
48+
49+
#endif // defined(SHARED_LIB)
50+
51+
// CHECK: Interposed write called: fd={{[0-9]+}}, count={{[0-9]+}}
52+
// CHECK: Write test completed

0 commit comments

Comments
 (0)