forked from OSchip/llvm-project
Move pthread_cond_* interceptors from sanitizer_common with all the ugly hacks to TSan
llvm-svn: 206423
This commit is contained in:
parent
6623e7f94a
commit
aefbec9dfd
|
@ -2648,145 +2648,6 @@ INTERCEPTOR(int, pthread_mutex_unlock, void *m) {
|
|||
#define INIT_PTHREAD_MUTEX_UNLOCK
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_PTHREAD_COND
|
||||
// Problem:
|
||||
// NPTL implementation of pthread_cond has 2 versions (2.2.5 and 2.3.2).
|
||||
// pthread_cond_t has different size in the different versions.
|
||||
// If call new REAL functions for old pthread_cond_t, they will corrupt memory
|
||||
// after pthread_cond_t (old cond is smaller).
|
||||
// If we call old REAL functions for new pthread_cond_t, we will lose some
|
||||
// functionality (e.g. old functions do not support waiting against
|
||||
// CLOCK_REALTIME).
|
||||
// Proper handling would require to have 2 versions of interceptors as well.
|
||||
// But this is messy, in particular requires linker scripts when sanitizer
|
||||
// runtime is linked into a shared library.
|
||||
// Instead we assume we don't have dynamic libraries built against old
|
||||
// pthread (2.2.5 is dated by 2002). And provide legacy_pthread_cond flag
|
||||
// that allows to work with old libraries (but this mode does not support
|
||||
// some features, e.g. pthread_condattr_getpshared).
|
||||
static void *init_cond(void *c, bool force = false) {
|
||||
// sizeof(pthread_cond_t) >= sizeof(uptr) in both versions.
|
||||
// So we allocate additional memory on the side large enough to hold
|
||||
// any pthread_cond_t object. Always call new REAL functions, but pass
|
||||
// the aux object to them.
|
||||
// Note: the code assumes that PTHREAD_COND_INITIALIZER initializes
|
||||
// first word of pthread_cond_t to zero.
|
||||
// It's all relevant only for linux.
|
||||
if (!common_flags()->legacy_pthread_cond)
|
||||
return c;
|
||||
atomic_uintptr_t *p = (atomic_uintptr_t*)c;
|
||||
uptr cond = atomic_load(p, memory_order_acquire);
|
||||
if (!force && cond != 0)
|
||||
return (void*)cond;
|
||||
void *newcond = WRAP(malloc)(pthread_cond_t_sz);
|
||||
internal_memset(newcond, 0, pthread_cond_t_sz);
|
||||
if (atomic_compare_exchange_strong(p, &cond, (uptr)newcond,
|
||||
memory_order_acq_rel))
|
||||
return newcond;
|
||||
WRAP(free)(newcond);
|
||||
return (void*)cond;
|
||||
}
|
||||
|
||||
struct CondMutexUnlockCtx {
|
||||
void *ctx;
|
||||
void *m;
|
||||
};
|
||||
|
||||
static void cond_mutex_unlock(CondMutexUnlockCtx *arg) {
|
||||
COMMON_INTERCEPTOR_MUTEX_LOCK(arg->ctx, arg->m);
|
||||
}
|
||||
|
||||
namespace __sanitizer {
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg);
|
||||
} // namespace __sanitizer
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_init, void *c, void *a) {
|
||||
void *cond = init_cond(c, true);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_init, cond, a);
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, c, sizeof(uptr));
|
||||
return REAL(pthread_cond_init)(cond, a);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) {
|
||||
void *cond = init_cond(c);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_wait, cond, m);
|
||||
COMMON_INTERCEPTOR_MUTEX_UNLOCK(ctx, m);
|
||||
COMMON_INTERCEPTOR_READ_RANGE(ctx, c, sizeof(uptr));
|
||||
CondMutexUnlockCtx arg = {ctx, m};
|
||||
// This ensures that we handle mutex lock even in case of pthread_cancel.
|
||||
// See test/tsan/cond_cancel.cc.
|
||||
int res = __sanitizer::call_pthread_cancel_with_cleanup(
|
||||
(int(*)(void *c, void *m, void *abstime))REAL(pthread_cond_wait),
|
||||
cond, m, 0, (void(*)(void *arg))cond_mutex_unlock, &arg);
|
||||
if (res == errno_EOWNERDEAD)
|
||||
COMMON_INTERCEPTOR_MUTEX_REPAIR(ctx, m);
|
||||
COMMON_INTERCEPTOR_MUTEX_LOCK(ctx, m);
|
||||
return res;
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) {
|
||||
void *cond = init_cond(c);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_timedwait, cond, m, abstime);
|
||||
COMMON_INTERCEPTOR_MUTEX_UNLOCK(ctx, m);
|
||||
COMMON_INTERCEPTOR_READ_RANGE(ctx, c, sizeof(uptr));
|
||||
CondMutexUnlockCtx arg = {ctx, m};
|
||||
// This ensures that we handle mutex lock even in case of pthread_cancel.
|
||||
// See test/tsan/cond_cancel.cc.
|
||||
int res = __sanitizer::call_pthread_cancel_with_cleanup(
|
||||
REAL(pthread_cond_timedwait), cond, m, abstime,
|
||||
(void(*)(void *arg))cond_mutex_unlock, &arg);
|
||||
if (res == errno_EOWNERDEAD)
|
||||
COMMON_INTERCEPTOR_MUTEX_REPAIR(ctx, m);
|
||||
COMMON_INTERCEPTOR_MUTEX_LOCK(ctx, m);
|
||||
return res;
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_signal, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_signal, cond);
|
||||
COMMON_INTERCEPTOR_READ_RANGE(ctx, c, sizeof(uptr));
|
||||
return REAL(pthread_cond_signal)(cond);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_broadcast, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_broadcast, cond);
|
||||
COMMON_INTERCEPTOR_READ_RANGE(ctx, c, sizeof(uptr));
|
||||
return REAL(pthread_cond_broadcast)(cond);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_destroy, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
void *ctx;
|
||||
COMMON_INTERCEPTOR_ENTER(ctx, pthread_cond_destroy, cond);
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, c, sizeof(uptr));
|
||||
int res = REAL(pthread_cond_destroy)(cond);
|
||||
if (common_flags()->legacy_pthread_cond) {
|
||||
// Free our aux cond and zero the pointer to not leave dangling pointers.
|
||||
WRAP(free)(cond);
|
||||
atomic_store((atomic_uintptr_t*)c, 0, memory_order_relaxed);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#define INIT_PTHREAD_COND \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_init, "GLIBC_2.3.2"); \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_signal, "GLIBC_2.3.2"); \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_broadcast, "GLIBC_2.3.2"); \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_wait, "GLIBC_2.3.2"); \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_timedwait, "GLIBC_2.3.2"); \
|
||||
INTERCEPT_FUNCTION_VER(pthread_cond_destroy, "GLIBC_2.3.2")
|
||||
#else
|
||||
#define INIT_PTHREAD_COND
|
||||
#endif
|
||||
|
||||
#if SANITIZER_INTERCEPT_GETMNTENT || SANITIZER_INTERCEPT_GETMNTENT_R
|
||||
static void write_mntent(void *ctx, __sanitizer_mntent *mnt) {
|
||||
COMMON_INTERCEPTOR_WRITE_RANGE(ctx, mnt, sizeof(*mnt));
|
||||
|
@ -3934,7 +3795,6 @@ INTERCEPTOR(int, xdr_string, __sanitizer_XDR *xdrs, char **p,
|
|||
INIT__EXIT; \
|
||||
INIT_PTHREAD_MUTEX_LOCK; \
|
||||
INIT_PTHREAD_MUTEX_UNLOCK; \
|
||||
INIT_PTHREAD_COND; \
|
||||
INIT_GETMNTENT; \
|
||||
INIT_GETMNTENT_R; \
|
||||
INIT_STATFS; \
|
||||
|
|
|
@ -527,18 +527,6 @@ void SetIndirectCallWrapper(uptr wrapper) {
|
|||
indirect_call_wrapper = wrapper;
|
||||
}
|
||||
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg) {
|
||||
// pthread_cleanup_push/pop are hardcore macros mess.
|
||||
// We can't intercept nor call them w/o including pthread.h.
|
||||
int res;
|
||||
pthread_cleanup_push(cleanup, arg);
|
||||
res = fn(c, m, abstime);
|
||||
pthread_cleanup_pop(0);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
#endif // SANITIZER_FREEBSD || SANITIZER_LINUX
|
||||
|
|
|
@ -302,18 +302,6 @@ MacosVersion GetMacosVersion() {
|
|||
return result;
|
||||
}
|
||||
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg) {
|
||||
// pthread_cleanup_push/pop are hardcore macros mess.
|
||||
// We can't intercept nor call them w/o including pthread.h.
|
||||
int res;
|
||||
pthread_cleanup_push(cleanup, arg);
|
||||
res = fn(c, m, abstime);
|
||||
pthread_cleanup_pop(0);
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace __sanitizer
|
||||
|
||||
#endif // SANITIZER_MAC
|
||||
|
|
|
@ -181,7 +181,6 @@
|
|||
#define SANITIZER_INTERCEPT__EXIT SI_LINUX
|
||||
|
||||
#define SANITIZER_INTERCEPT_PHTREAD_MUTEX SI_NOT_WINDOWS
|
||||
#define SANITIZER_INTERCEPT_PTHREAD_COND SI_NOT_WINDOWS
|
||||
#define SANITIZER_INTERCEPT_PTHREAD_SETNAME_NP SI_LINUX_NOT_ANDROID
|
||||
|
||||
#define SANITIZER_INTERCEPT_TLS_GET_ADDR SI_LINUX_NOT_ANDROID
|
||||
|
|
|
@ -213,6 +213,7 @@ ScopedInterceptor::~ScopedInterceptor() {
|
|||
|
||||
#define TSAN_INTERCEPTOR(ret, func, ...) INTERCEPTOR(ret, func, __VA_ARGS__)
|
||||
#define TSAN_INTERCEPT(func) INTERCEPT_FUNCTION(func)
|
||||
#define TSAN_INTERCEPT_VER(func, ver) INTERCEPT_FUNCTION_VER(func, ver)
|
||||
|
||||
#define BLOCK_REAL(name) (BlockingCall(thr), REAL(name))
|
||||
|
||||
|
@ -915,6 +916,122 @@ TSAN_INTERCEPTOR(int, pthread_detach, void *th) {
|
|||
return res;
|
||||
}
|
||||
|
||||
// Problem:
|
||||
// NPTL implementation of pthread_cond has 2 versions (2.2.5 and 2.3.2).
|
||||
// pthread_cond_t has different size in the different versions.
|
||||
// If call new REAL functions for old pthread_cond_t, they will corrupt memory
|
||||
// after pthread_cond_t (old cond is smaller).
|
||||
// If we call old REAL functions for new pthread_cond_t, we will lose some
|
||||
// functionality (e.g. old functions do not support waiting against
|
||||
// CLOCK_REALTIME).
|
||||
// Proper handling would require to have 2 versions of interceptors as well.
|
||||
// But this is messy, in particular requires linker scripts when sanitizer
|
||||
// runtime is linked into a shared library.
|
||||
// Instead we assume we don't have dynamic libraries built against old
|
||||
// pthread (2.2.5 is dated by 2002). And provide legacy_pthread_cond flag
|
||||
// that allows to work with old libraries (but this mode does not support
|
||||
// some features, e.g. pthread_condattr_getpshared).
|
||||
static void *init_cond(void *c, bool force = false) {
|
||||
// sizeof(pthread_cond_t) >= sizeof(uptr) in both versions.
|
||||
// So we allocate additional memory on the side large enough to hold
|
||||
// any pthread_cond_t object. Always call new REAL functions, but pass
|
||||
// the aux object to them.
|
||||
// Note: the code assumes that PTHREAD_COND_INITIALIZER initializes
|
||||
// first word of pthread_cond_t to zero.
|
||||
// It's all relevant only for linux.
|
||||
if (!common_flags()->legacy_pthread_cond)
|
||||
return c;
|
||||
atomic_uintptr_t *p = (atomic_uintptr_t*)c;
|
||||
uptr cond = atomic_load(p, memory_order_acquire);
|
||||
if (!force && cond != 0)
|
||||
return (void*)cond;
|
||||
void *newcond = WRAP(malloc)(pthread_cond_t_sz);
|
||||
internal_memset(newcond, 0, pthread_cond_t_sz);
|
||||
if (atomic_compare_exchange_strong(p, &cond, (uptr)newcond,
|
||||
memory_order_acq_rel))
|
||||
return newcond;
|
||||
WRAP(free)(newcond);
|
||||
return (void*)cond;
|
||||
}
|
||||
|
||||
struct CondMutexUnlockCtx {
|
||||
ThreadState *thr;
|
||||
uptr pc;
|
||||
void *m;
|
||||
};
|
||||
|
||||
static void cond_mutex_unlock(CondMutexUnlockCtx *arg) {
|
||||
MutexLock(arg->thr, arg->pc, (uptr)arg->m);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_init, void *c, void *a) {
|
||||
void *cond = init_cond(c, true);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, cond, a);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), true);
|
||||
return REAL(pthread_cond_init)(cond, a);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) {
|
||||
void *cond = init_cond(c);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_wait, cond, m);
|
||||
MutexUnlock(thr, pc, (uptr)m);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false);
|
||||
CondMutexUnlockCtx arg = {thr, pc, m};
|
||||
// This ensures that we handle mutex lock even in case of pthread_cancel.
|
||||
// See test/tsan/cond_cancel.cc.
|
||||
int res = call_pthread_cancel_with_cleanup(
|
||||
(int(*)(void *c, void *m, void *abstime))REAL(pthread_cond_wait),
|
||||
cond, m, 0, (void(*)(void *arg))cond_mutex_unlock, &arg);
|
||||
if (res == errno_EOWNERDEAD)
|
||||
MutexRepair(thr, pc, (uptr)m);
|
||||
MutexLock(thr, pc, (uptr)m);
|
||||
return res;
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) {
|
||||
void *cond = init_cond(c);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_timedwait, cond, m, abstime);
|
||||
MutexUnlock(thr, pc, (uptr)m);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false);
|
||||
CondMutexUnlockCtx arg = {thr, pc, m};
|
||||
// This ensures that we handle mutex lock even in case of pthread_cancel.
|
||||
// See test/tsan/cond_cancel.cc.
|
||||
int res = call_pthread_cancel_with_cleanup(
|
||||
REAL(pthread_cond_timedwait), cond, m, abstime,
|
||||
(void(*)(void *arg))cond_mutex_unlock, &arg);
|
||||
if (res == errno_EOWNERDEAD)
|
||||
MutexRepair(thr, pc, (uptr)m);
|
||||
MutexLock(thr, pc, (uptr)m);
|
||||
return res;
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_signal, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_signal, cond);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false);
|
||||
return REAL(pthread_cond_signal)(cond);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_broadcast, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_broadcast, cond);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), false);
|
||||
return REAL(pthread_cond_broadcast)(cond);
|
||||
}
|
||||
|
||||
INTERCEPTOR(int, pthread_cond_destroy, void *c) {
|
||||
void *cond = init_cond(c);
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, cond);
|
||||
MemoryAccessRange(thr, pc, (uptr)c, sizeof(uptr), true);
|
||||
int res = REAL(pthread_cond_destroy)(cond);
|
||||
if (common_flags()->legacy_pthread_cond) {
|
||||
// Free our aux cond and zero the pointer to not leave dangling pointers.
|
||||
WRAP(free)(cond);
|
||||
atomic_store((atomic_uintptr_t*)c, 0, memory_order_relaxed);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
TSAN_INTERCEPTOR(int, pthread_mutex_init, void *m, void *a) {
|
||||
SCOPED_TSAN_INTERCEPTOR(pthread_mutex_init, m, a);
|
||||
int res = REAL(pthread_mutex_init)(m, a);
|
||||
|
@ -2209,6 +2326,13 @@ void InitializeInterceptors() {
|
|||
TSAN_INTERCEPT(pthread_join);
|
||||
TSAN_INTERCEPT(pthread_detach);
|
||||
|
||||
TSAN_INTERCEPT_VER(pthread_cond_init, "GLIBC_2.3.2");
|
||||
TSAN_INTERCEPT_VER(pthread_cond_signal, "GLIBC_2.3.2");
|
||||
TSAN_INTERCEPT_VER(pthread_cond_broadcast, "GLIBC_2.3.2");
|
||||
TSAN_INTERCEPT_VER(pthread_cond_wait, "GLIBC_2.3.2");
|
||||
TSAN_INTERCEPT_VER(pthread_cond_timedwait, "GLIBC_2.3.2");
|
||||
TSAN_INTERCEPT_VER(pthread_cond_destroy, "GLIBC_2.3.2");
|
||||
|
||||
TSAN_INTERCEPT(pthread_mutex_init);
|
||||
TSAN_INTERCEPT(pthread_mutex_destroy);
|
||||
TSAN_INTERCEPT(pthread_mutex_trylock);
|
||||
|
|
|
@ -165,6 +165,10 @@ bool IsGlobalVar(uptr addr);
|
|||
int ExtractResolvFDs(void *state, int *fds, int nfd);
|
||||
int ExtractRecvmsgFDs(void *msg, int *fds, int nfd);
|
||||
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg);
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
#else // defined(__LP64__) || defined(_WIN64)
|
||||
|
|
|
@ -358,8 +358,19 @@ int ExtractRecvmsgFDs(void *msgp, int *fds, int nfd) {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg) {
|
||||
// pthread_cleanup_push/pop are hardcore macros mess.
|
||||
// We can't intercept nor call them w/o including pthread.h.
|
||||
int res;
|
||||
pthread_cleanup_push(cleanup, arg);
|
||||
res = fn(c, m, abstime);
|
||||
pthread_cleanup_pop(0);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
|
|
|
@ -91,6 +91,20 @@ void FinalizePlatform() {
|
|||
fflush(0);
|
||||
}
|
||||
|
||||
#ifndef TSAN_GO
|
||||
int call_pthread_cancel_with_cleanup(int(*fn)(void *c, void *m,
|
||||
void *abstime), void *c, void *m, void *abstime,
|
||||
void(*cleanup)(void *arg), void *arg) {
|
||||
// pthread_cleanup_push/pop are hardcore macros mess.
|
||||
// We can't intercept nor call them w/o including pthread.h.
|
||||
int res;
|
||||
pthread_cleanup_push(cleanup, arg);
|
||||
res = fn(c, m, abstime);
|
||||
pthread_cleanup_pop(0);
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace __tsan
|
||||
|
||||
#endif // SANITIZER_MAC
|
||||
|
|
Loading…
Reference in New Issue