Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix ThreadSanitizer support by removing detaching of main thread upon program termination. #3525

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)

if(APPLE)
copy_compilerrt_lib("darwin/libclang_rt.asan_osx_dynamic.dylib" "libldc_rt.asan.dylib" TRUE)
copy_compilerrt_lib("darwin/libclang_rt.tsan_osx_dynamic.dylib" "libldc_rt.tsan.dylib" TRUE)
copy_compilerrt_lib("darwin/libclang_rt.osx.a" "libldc_rt.builtins.a" FALSE)
copy_compilerrt_lib("darwin/libclang_rt.profile_osx.a" "libldc_rt.profile.a" FALSE)
copy_compilerrt_lib("darwin/libclang_rt.fuzzer_osx.a" "libldc_rt.fuzzer.a" FALSE)
Expand All @@ -777,6 +778,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)
set(LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH "x86_64" CACHE STRING "Non-Mac Posix: architecture used as libname suffix for the compiler-rt source libraries, e.g., 'aarch64'.")

copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.asan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.asan.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.tsan-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.tsan.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.builtins-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.builtins.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.profile-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.profile.a" FALSE)
copy_compilerrt_lib("${LDC_INSTALL_LLVM_RUNTIME_LIBS_OS}/libclang_rt.xray-${LDC_INSTALL_LLVM_RUNTIME_LIBS_ARCH}.a" "libldc_rt.xray.a" FALSE)
Expand Down
46 changes: 43 additions & 3 deletions driver/linker-gcc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class ArgsBuilder {
private:
virtual void addSanitizers(const llvm::Triple &triple);
virtual void addASanLinkFlags(const llvm::Triple &triple);
virtual void addTSanLinkFlags(const llvm::Triple &triple);
virtual void addFuzzLinkFlags(const llvm::Triple &triple);
virtual void addCppStdlibLinkFlags(const llvm::Triple &triple);
virtual void addProfileRuntimeLinkFlags(const llvm::Triple &triple);
Expand Down Expand Up @@ -322,6 +323,47 @@ void ArgsBuilder::addASanLinkFlags(const llvm::Triple &triple) {
args.push_back("-fsanitize=address");
}

void ArgsBuilder::addTSanLinkFlags(const llvm::Triple &triple) {
// Examples: "libclang_rt.tsan-x86_64.a" or "libclang_rt.tsan-arm.a" and
// "libclang_rt.tsan-x86_64.so"

// TODO: let user choose to link with shared lib.
// In case of shared TSan, I think we also need to statically link with
// libclang_rt.tsan-preinit-<arch>.a on Linux. On Darwin, the only option is
// to use the shared library.
bool linkSharedTSan = triple.isOSDarwin();
const auto searchPaths =
getFullCompilerRTLibPathCandidates("tsan", triple, linkSharedTSan);

for (const auto &filepath : searchPaths) {
IF_LOG Logger::println("Searching TSan lib: %s", filepath.c_str());

if (llvm::sys::fs::exists(filepath) &&
!llvm::sys::fs::is_directory(filepath)) {
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
args.push_back(filepath);

if (linkSharedTSan) {
// Add @executable_path to rpath to support having the shared lib copied
// with the executable.
args.push_back("-rpath");
args.push_back("@executable_path");

// Add the path to the resource dir to rpath to support using the shared
// lib from the default location without copying.
args.push_back("-rpath");
args.push_back(std::string(llvm::sys::path::parent_path(filepath)));
}

return;
}
}

// When we reach here, we did not find the TSan library.
// Fallback, requires Clang.
args.push_back("-fsanitize=thread");
}

// Adds all required link flags for -fsanitize=fuzzer when libFuzzer library is
// found.
void ArgsBuilder::addFuzzLinkFlags(const llvm::Triple &triple) {
Expand Down Expand Up @@ -459,10 +501,8 @@ void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
args.push_back("-fsanitize=memory");
}

// TODO: instead of this, we should link with our own sanitizer libraries
// because LDC's LLVM version could be different from the system clang.
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
args.push_back("-fsanitize=thread");
addTSanLinkFlags(triple);
}
}

Expand Down
2 changes: 1 addition & 1 deletion runtime/druntime
29 changes: 29 additions & 0 deletions tests/sanitizers/deflake.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# Copied from LLVM's testsuite: compiler-rt/test/tsan

# This script is used to deflake inherently flaky tsan tests.
# It is invoked from lit tests as:
# %deflake $THRESHOLD mybinary
# which is then substituted by lit to:
# $(dirname %s)/deflake.bash $THRESHOLD mybinary
# - When TSAN_TEST_DEFLAKE_THRESHOLD is defined to a positive integer value,
# THRESHOLD will be the defined value.
# - When TSAN_TEST_DEFLAKE_THRESHOLD is not defined, THRESHOLD will be 10.
# The script runs the target program up to $THRESHOLD times,
# until it fails (i.e. produces a race report).

THRESHOLD="${1}"
shift

# Early exit if $THRESHOLD is not a non-negative integer
[[ "${THRESHOLD}" =~ ^[0-9]+$ ]] || exit 1

