https://github.com/naveen-seth created https://github.com/llvm/llvm-project/pull/212748
This introduces a lightweight ThreadSanitizer submode that focuses exclusively on lock correctness validation, enabled by `-fsanitize=thread-deadlock`. The motivation behind this is to allow lock-order inversion and mutex misuse detection without enabling data-race detection. This is useful for: - Programs that exceed regular TSan's shadow memory address space constraints - Combining with other sanitizers incompatible with TSan because of the shadow memory mappings. (This works with MSan.) - Platforms where regular TSan is not supported - Users who are only interested in lock-correctness but not in data-races. This is implemented as a separate standalone runtime, as the regular TSan runtime intertwines its lock tracking with SyncVar objects in shadow memory. A follow up patch will remove the old tsan/dd, which was never wired to the compiler and is effectively unmaintained/dead code. This follows the RFC: https://discourse.llvm.org/t/rfc-add-update-tsan-submode-for-lock-correctness-validation-only/91319?u=naveen-seth >From c3a0d70e4ed1f625c585f8f9de7825ab1a92372e Mon Sep 17 00:00:00 2001 From: Naveen Seth Hanig <[email protected]> Date: Wed, 29 Jul 2026 13:20:20 +0200 Subject: [PATCH] [tsan] Revive TSAN's standalone deadlock sanitizer implementation This introduces a lightweight ThreadSanitizer submode that focuses exclusively on lock correctness validation, enabled by `-fsanitize=thread-deadlock`. The motivation behind this is to allow lock-order inversion and mutex misuse detection without enabling data-race detection. This is useful for: - Programs that exceed regular TSan's shadow memory address space constraints - Combining with other sanitizers incompatible with TSan - Platforms where regular TSan is not supported - Users who are only interested in lock-correctness but not in data-races. This is implemented as a separate standalone runtime, as the regular TSan runtime intertwines its lock tracking with SyncVar objects in shadow memory. A follow up patch will remove the old tsan/dd, which was never wired to the compiler and is effectively unmaintained/dead code. This follows the RFC: https://discourse.llvm.org/t/rfc-add-update-tsan-submode-for-lock-correctness-validation-only/91319?u=naveen-seth --- clang/include/clang/Basic/Sanitizers.def | 3 + clang/include/clang/Driver/SanitizerArgs.h | 3 + clang/lib/CodeGen/BackendUtil.cpp | 9 + clang/lib/Driver/SanitizerArgs.cpp | 4 +- clang/lib/Driver/ToolChains/CommonArgs.cpp | 4 + clang/lib/Driver/ToolChains/Linux.cpp | 1 + .../cmake/Modules/AllSupportedArchDefs.cmake | 1 + compiler-rt/cmake/config-ix.cmake | 10 +- .../sanitizer_internal_defs.h | 3 + compiler-rt/lib/tsan_deadlock/CMakeLists.txt | 76 ++++ .../tsan_deadlock_interceptors.cpp | 398 ++++++++++++++++++ .../tsan_deadlock/tsan_deadlock_interface.cpp | 36 ++ .../lib/tsan_deadlock/tsan_deadlock_rtl.cpp | 339 +++++++++++++++ .../lib/tsan_deadlock/tsan_deadlock_rtl.h | 102 +++++ compiler-rt/test/tsan_deadlock/CMakeLists.txt | 29 ++ compiler-rt/test/tsan_deadlock/lit.cfg.py | 47 +++ .../test/tsan_deadlock/lit.site.cfg.py.in | 12 + .../test/tsan_deadlock/mutex_bad_unlock.cpp | 26 ++ .../test/tsan_deadlock/mutex_cycle2.cpp | 31 ++ .../test/tsan_deadlock/mutex_cycle_long.c | 44 ++ .../tsan_deadlock/mutex_destroy_locked.cpp | 20 + .../tsan_deadlock/mutex_destroy_locked2.cpp | 27 ++ .../test/tsan_deadlock/mutex_double_lock.cpp | 17 + .../Instrumentation/ThreadSanitizer.h | 16 +- .../Instrumentation/ThreadSanitizer.cpp | 19 +- 25 files changed, 1267 insertions(+), 10 deletions(-) create mode 100644 compiler-rt/lib/tsan_deadlock/CMakeLists.txt create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h create mode 100644 compiler-rt/test/tsan_deadlock/CMakeLists.txt create mode 100644 compiler-rt/test/tsan_deadlock/lit.cfg.py create mode 100644 compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in create mode 100644 compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle_long.c create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp create mode 100644 compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def index da85431625026..fcb43dc31fcdc 100644 --- a/clang/include/clang/Basic/Sanitizers.def +++ b/clang/include/clang/Basic/Sanitizers.def @@ -79,6 +79,9 @@ SANITIZER("type", Type) // ThreadSanitizer SANITIZER("thread", Thread) +// DeadlockSanitizer (ThreadSanitizer, with only the deadlock detection.) +SANITIZER("thread-deadlock", ThreadDeadlock) + // Numerical stability sanitizer. SANITIZER("numerical", NumericalStability) diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 6a01b3e36d44c..7b7f738d91131 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -104,6 +104,9 @@ class SanitizerArgs { } bool needsTysanRt() const { return Sanitizers.has(SanitizerKind::Type); } bool needsTsanRt() const { return Sanitizers.has(SanitizerKind::Thread); } + bool needsDeadlockRt() const { + return Sanitizers.has(SanitizerKind::ThreadDeadlock); + } bool needsMsanRt() const { return Sanitizers.has(SanitizerKind::Memory); } bool needsFuzzer() const { return Sanitizers.has(SanitizerKind::Fuzzer); } bool needsLsanRt() const { diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index 2b755fa916e55..ce9740554a348 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -741,6 +741,15 @@ static void addSanitizers(const Triple &TargetTriple, MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass())); } + if (LangOpts.Sanitize.has(SanitizerKind::ThreadDeadlock)) { + MPM.addPass(ModuleThreadSanitizerPass()); + MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass( + ThreadSanitizerOptions{/*InstrumentMemoryAccesses=*/false, + /*InstrumentAtomics=*/false, + /*InstrumentMemIntrinsics=*/false, + /*AlwaysInstrumentFuncEntryExit=*/true}))); + } + if (LangOpts.Sanitize.has(SanitizerKind::Type)) MPM.addPass(TypeSanitizerPass()); diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index c77ba78122a81..3c3bc84eacd3f 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -40,7 +40,8 @@ static const SanitizerMask NotAllowedWithExecuteOnly = SanitizerKind::Function | SanitizerKind::KCFI; static const SanitizerMask NeedsUnwindTables = SanitizerKind::Address | SanitizerKind::HWAddress | SanitizerKind::Type | - SanitizerKind::Thread | SanitizerKind::Memory | SanitizerKind::DataFlow | + SanitizerKind::Thread | SanitizerKind::ThreadDeadlock | + SanitizerKind::Memory | SanitizerKind::DataFlow | SanitizerKind::NumericalStability; static const SanitizerMask SupportsCoverage = SanitizerKind::Address | SanitizerKind::HWAddress | @@ -707,6 +708,7 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, SanitizerKind::Memory | SanitizerKind::Leak | SanitizerKind::Thread), std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory), + std::make_pair(SanitizerKind::Thread, SanitizerKind::ThreadDeadlock), std::make_pair(SanitizerKind::Leak, SanitizerKind::Thread | SanitizerKind::Memory), std::make_pair(SanitizerKind::KernelAddress, diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp index 08c06951cf220..d7acfacd17b05 100644 --- a/clang/lib/Driver/ToolChains/CommonArgs.cpp +++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp @@ -1655,6 +1655,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, } if (SanArgs.needsTsanRt()) SharedRuntimes.push_back("tsan"); + if (SanArgs.needsDeadlockRt()) + SharedRuntimes.push_back("tsan_deadlock"); if (SanArgs.needsTysanRt()) SharedRuntimes.push_back("tysan"); if (SanArgs.needsHwasanRt()) { @@ -1729,6 +1731,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args, if (SanArgs.linkCXXRuntimes()) StaticRuntimes.push_back("tsan_cxx"); } + if (!SanArgs.needsSharedRt() && SanArgs.needsDeadlockRt()) + StaticRuntimes.push_back("tsan_deadlock"); if (!SanArgs.needsSharedRt() && SanArgs.needsTysanRt()) StaticRuntimes.push_back("tysan"); if (!SanArgs.needsSharedRt() && SanArgs.needsUbsanRt()) { diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp index 6dca14d8bf0a8..ab9257362cea1 100644 --- a/clang/lib/Driver/ToolChains/Linux.cpp +++ b/clang/lib/Driver/ToolChains/Linux.cpp @@ -1001,6 +1001,7 @@ Linux::getSupportedSanitizers(BoundArch BA, if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ || IsLoongArch64 || IsRISCV64) Res |= SanitizerKind::Thread; + Res |= SanitizerKind::ThreadDeadlock; if (IsX86_64 || IsAArch64 || IsSystemZ || IsHexagon) Res |= SanitizerKind::Type; if (IsX86_64 || IsSystemZ || IsPowerPC64) diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake index 9c9874d94a1f2..5eebb98cda68a 100644 --- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake +++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake @@ -52,6 +52,7 @@ set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32}) set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64} ${S390X}) set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64} ${HEXAGON}) +set(ALL_TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_SANITIZER_COMMON_SUPPORTED_ARCH}) if(ANDROID) set(OS_NAME "Android") diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake index 083f1c98d0f16..d0ef92ed6d446 100644 --- a/compiler-rt/cmake/config-ix.cmake +++ b/compiler-rt/cmake/config-ix.cmake @@ -476,6 +476,7 @@ if(APPLE) set(SANITIZER_COMMON_SUPPORTED_OS osx) set(PROFILE_SUPPORTED_OS osx) set(TSAN_SUPPORTED_OS osx) + set(TSAN_DEADLOCK_SUPPORTED_OS osx) set(TYSAN_SUPPORTED_OS osx) set(XRAY_SUPPORTED_OS osx) set(FUZZER_SUPPORTED_OS osx) @@ -575,6 +576,7 @@ if(APPLE) list(APPEND SANITIZER_COMMON_SUPPORTED_OS ${platform}sim) list(APPEND PROFILE_SUPPORTED_OS ${platform}sim) list(APPEND TSAN_SUPPORTED_OS ${platform}sim) + list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform}sim) list(APPEND FUZZER_SUPPORTED_OS ${platform}sim) list(APPEND ORC_SUPPORTED_OS ${platform}sim) list(APPEND UBSAN_SUPPORTED_OS ${platform}sim) @@ -608,6 +610,7 @@ if(APPLE) list_intersect(DARWIN_${platform}_TSAN_ARCHS DARWIN_${platform}_ARCHS ALL_TSAN_SUPPORTED_ARCH) if(DARWIN_${platform}_TSAN_ARCHS) list(APPEND TSAN_SUPPORTED_OS ${platform}) + list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform}) endif() list(APPEND FUZZER_SUPPORTED_OS ${platform}) list(APPEND ORC_SUPPORTED_OS ${platform}) @@ -677,6 +680,9 @@ if(APPLE) list_intersect(TSAN_SUPPORTED_ARCH ALL_TSAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) + list_intersect(TSAN_DEADLOCK_SUPPORTED_ARCH + ALL_TSAN_DEADLOCK_SUPPORTED_ARCH + SANITIZER_COMMON_SUPPORTED_ARCH) list_intersect(UBSAN_SUPPORTED_ARCH ALL_UBSAN_SUPPORTED_ARCH SANITIZER_COMMON_SUPPORTED_ARCH) @@ -726,6 +732,7 @@ else() filter_available_targets(PROFILE_SUPPORTED_ARCH ${ALL_PROFILE_SUPPORTED_ARCH}) filter_available_targets(CTX_PROFILE_SUPPORTED_ARCH ${ALL_CTX_PROFILE_SUPPORTED_ARCH}) filter_available_targets(TSAN_SUPPORTED_ARCH ${ALL_TSAN_SUPPORTED_ARCH}) + filter_available_targets(TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_TSAN_DEADLOCK_SUPPORTED_ARCH}) filter_available_targets(TYSAN_SUPPORTED_ARCH ${ALL_TYSAN_SUPPORTED_ARCH}) filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH}) filter_available_targets(SAFESTACK_SUPPORTED_ARCH @@ -765,7 +772,7 @@ if(COMPILER_RT_SUPPORTED_ARCH) endif() message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}") -set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi) +set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tsan_deadlock;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi) set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING "sanitizers to build if supported on the target (all;${ALL_SANITIZERS})") list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}") @@ -879,6 +886,7 @@ if (COMPILER_RT_HAS_SANITIZER_COMMON AND TSAN_SUPPORTED_ARCH) else() set(COMPILER_RT_HAS_TSAN FALSE) endif() +set(COMPILER_RT_HAS_TSAN_DEADLOCK ${COMPILER_RT_HAS_TSAN}) if (OS_NAME MATCHES "Linux|FreeBSD|Windows|NetBSD|SunOS") set(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME TRUE) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h index c694897b6556b..8d30a4e2f044c 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h @@ -500,5 +500,8 @@ using namespace __sanitizer; namespace __memprof { using namespace __sanitizer; } +namespace __tsan_deadlock { +using namespace __sanitizer; +} #endif // SANITIZER_DEFS_H diff --git a/compiler-rt/lib/tsan_deadlock/CMakeLists.txt b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt new file mode 100644 index 0000000000000..74fc0f8bdb1cb --- /dev/null +++ b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt @@ -0,0 +1,76 @@ +# Build for the DeadlockSanitizer runtime support library. + +include_directories(..) + +set(TSAN_DEADLOCK_SOURCES + tsan_deadlock_rtl.cpp + tsan_deadlock_interceptors.cpp + tsan_deadlock_interface.cpp) + +set(TSAN_DEADLOCK_HEADERS + tsan_deadlock_rtl.h) + +set(TSAN_DEADLOCK_CFLAGS ${SANITIZER_COMMON_CFLAGS}) +append_rtti_flag(OFF TSAN_DEADLOCK_CFLAGS) + +set(TSAN_DEADLOCK_DYNAMIC_LINK_LIBS + ${COMPILER_RT_UNWINDER_LINK_LIBS} + ${SANITIZER_CXX_ABI_LIBRARIES} + ${SANITIZER_COMMON_LINK_LIBS}) + +append_list_if(COMPILER_RT_HAS_LIBDL dl TSAN_DEADLOCK_DYNAMIC_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBM m TSAN_DEADLOCK_DYNAMIC_LINK_LIBS) +append_list_if(COMPILER_RT_HAS_LIBPTHREAD pthread TSAN_DEADLOCK_DYNAMIC_LINK_LIBS) + +add_compiler_rt_component(tsan_deadlock) + +if(APPLE) + add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS) + + add_compiler_rt_runtime(clang_rt.tsan_deadlock + SHARED + OS ${TSAN_DEADLOCK_SUPPORTED_OS} + ARCHS ${TSAN_DEADLOCK_SUPPORTED_ARCH} + SOURCES ${TSAN_DEADLOCK_SOURCES} + ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS} + OBJECT_LIBS RTInterception + RTSanitizerCommon + RTSanitizerCommonLibc + RTSanitizerCommonCoverage + RTSanitizerCommonSymbolizer + CFLAGS ${TSAN_DEADLOCK_CFLAGS} + LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS} + LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS} + PARENT_TARGET tsan_deadlock) +else() + foreach(arch ${TSAN_DEADLOCK_SUPPORTED_ARCH}) + add_compiler_rt_runtime(clang_rt.tsan_deadlock + STATIC + ARCHS ${arch} + SOURCES ${TSAN_DEADLOCK_SOURCES} + $<TARGET_OBJECTS:RTInterception.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommon.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}> + ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS} + CFLAGS ${TSAN_DEADLOCK_CFLAGS} + PARENT_TARGET tsan_deadlock) + add_compiler_rt_runtime(clang_rt.tsan_deadlock + SHARED + ARCHS ${arch} + SOURCES ${TSAN_DEADLOCK_SOURCES} + $<TARGET_OBJECTS:RTInterception.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommon.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}> + $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}> + ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS} + CFLAGS ${TSAN_DEADLOCK_CFLAGS} + LINK_LIBS ${TSAN_DEADLOCK_DYNAMIC_LINK_LIBS} + LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} + PARENT_TARGET tsan_deadlock) + endforeach() +endif() diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp new file mode 100644 index 0000000000000..51ed3a22e2d9d --- /dev/null +++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp @@ -0,0 +1,398 @@ +//===-- tsan_deadlock_interceptors.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 DeadlockSanitizer. +// +//===----------------------------------------------------------------------===// + +#include <pthread.h> + +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_allocator_internal.h" +#include "sanitizer_common/sanitizer_errno.h" +#include "sanitizer_common/sanitizer_glibc_version.h" +#include "sanitizer_common/sanitizer_stacktrace.h" +#include "tsan_deadlock_rtl.h" + +using namespace __tsan_deadlock; + +__attribute__((tls_model("initial-exec"))) static __thread volatile int initing; +static bool inited; + +static bool InitThread() { + if (initing) + return false; + if (cur_thread()) + return true; + initing = true; + if (!inited) { + inited = true; + Initialize(); + } + ThreadInit(&thr_tls); + initing = false; + return true; +} + +struct ThreadArg { + void *(*fn)(void *); + void *arg; +}; + +static void *ThreadTrampoline(void *arg) { + ThreadArg *targ = static_cast<ThreadArg *>(arg); + void *(*fn)(void *) = targ->fn; + void *fn_arg = targ->arg; + InternalFree(targ); + ThreadInit(&thr_tls); + void *retval = fn(fn_arg); + // Also called from the pthread_exit interceptor; guard with is_inited to + // stay idempotent. + if (thr_tls.is_inited) + ThreadDestroy(&thr_tls); + return retval; +} + +INTERCEPTOR(int, pthread_create, pthread_t *th, const pthread_attr_t *attr, + void *(*fn)(void *), void *arg) { + InitThread(); + ThreadArg *targ = static_cast<ThreadArg *>(InternalAlloc(sizeof(ThreadArg))); + targ->fn = fn; + targ->arg = arg; + return REAL(pthread_create)(th, attr, ThreadTrampoline, targ); +} + +INTERCEPTOR(int, pthread_join, pthread_t t, void **retval) { + InitThread(); + return REAL(pthread_join)(t, retval); +} + +INTERCEPTOR(void, pthread_exit, void *retval) { + if (thr_tls.is_inited) + ThreadDestroy(&thr_tls); + REAL(pthread_exit)(retval); +} + +INTERCEPTOR(int, pthread_mutex_init, pthread_mutex_t *m, + const pthread_mutexattr_t *attr) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_mutex_init)(m, attr); + if (res == 0) { + bool reentrant = false; + if (attr) { + int type = 0; + if (pthread_mutexattr_gettype(attr, &type) == 0) + reentrant = (type == PTHREAD_MUTEX_RECURSIVE); + } + MutexInit(cur_thread(), (uptr)m, reentrant, pc); + } + return res; +} + +INTERCEPTOR(int, pthread_mutex_destroy, pthread_mutex_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_mutex_destroy)(m); + if (res == 0 || res == errno_EBUSY) + MutexDestroy(cur_thread(), (uptr)m, pc); + return res; +} + +INTERCEPTOR(int, pthread_mutex_lock, pthread_mutex_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeLock(cur_thread(), (uptr)m, true, pc); + int res = REAL(pthread_mutex_lock)(m); + MutexAfterLock(cur_thread(), (uptr)m, true, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_mutex_trylock, pthread_mutex_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_mutex_trylock)(m); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, true, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_mutex_timedlock, pthread_mutex_t *m, + const struct timespec *abstime) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_mutex_timedlock)(m, abstime); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, true, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_mutex_unlock, pthread_mutex_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc); + return REAL(pthread_mutex_unlock)(m); +} + +INTERCEPTOR(int, pthread_spin_init, pthread_spinlock_t *m, int pshared) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_spin_init)(m, pshared); + if (res == 0) + MutexInit(cur_thread(), (uptr)m, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_spin_destroy, pthread_spinlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_spin_destroy)(m); + if (res == 0) + MutexDestroy(cur_thread(), (uptr)m, pc); + return res; +} + +INTERCEPTOR(int, pthread_spin_lock, pthread_spinlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeLock(cur_thread(), (uptr)m, true, pc); + int res = REAL(pthread_spin_lock)(m); + MutexAfterLock(cur_thread(), (uptr)m, true, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_spin_trylock, pthread_spinlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_spin_trylock)(m); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, true, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_spin_unlock, pthread_spinlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc); + return REAL(pthread_spin_unlock)(m); +} + +INTERCEPTOR(int, pthread_rwlock_init, pthread_rwlock_t *m, + const pthread_rwlockattr_t *attr) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_rwlock_init)(m, attr); + if (res == 0) + MutexInit(cur_thread(), (uptr)m, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_destroy, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexDestroy(cur_thread(), (uptr)m, pc); + return REAL(pthread_rwlock_destroy)(m); +} + +INTERCEPTOR(int, pthread_rwlock_rdlock, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeLock(cur_thread(), (uptr)m, false, pc); + int res = REAL(pthread_rwlock_rdlock)(m); + MutexAfterLock(cur_thread(), (uptr)m, false, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_tryrdlock, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_rwlock_tryrdlock)(m); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, false, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_timedrdlock, pthread_rwlock_t *m, + const timespec *abstime) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_rwlock_timedrdlock)(m, abstime); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, false, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_wrlock, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeLock(cur_thread(), (uptr)m, true, pc); + int res = REAL(pthread_rwlock_wrlock)(m); + MutexAfterLock(cur_thread(), (uptr)m, true, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_trywrlock, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_rwlock_trywrlock)(m); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, true, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_timedwrlock, pthread_rwlock_t *m, + const timespec *abstime) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + int res = REAL(pthread_rwlock_timedwrlock)(m, abstime); + if (res == 0) + MutexAfterLock(cur_thread(), (uptr)m, true, true, pc); + return res; +} + +INTERCEPTOR(int, pthread_rwlock_unlock, pthread_rwlock_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeUnlock(cur_thread(), (uptr)m, false, pc); + return REAL(pthread_rwlock_unlock)(m); +} + +static pthread_cond_t *init_cond(pthread_cond_t *c, bool force = false) { + 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 (pthread_cond_t *)cond; + void *newcond = InternalAlloc(sizeof(pthread_cond_t)); + internal_memset(newcond, 0, sizeof(pthread_cond_t)); + if (atomic_compare_exchange_strong(p, &cond, (uptr)newcond, + memory_order_acq_rel)) + return (pthread_cond_t *)newcond; + InternalFree(newcond); + return (pthread_cond_t *)cond; +} + +INTERCEPTOR(int, pthread_cond_init, pthread_cond_t *c, + const pthread_condattr_t *a) { + InitThread(); + return REAL(pthread_cond_init)(init_cond(c, true), a); +} + +INTERCEPTOR(int, pthread_cond_wait, pthread_cond_t *c, pthread_mutex_t *m) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc); + int res = REAL(pthread_cond_wait)(init_cond(c), m); + MutexBeforeLock(cur_thread(), (uptr)m, true, pc); + MutexAfterLock(cur_thread(), (uptr)m, true, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_cond_timedwait, pthread_cond_t *c, pthread_mutex_t *m, + const timespec *abstime) { + uptr pc = GET_CURRENT_PC(); + InitThread(); + MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc); + int res = REAL(pthread_cond_timedwait)(init_cond(c), m, abstime); + MutexBeforeLock(cur_thread(), (uptr)m, true, pc); + MutexAfterLock(cur_thread(), (uptr)m, true, false, pc); + return res; +} + +INTERCEPTOR(int, pthread_cond_signal, pthread_cond_t *c) { + InitThread(); + return REAL(pthread_cond_signal)(init_cond(c)); +} + +INTERCEPTOR(int, pthread_cond_broadcast, pthread_cond_t *c) { + InitThread(); + return REAL(pthread_cond_broadcast)(init_cond(c)); +} + +INTERCEPTOR(int, pthread_cond_destroy, pthread_cond_t *c) { + InitThread(); + pthread_cond_t *cond = init_cond(c); + int res = REAL(pthread_cond_destroy)(cond); + if (common_flags()->legacy_pthread_cond) { + InternalFree(cond); + atomic_store((atomic_uintptr_t *)c, 0, memory_order_relaxed); + } + return res; +} + +INTERCEPTOR(char *, realpath, const char *path, char *resolved_path) { + InitThread(); + return REAL(realpath)(path, resolved_path); +} + +INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) { + InitThread(); + return REAL(read)(fd, ptr, count); +} + +INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) { + InitThread(); + return REAL(pread)(fd, ptr, count, offset); +} + +namespace __tsan_deadlock { + +void InitializeInterceptors() { + INTERCEPT_FUNCTION(pthread_create); + INTERCEPT_FUNCTION(pthread_join); + INTERCEPT_FUNCTION(pthread_exit); + + INTERCEPT_FUNCTION(pthread_mutex_init); + INTERCEPT_FUNCTION(pthread_mutex_destroy); + INTERCEPT_FUNCTION(pthread_mutex_lock); + INTERCEPT_FUNCTION(pthread_mutex_trylock); + INTERCEPT_FUNCTION(pthread_mutex_timedlock); + INTERCEPT_FUNCTION(pthread_mutex_unlock); + + INTERCEPT_FUNCTION(pthread_spin_init); + INTERCEPT_FUNCTION(pthread_spin_destroy); + INTERCEPT_FUNCTION(pthread_spin_lock); + INTERCEPT_FUNCTION(pthread_spin_trylock); + INTERCEPT_FUNCTION(pthread_spin_unlock); + + INTERCEPT_FUNCTION(pthread_rwlock_init); + INTERCEPT_FUNCTION(pthread_rwlock_destroy); + INTERCEPT_FUNCTION(pthread_rwlock_rdlock); + INTERCEPT_FUNCTION(pthread_rwlock_tryrdlock); + INTERCEPT_FUNCTION(pthread_rwlock_timedrdlock); + INTERCEPT_FUNCTION(pthread_rwlock_wrlock); + INTERCEPT_FUNCTION(pthread_rwlock_trywrlock); + INTERCEPT_FUNCTION(pthread_rwlock_timedwrlock); + INTERCEPT_FUNCTION(pthread_rwlock_unlock); + + // See the comment in tsan_interceptors_posix.cpp. +#if SANITIZER_GLIBC && !__GLIBC_PREREQ(2, 36) && \ + (defined(__x86_64__) || defined(__mips__) || SANITIZER_PPC64V1 || \ + defined(__s390x__)) + 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 + INTERCEPT_FUNCTION(pthread_cond_init); + INTERCEPT_FUNCTION(pthread_cond_signal); + INTERCEPT_FUNCTION(pthread_cond_broadcast); + INTERCEPT_FUNCTION(pthread_cond_wait); + INTERCEPT_FUNCTION(pthread_cond_timedwait); + INTERCEPT_FUNCTION(pthread_cond_destroy); +#endif + + INTERCEPT_FUNCTION(realpath); + INTERCEPT_FUNCTION(read); + INTERCEPT_FUNCTION(pread); +} + +} // namespace __tsan_deadlock diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp new file mode 100644 index 0000000000000..8a0d4c3295091 --- /dev/null +++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp @@ -0,0 +1,36 @@ +//===-- tsan_deadlock_interface.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 DeadlockSanitizer. +// +//===----------------------------------------------------------------------===// + +#include "tsan_deadlock_rtl.h" + +using namespace __tsan_deadlock; + +extern "C" { + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_init() { Initialize(); } + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_entry(void *pc) { + if (UNLIKELY(!thr_tls.is_inited)) + return; + DCHECK_LT(thr_tls.shadow_stack_pos, thr_tls.shadow_stack_end); + thr_tls.shadow_stack_pos[0] = (uptr)pc; + thr_tls.shadow_stack_pos++; +} + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_exit() { + if (UNLIKELY(!thr_tls.is_inited)) + return; + if (LIKELY(thr_tls.shadow_stack_pos > thr_tls.shadow_stack)) + thr_tls.shadow_stack_pos--; +} + +} // extern "C" diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp new file mode 100644 index 0000000000000..099ffa1b267cc --- /dev/null +++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp @@ -0,0 +1,339 @@ +//===-- tsan_deadlock_rtl.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 DeadlockSanitizer. +// +//===----------------------------------------------------------------------===// + +#include "tsan_deadlock_rtl.h" + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_flag_parser.h" +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "sanitizer_common/sanitizer_report_decorator.h" +#include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_stacktrace.h" + +namespace __tsan_deadlock { + +Flags tsan_deadlock_flags; + +static Context *ctx; + +static const uptr kShadowStackSize = 64 * 1024; + +__attribute__((tls_model("initial-exec"))) THREADLOCAL Thread thr_tls; + +Thread *cur_thread() { return thr_tls.is_inited ? &thr_tls : nullptr; } + +class Decorator : public __sanitizer::SanitizerCommonDecorator { +public: + Decorator() : SanitizerCommonDecorator() {} + const char *Mutex() { return Magenta(); } + const char *ThreadDescription() { return Cyan(); } +}; + +static u32 CurrentStackId(Thread *thr, uptr pc) { + if (pc) { + DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end); + *thr->shadow_stack_pos = pc; + thr->shadow_stack_pos++; + } + u32 id = StackDepotPut( + StackTrace(thr->shadow_stack, thr->shadow_stack_pos - thr->shadow_stack)); + if (pc) + thr->shadow_stack_pos--; + return id; +} + +static void PrintStack(Thread *thr, u32 stk) { + if (!stk) + return; + StackTrace trace = StackDepotGet(stk); + thr->ignore_interceptors++; + trace.Print(); + thr->ignore_interceptors--; +} + +static void ReportDeadlock(Thread *thr, DDReport *rep) { + if (!rep) + return; + Decorator d; + Lock lock(&ctx->report_mutex); + Printf("==================\n"); + Printf("%sWARNING: %s: lock-order-inversion (potential deadlock) (pid=%d)%s\n", + d.Warning(), SanitizerToolName, (int)internal_getpid(), d.Default()); + Printf(" Cycle in lock order graph: "); + for (int i = 0; i < rep->n; i++) + Printf("%sM%d%s (0x%zx) => ", d.Mutex(), i, d.Default(), + (uptr)rep->loop[i].mtx_ctx0); + Printf("%sM0%s\n\n", d.Mutex(), d.Default()); + for (int i = 0; i < rep->n; i++) { + int next = (i + 1) % rep->n; + Printf(" %sMutex M%d%s acquired here while holding %sMutex M%d%s" + " in %sthread T%lld%s:\n", + d.Mutex(), next, d.Default(), d.Mutex(), i, d.Default(), + d.ThreadDescription(), (s64)rep->loop[i].thr_ctx, d.Default()); + PrintStack(thr, rep->loop[i].stk[0]); + if (flags()->second_deadlock_stack && rep->loop[i].stk[1]) { + Printf(" %sMutex M%d%s previously acquired by the same thread here:\n", + d.Mutex(), i, d.Default()); + PrintStack(thr, rep->loop[i].stk[1]); + } + if (i == 0 && !flags()->second_deadlock_stack) { + Printf(" HINT: use TSAN_DEADLOCK_OPTIONS=second_deadlock_stack=1 to get " + "more informative warning message\n\n"); + } + } + Printf("==================\n"); + StackTrace empty; + ReportErrorSummary("lock-order-inversion", &empty); + if (flags()->halt_on_error) + Die(); +} + +static void ReportMutexMisuse(Thread *thr, const char *what, uptr addr, uptr pc, + u32 creation_stk, u32 last_lock_stk = 0) { + Decorator d; + Lock lock(&ctx->report_mutex); + Printf("==================\n"); + Printf("%sWARNING: %s: %s on %smutex 0x%zx%s (pid=%d)%s\n", + d.Warning(), SanitizerToolName, what, d.Mutex(), addr, d.Default(), + (int)internal_getpid(), d.Default()); + PrintStack(thr, CurrentStackId(thr, pc)); + if (last_lock_stk) { + Printf(" and:\n"); + PrintStack(thr, last_lock_stk); + } + if (creation_stk) { + Printf("%s Mutex M0 (%p) created at:%s\n", d.Mutex(), + reinterpret_cast<void *>(addr), d.Default()); + PrintStack(thr, creation_stk); + } + Printf("==================\n"); + StackTrace empty; + ReportErrorSummary(what, &empty); + if (flags()->halt_on_error) + Die(); +} + +Callback::Callback(Thread *thr, uptr pc) : thr(thr), pc(pc) { + DDCallback::pt = thr->dd_pt; + DDCallback::lt = thr->dd_lt; +} + +u32 Callback::Unwind() { return CurrentStackId(thr, pc); } + +int Callback::UniqueTid() { return thr->unique_id; } + +static void InitializeFlags() { + Flags *f = flags(); + f->SetDefaults(); + SetCommonFlagsDefaults(); + { + CommonFlags cf; + cf.CopyFrom(*common_flags()); + cf.allow_addr2line = true; + cf.exitcode = 66; + OverrideCommonFlags(cf); + } + FlagParser parser; + RegisterFlag(&parser, "second_deadlock_stack", + "Report where each mutex is locked in deadlock reports", + &f->second_deadlock_stack); + RegisterFlag(&parser, "report_destroy_locked", + "Report destruction of a locked mutex?", + &f->report_destroy_locked); + RegisterFlag(&parser, "report_mutex_bugs", + "Report incorrect usages of mutexes and mutex annotations?", + &f->report_mutex_bugs); + RegisterFlag(&parser, "report_bugs", + "Turns off bug reporting entirely (useful for benchmarking).", + &f->report_bugs); + RegisterFlag(&parser, "halt_on_error", "Exit after first reported error.", + &f->halt_on_error); + RegisterCommonFlags(&parser); + parser.ParseStringFromEnv("TSAN_DEADLOCK_OPTIONS"); + if (!f->report_bugs) { + f->report_destroy_locked = false; + f->report_mutex_bugs = false; + } + SetVerbosity(common_flags()->verbosity); +} + +void Initialize() { + static atomic_uint32_t initialized; + if (atomic_fetch_add(&initialized, 1, memory_order_relaxed) != 0) + return; + static u64 ctx_mem[sizeof(Context) / sizeof(u64) + 1]; + ctx = new (ctx_mem) Context(); + SanitizerToolName = "DeadlockSanitizer"; + InitializeFlags(); + ctx->dd = DDetector::Create(flags()); + InitializeInterceptors(); +} + +void ThreadInit(Thread *thr) { + static atomic_uintptr_t id_gen; + uptr id = atomic_fetch_add(&id_gen, 1, memory_order_relaxed); + thr->unique_id = (int)id; + thr->dd_pt = ctx->dd->CreatePhysicalThread(); + thr->dd_lt = ctx->dd->CreateLogicalThread(id); + thr->shadow_stack = static_cast<uptr *>(MmapNoReserveOrDie( + kShadowStackSize * sizeof(uptr), "shadow stack")); + thr->shadow_stack_pos = thr->shadow_stack; + thr->shadow_stack_end = thr->shadow_stack + kShadowStackSize; + thr->is_inited = true; +} + +void ThreadDestroy(Thread *thr) { + thr->is_inited = false; + ctx->dd->DestroyLogicalThread(thr->dd_lt); + thr->dd_lt = nullptr; + UnmapOrDie(thr->shadow_stack, kShadowStackSize * sizeof(uptr)); + thr->shadow_stack = nullptr; + thr->shadow_stack_pos = nullptr; + thr->shadow_stack_end = nullptr; + ctx->dd->DestroyPhysicalThread(thr->dd_pt); + thr->dd_pt = nullptr; +} + +static void EnsureMutexInit(Callback *cb, MutexHashMap::Handle *h, uptr m, + uptr pc) { + if (h->created()) { + ctx->dd->MutexInit(cb, &(*h)->dd); + (*h)->dd.ctx = m; + (*h)->creation_stk = CurrentStackId(cb->thr, pc); + } +} + +void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc) { + if (!thr || thr->ignore_interceptors) + return; + Callback cb(thr, pc); + MutexHashMap::Handle h(&ctx->mutex_map, m); + EnsureMutexInit(&cb, &h, m, pc); + h->reentrant = reentrant; +} + +void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc) { + if (!thr || thr->ignore_interceptors) + return; + Callback cb(thr, pc); + bool report_double_lock = false; + { + MutexHashMap::Handle h(&ctx->mutex_map, m); + EnsureMutexInit(&cb, &h, m, pc); + if (writelock) { + uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed); + if (owner == (uptr)thr->dd_lt && !h->reentrant) + report_double_lock = true; + else if (owner != (uptr)thr->dd_lt) + ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock); + } else { + ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock); + } + } + if (report_double_lock && flags()->report_mutex_bugs) { + MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false, + /*create=*/false); + u32 cstk = h.exists() ? h->creation_stk : 0; + ReportMutexMisuse(thr, "double lock of a mutex", m, pc, cstk); + } else if (flags()->report_bugs) { + ReportDeadlock(thr, ctx->dd->GetReport(&cb)); + } +} + +void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock, uptr pc) { + if (!thr || thr->ignore_interceptors) + return; + Callback cb(thr, pc); + { + MutexHashMap::Handle h(&ctx->mutex_map, m); + EnsureMutexInit(&cb, &h, m, pc); + if (writelock) { + uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed); + if (owner == (uptr)thr->dd_lt) { + DCHECK(h->reentrant); + h->recursion++; + } else { + DCHECK_EQ(owner, 0u); + CHECK_EQ(h->recursion, 0); + h->recursion = 1; + h->last_lock_stk = CurrentStackId(thr, pc); + atomic_store(&h->owner_tid, (uptr)thr->dd_lt, memory_order_relaxed); + ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock); + } + } else { + ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock); + } + } + if (flags()->report_bugs) + ReportDeadlock(thr, ctx->dd->GetReport(&cb)); +} + +void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc) { + if (!thr || thr->ignore_interceptors) + return; + Callback cb(thr, pc); + bool report_bad_unlock = false; + { + MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false, + /*create=*/false); + if (!h.exists()) + return; + if (writelock) { + uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed); + if (owner != (uptr)thr->dd_lt) { + report_bad_unlock = true; + } else { + h->recursion--; + if (h->recursion == 0) { + atomic_store(&h->owner_tid, 0, memory_order_relaxed); + ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock); + } + } + } else { + ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock); + } + } + if (report_bad_unlock && flags()->report_mutex_bugs) { + MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false, + /*create=*/false); + u32 cstk = h.exists() ? h->creation_stk : 0; + ReportMutexMisuse(thr, "unlock of an unlocked mutex (or by a wrong thread)", + m, pc, cstk); + } else if (flags()->report_bugs) { + ReportDeadlock(thr, ctx->dd->GetReport(&cb)); + } +} + +void MutexDestroy(Thread *thr, uptr m, uptr pc) { + if (!thr || thr->ignore_interceptors) + return; + Callback cb(thr, pc); + bool report_destroy_locked = false; + u32 creation_stk = 0; + u32 last_lock_stk = 0; + { + MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/true); + if (!h.exists()) + return; + creation_stk = h->creation_stk; + last_lock_stk = h->last_lock_stk; + if (atomic_load(&h->owner_tid, memory_order_relaxed) != 0) + report_destroy_locked = true; + ctx->dd->MutexDestroy(&cb, &h->dd); + } + if (report_destroy_locked && flags()->report_destroy_locked) + ReportMutexMisuse(thr, "destroy of a locked mutex", m, pc, creation_stk, + last_lock_stk); +} + +} // namespace __tsan_deadlock diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h new file mode 100644 index 0000000000000..ac9046c557c24 --- /dev/null +++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h @@ -0,0 +1,102 @@ +//===-- tsan_deadlock_rtl.h -------------------------------------*- C++ -*-===// +// +// 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 DeadlockSanitizer. +// +// Main internal header. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_DEADLOCK_RTL_H +#define TSAN_DEADLOCK_RTL_H + +#include "sanitizer_common/sanitizer_addrhashmap.h" +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_deadlock_detector_interface.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_mutex.h" + +namespace __tsan_deadlock { + +struct Flags : DDFlags { + bool report_destroy_locked; + bool report_mutex_bugs; + bool report_bugs; + bool halt_on_error; + + void SetDefaults() { + second_deadlock_stack = false; + report_destroy_locked = true; + report_mutex_bugs = true; + report_bugs = true; + halt_on_error = false; + } +}; + +struct UserMutex { + DDMutex dd; + atomic_uintptr_t owner_tid; // DDLogicalThread* cast to uptr; 0 = unlocked + u32 recursion; + u32 creation_stk; + u32 last_lock_stk; + bool reentrant; +}; + +// Stored in TLS; shadow_stack_pos is first for cache-line friendliness. +struct Thread { + uptr *shadow_stack_pos; + uptr *shadow_stack; + uptr *shadow_stack_end; + + DDPhysicalThread *dd_pt; + DDLogicalThread *dd_lt; + + int ignore_interceptors; + int unique_id; + bool is_inited; +}; + +struct Callback final : public DDCallback { + Thread *thr; + uptr pc; + + explicit Callback(Thread *thr, uptr pc); + u32 Unwind() override; + int UniqueTid() override; +}; + +typedef AddrHashMap<UserMutex, 31051> MutexHashMap; + +struct Context { + DDetector *dd; + Mutex report_mutex; + MutexHashMap mutex_map; +}; + +extern Flags tsan_deadlock_flags; +inline Flags *flags() { return &tsan_deadlock_flags; } + +extern THREADLOCAL Thread thr_tls; + +Thread *cur_thread(); + +void Initialize(); +void InitializeInterceptors(); + +void ThreadInit(Thread *thr); +void ThreadDestroy(Thread *thr); + +void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc = 0); +void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc); +void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock, uptr pc); +void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc); +void MutexDestroy(Thread *thr, uptr m, uptr pc); + +} // namespace __tsan_deadlock + +#endif // TSAN_DEADLOCK_RTL_H diff --git a/compiler-rt/test/tsan_deadlock/CMakeLists.txt b/compiler-rt/test/tsan_deadlock/CMakeLists.txt new file mode 100644 index 0000000000000..308780098af39 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/CMakeLists.txt @@ -0,0 +1,29 @@ +set(TSAN_DEADLOCK_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +set(TSAN_DEADLOCK_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS}) +list(APPEND TSAN_DEADLOCK_TEST_DEPS tsan_deadlock) + +set(TSAN_DEADLOCK_TESTSUITES) +set(TSAN_DEADLOCK_TEST_ARCH ${TSAN_DEADLOCK_SUPPORTED_ARCH}) + +foreach(arch ${TSAN_DEADLOCK_TEST_ARCH}) + set(TSAN_DEADLOCK_TEST_TARGET_ARCH ${arch}) + string(TOLOWER "-${arch}" TSAN_DEADLOCK_TEST_CONFIG_SUFFIX) + get_test_cc_for_arch(${arch} TSAN_DEADLOCK_TEST_TARGET_CC TSAN_DEADLOCK_TEST_TARGET_CFLAGS) + + string(TOUPPER ${arch} ARCH_UPPER_CASE) + set(CONFIG_NAME ${ARCH_UPPER_CASE}Config) + + configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py + MAIN_CONFIG + ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py) + + list(APPEND TSAN_DEADLOCK_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}) +endforeach() + +add_lit_testsuite(check-tsan_deadlock "Running DeadlockSanitizer tests" + ${TSAN_DEADLOCK_TESTSUITES} + DEPENDS ${TSAN_DEADLOCK_TEST_DEPS}) +set_target_properties(check-tsan_deadlock PROPERTIES FOLDER "Compiler-RT Tests") diff --git a/compiler-rt/test/tsan_deadlock/lit.cfg.py b/compiler-rt/test/tsan_deadlock/lit.cfg.py new file mode 100644 index 0000000000000..0c7aeb539b778 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/lit.cfg.py @@ -0,0 +1,47 @@ +# -*- Python -*- + +import os + +# Setup config name. +config.name = "DeadlockSanitizer" + config.name_suffix + +# Setup source root. +config.test_source_root = os.path.dirname(__file__) + +default_tsan_deadlock_opts = "atexit_sleep_ms=0" + +if config.target_os == "Darwin": + # On Darwin, we default to `abort_on_error=1`, which would make tests run + # much slower. Let's override this and run lit tests with 'abort_on_error=0'. + default_tsan_deadlock_opts += ":abort_on_error=0" + +if default_tsan_deadlock_opts: + config.environment["TSAN_DEADLOCK_OPTIONS"] = default_tsan_deadlock_opts + default_tsan_deadlock_opts += ":" +config.substitutions.append( + ("%env_tsan_deadlock_opts=", "env TSAN_DEADLOCK_OPTIONS=" + default_tsan_deadlock_opts) +) + +clang_tsan_deadlock_cflags = ( + ["-fsanitize=thread-deadlock", "-Wall", "-pthread"] + + [config.target_cflags] + + config.debug_info_flags +) +clang_tsan_deadlock_cxxflags = config.cxx_mode_flags + clang_tsan_deadlock_cflags + ["-std=c++14"] + + +def build_invocation(compile_flags): + return " " + " ".join([config.clang] + compile_flags) + " " + + +config.substitutions.append( + ("%clang_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cflags)) +) +config.substitutions.append( + ("%clangxx_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cxxflags)) +) + +config.suffixes = [".c", ".cpp"] + +if config.target_os not in ["FreeBSD", "Linux", "Darwin", "NetBSD"]: + config.unsupported = True diff --git a/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in new file mode 100644 index 0000000000000..2ba3b1af1c45e --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in @@ -0,0 +1,12 @@ +@LIT_SITE_CFG_IN_HEADER@ + +config.name_suffix = "@TSAN_DEADLOCK_TEST_CONFIG_SUFFIX@" +config.tsan_deadlock_lit_source_dir = "@TSAN_DEADLOCK_LIT_SOURCE_DIR@" +config.target_cflags = "@TSAN_DEADLOCK_TEST_TARGET_CFLAGS@" +config.target_arch = "@TSAN_DEADLOCK_TEST_TARGET_ARCH@" + +# Load common config for all compiler-rt lit tests. +lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured") + +# Load tool-specific config that would do the real work. +lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py") diff --git a/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp new file mode 100644 index 0000000000000..16723d89ff033 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp @@ -0,0 +1,26 @@ +// Adapted from tsan/mutex_bad_unlock.cpp +// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s + +#include <pthread.h> + +static pthread_mutex_t mu; + +static void *unlocker(void *) { + pthread_mutex_unlock(&mu); + return 0; +} + +int main() { + pthread_mutex_init(&mu, 0); + pthread_mutex_lock(&mu); + pthread_t t; + pthread_create(&t, 0, unlocker, 0); + pthread_join(t, 0); + return 0; +} + +// CHECK: WARNING: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread) +// CHECK: {{.*}} in pthread_mutex_unlock +// CHECK: Mutex M0 (0x{{.*}}) created at: +// CHECK: {{.*}} in pthread_mutex_init +// CHECK: SUMMARY: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread) diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp new file mode 100644 index 0000000000000..8b28da1175e97 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp @@ -0,0 +1,31 @@ +// Equivalent to tsan/mutex_cycle2.c +// RUN: %clangxx_tsan_deadlock %s -o %t +// RUN: not %run %t 2>&1 | FileCheck %s +// RUN: %env_tsan_deadlock_opts=report_bugs=0 %run %t 2>&1 | FileCheck %s --check-prefix=DISABLED +#include <pthread.h> +#include <stdio.h> + +int main() { + pthread_mutex_t mu1, mu2; + pthread_mutex_init(&mu1, NULL); + pthread_mutex_init(&mu2, NULL); + + // mu1 => mu2 + pthread_mutex_lock(&mu1); + pthread_mutex_lock(&mu2); + pthread_mutex_unlock(&mu2); + pthread_mutex_unlock(&mu1); + + // mu2 => mu1 + pthread_mutex_lock(&mu2); + pthread_mutex_lock(&mu1); + // CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock) + // DISABLED-NOT: DeadlockSanitizer + // DISABLED: PASS + pthread_mutex_unlock(&mu1); + pthread_mutex_unlock(&mu2); + + pthread_mutex_destroy(&mu1); + pthread_mutex_destroy(&mu2); + fprintf(stderr, "PASS\n"); +} diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c new file mode 100644 index 0000000000000..0b7c489faa513 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c @@ -0,0 +1,44 @@ +// Equivalent to tsan/mutex_cycle_long.c +// RUN: %clangxx_tsan_deadlock %s -o %t +// RUN: not %run %t 5 2>&1 | FileCheck %s +// RUN: not %run %t 10 2>&1 | FileCheck %s +// RUN: not %run %t 15 2>&1 | FileCheck %s +// RUN: not %run %t 20 2>&1 | FileCheck %s +// RUN: %run %t 30 2>&1 | FileCheck %s --check-prefix=CHECK-TOO-LONG-CYCLE + +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char *argv[]) { + int num_mutexes = 5; + if (argc > 1) num_mutexes = atoi(argv[1]); + + pthread_mutex_t m[num_mutexes]; + for (int i = 0; i < num_mutexes; ++i) + pthread_mutex_init(&m[i], NULL); + + for (int i = 0; i < num_mutexes - 1; ++i) { + pthread_mutex_lock(&m[i]); + pthread_mutex_lock(&m[i + 1]); + + pthread_mutex_unlock(&m[i]); + pthread_mutex_unlock(&m[i + 1]); + } + + pthread_mutex_lock(&m[num_mutexes - 1]); + pthread_mutex_lock(&m[0]); + + pthread_mutex_unlock(&m[num_mutexes - 1]); + pthread_mutex_unlock(&m[0]); + + for (int i = 0; i < num_mutexes; ++i) + pthread_mutex_destroy(&m[i]); + + fprintf(stderr, "PASS\n"); +} + +// CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock) +// CHECK-TOO-LONG-CYCLE: WARNING: too long mutex cycle found +// CHECK: PASS +// CHECK-TOO-LONG-CYCLE: PASS diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp new file mode 100644 index 0000000000000..96e813bebcd3f --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp @@ -0,0 +1,20 @@ +// Equivalent to tsan/mutex_destroy_locked.cpp +// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s + +#include <pthread.h> + +int main() { + pthread_mutex_t m; + pthread_mutex_init(&m, 0); + pthread_mutex_lock(&m); + pthread_mutex_destroy(&m); + return 0; +} + +// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex +// CHECK: {{.*}} in pthread_mutex_destroy +// CHECK: and: +// CHECK: {{.*}} in pthread_mutex_lock +// CHECK: Mutex M0 (0x{{.*}}) created at: +// CHECK: {{.*}} in pthread_mutex_init +// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp new file mode 100644 index 0000000000000..5aced7b4a7a0c --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp @@ -0,0 +1,27 @@ +// Equivalent to tsan/mutex_destroy_locked2.cpp +// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s + +#include <pthread.h> + +static void *thread(void *) { + pthread_mutex_t m; + pthread_mutex_init(&m, 0); + pthread_mutex_lock(&m); + pthread_mutex_destroy(&m); + return 0; +} + +int main() { + pthread_t th; + pthread_create(&th, 0, thread, 0); + pthread_join(th, 0); + return 0; +} + +// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex +// CHECK: {{.*}} in pthread_mutex_destroy +// CHECK: and: +// CHECK: {{.*}} in pthread_mutex_lock +// CHECK: Mutex M0 (0x{{.*}}) created at: +// CHECK: {{.*}} in pthread_mutex_init +// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex diff --git a/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp new file mode 100644 index 0000000000000..7236ade4c33c3 --- /dev/null +++ b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp @@ -0,0 +1,17 @@ +// Adapted from tsan/mutex_double_lock.cpp +// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s + +#include <pthread.h> + +int main() { + pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&mu); + pthread_mutex_lock(&mu); + return 0; +} + +// CHECK: WARNING: DeadlockSanitizer: double lock of a mutex +// CHECK: {{.*}} in pthread_mutex_lock +// CHECK: Mutex M0 (0x{{.*}}) created at: +// CHECK: {{.*}} in pthread_mutex_lock +// CHECK: SUMMARY: DeadlockSanitizer: double lock of a mutex diff --git a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h index 39e09a458cba5..cad45cb4f8a4d 100644 --- a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h +++ b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h @@ -20,13 +20,27 @@ namespace llvm { class Function; class Module; +struct ThreadSanitizerOptions { + bool InstrumentMemoryAccesses = true; + bool InstrumentAtomics = true; + bool InstrumentMemIntrinsics = true; + bool AlwaysInstrumentFuncEntryExit = false; +}; + /// A function pass for tsan instrumentation. /// /// Instruments functions to detect race conditions reads. This function pass /// inserts calls to runtime library functions. If the functions aren't declared /// yet, the pass inserts the declarations. Otherwise the existing globals are -struct ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> { +class ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> { +public: + LLVM_ABI + ThreadSanitizerPass(const ThreadSanitizerOptions &Options = {}) + : Options(Options) {} LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM); + +private: + ThreadSanitizerOptions Options; }; /// A module pass for tsan instrumentation. diff --git a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp index f05efd863fb74..0a863aa3ae559 100644 --- a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp +++ b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp @@ -109,7 +109,7 @@ namespace { /// ensures the __tsan_init function is in the list of global constructors for /// the module. struct ThreadSanitizer { - ThreadSanitizer() { + ThreadSanitizer(ThreadSanitizerOptions Options) : Options(Options) { // Check options and warn user. if (ClInstrumentReadBeforeWrite && ClCompoundReadBeforeWrite) { errs() @@ -172,6 +172,7 @@ struct ThreadSanitizer { FunctionCallee TsanVptrUpdate; FunctionCallee TsanVptrLoad; FunctionCallee MemmoveFn, MemcpyFn, MemsetFn; + ThreadSanitizerOptions Options; }; void insertModuleCtor(Module &M) { @@ -186,7 +187,7 @@ void insertModuleCtor(Module &M) { PreservedAnalyses ThreadSanitizerPass::run(Function &F, FunctionAnalysisManager &FAM) { - ThreadSanitizer TSan; + ThreadSanitizer TSan(Options); if (TSan.sanitizeFunction(F, FAM.getResult<TargetLibraryAnalysis>(F))) return PreservedAnalyses::none(); return PreservedAnalyses::all(); @@ -550,19 +551,21 @@ bool ThreadSanitizer::sanitizeFunction(Function &F, // (e.g. variables that do not escape, etc). // Instrument memory accesses only if we want to report bugs in the function. - if (ClInstrumentMemoryAccesses && SanitizeFunction) + if (ClInstrumentMemoryAccesses && Options.InstrumentMemoryAccesses && + SanitizeFunction) for (const auto &II : AllLoadsAndStores) { Res |= instrumentLoadOrStore(II, DL); } // Instrument atomic memory accesses in any case (they can be used to // implement synchronization). - if (ClInstrumentAtomics) + if (ClInstrumentAtomics && Options.InstrumentAtomics) for (auto *Inst : AtomicAccesses) { Res |= instrumentAtomic(Inst, DL); } - if (ClInstrumentMemIntrinsics && SanitizeFunction) + if (ClInstrumentMemIntrinsics && Options.InstrumentMemIntrinsics && + SanitizeFunction) for (auto *Inst : MemIntrinCalls) { Res |= instrumentMemIntrinsic(Inst); } @@ -573,8 +576,10 @@ bool ThreadSanitizer::sanitizeFunction(Function &F, InsertRuntimeIgnores(F); } - // Instrument function entry/exit points if there were instrumented accesses. - if ((Res || HasCalls) && ClInstrumentFuncEntryExit) { + // Instrument function entry/exit points if there were instrumented accesses, + // or if all functions should be tracked (e.g. for tsan_deadlock). + if ((Res || HasCalls || Options.AlwaysInstrumentFuncEntryExit) && + ClInstrumentFuncEntryExit) { InstrumentationIRBuilder IRB(&F.getEntryBlock(), F.getEntryBlock().getFirstNonPHIIt()); auto ProgramAsPtrTy = PointerType::get(F.getParent()->getContext(), _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