while (( THRESHOLD-- )); do
OUT=`$@ 2>&1`
if [[ $? != 0 ]]; then
echo "$OUT"
exit 0
fi
done
exit 1
7 changes: 7 additions & 0 deletions tests/sanitizers/lit.local.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ if (platform.system() == 'Darwin') or (platform.system() == 'Linux'):
if m is not None:
config.available_features.add('ASan')
continue
m = re.match('.*tsan.*', file)
if m is not None:
config.available_features.add('TSan')
continue
m = re.match('.*(F|f)uzzer.*', file)
if m is not None:
config.available_features.add('Fuzzer')
Expand All @@ -25,3 +29,6 @@ if 'ASan' in config.available_features:
config.substitutions.append(('%env_asan_opts=',
'env ASAN_OPTIONS=' + default_asan_options + ':'))

# Add the %deflake substitution, to help with flaky tests.
# Usage: "%deflake <count> <program>", runs <program> a maximum of <count> times until a failure occurs.
config.substitutions.append( ("%deflake", os.path.join(os.path.dirname(__file__), "deflake.bash")))
10 changes: 10 additions & 0 deletions tests/sanitizers/tsan_noerror.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Test that a simple program passes ThreadSanitizer without error

// REQUIRES: TSan

// RUN: %ldc -fsanitize=thread %s -of=%t%exe
// RUN: %t%exe

void main()
{
}
59 changes: 59 additions & 0 deletions tests/sanitizers/tsan_tiny_race.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Test that ThreadSanitizer+LDC works on a very basic testcase.

// REQUIRES: TSan

// RUN: %ldc -g -fsanitize=thread %s -of=%t%exe
// RUN: %deflake 10 %t%exe | FileCheck %s

// CHECK: WARNING: ThreadSanitizer: data race

import core.sys.posix.pthread;

shared int global;

extern(C)
void *thread1(void *x) {
barrier_wait(&barrier);
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
global = 42;
return x;
}

int main() {
barrier_init(&barrier, 2);
pthread_t t;
pthread_create(&t, null, &thread1, null);
// CHECK-DAG: main{{.*}}[[@LINE+1]]
global = 43;
barrier_wait(&barrier);
pthread_join(t, null);
return global;
}

//----------------------------------------------------------------------------
// Code to facilitate thread synchronization to make this test deterministic.
// See LLVM: compiler-rt/test/tsan/test.h

// TSan-invisible barrier.
// Tests use it to establish necessary execution order in a way that does not
// interfere with tsan (does not establish synchronization between threads).
alias invisible_barrier_t = __c_ulonglong;
import core.stdc.config;
alias __c_unsigned = uint;
// Default instance of the barrier, but a test can declare more manually.
__gshared invisible_barrier_t barrier;

extern (C) {
// These functions reside inside the tsan library.
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
}

void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
__tsan_testonly_barrier_init(barrier, count);
}

void barrier_wait(invisible_barrier_t *barrier) {
__tsan_testonly_barrier_wait(barrier);
}
//----------------------------------------------------------------------------
60 changes: 60 additions & 0 deletions tests/sanitizers/tsan_tiny_race_TLS.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Test that ThreadSanitizer+LDC works on a very basic testcase.
// Note that -betterC is used, to avoid relying on druntime for this test.

// REQUIRES: TSan

// RUN: %ldc -betterC -g -fsanitize=thread %s -of=%t%exe
// RUN: %deflake 10 %t%exe | FileCheck %s

// CHECK: WARNING: ThreadSanitizer: data race

import core.sys.posix.pthread;

extern(C)
void *thread1(void *x) {
barrier_wait(&barrier);
// CHECK-DAG: thread1{{.*}}[[@LINE+1]]
*cast(int*)x = 42;
return x;
}

extern(C)
int main() {
int tls_variable;
barrier_init(&barrier, 2);
pthread_t t;
pthread_create(&t, null, &thread1, &tls_variable);
// CHECK-DAG: main{{.*}}[[@LINE+1]]
tls_variable = 43;
barrier_wait(&barrier);
pthread_join(t, null);
return 0;
}

//----------------------------------------------------------------------------
// Code to facilitate thread synchronization to make this test deterministic.
// See LLVM: compiler-rt/test/tsan/test.h

// TSan-invisible barrier.
// Tests use it to establish necessary execution order in a way that does not
// interfere with tsan (does not establish synchronization between threads).
alias invisible_barrier_t = __c_ulonglong;
import core.stdc.config;
alias __c_unsigned = uint;
// Default instance of the barrier, but a test can declare more manually.
__gshared invisible_barrier_t barrier;

extern (C) {
// These functions reside inside the tsan library.
void __tsan_testonly_barrier_init(invisible_barrier_t *barrier, __c_unsigned count);
void __tsan_testonly_barrier_wait(invisible_barrier_t *barrier);
}

void barrier_init(invisible_barrier_t *barrier, __c_unsigned count) {
__tsan_testonly_barrier_init(barrier, count);
}

void barrier_wait(invisible_barrier_t *barrier) {
__tsan_testonly_barrier_wait(barrier);
}
//----------------------------------------------------------------------------