From cfeab007a554034f0b3ab4a677cf9dd2696c12f9 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 30 Jul 2012 10:58:13 +0000 Subject: Import compiler-rt r160957. --- lib/tsan/CMakeLists.txt | 8 + lib/tsan/Makefile.mk | 18 + lib/tsan/Makefile.old | 106 ++ lib/tsan/analyze_libtsan.sh | 43 + lib/tsan/benchmarks/mini_bench_local.cc | 49 + lib/tsan/benchmarks/mini_bench_shared.cc | 51 + lib/tsan/benchmarks/start_many_threads.cc | 52 + lib/tsan/benchmarks/vts_many_threads_bench.cc | 120 ++ lib/tsan/check_analyze.sh | 43 + lib/tsan/go/buildgo.sh | 78 + lib/tsan/go/test.c | 51 + lib/tsan/go/tsan_go.cc | 185 +++ lib/tsan/output_tests/free_race.c | 43 + lib/tsan/output_tests/free_race2.c | 26 + lib/tsan/output_tests/heap_race.cc | 19 + lib/tsan/output_tests/memcpy_race.cc | 40 + lib/tsan/output_tests/mop_with_offset.cc | 36 + lib/tsan/output_tests/mop_with_offset2.cc | 36 + lib/tsan/output_tests/race_on_barrier.c | 31 + lib/tsan/output_tests/race_on_barrier2.c | 30 + lib/tsan/output_tests/race_on_mutex.c | 41 + lib/tsan/output_tests/race_with_finished_thread.cc | 43 + lib/tsan/output_tests/simple_race.c | 25 + lib/tsan/output_tests/simple_race.cc | 24 + lib/tsan/output_tests/simple_stack.c | 65 + lib/tsan/output_tests/simple_stack2.cc | 46 + lib/tsan/output_tests/static_init1.cc | 25 + lib/tsan/output_tests/static_init2.cc | 31 + lib/tsan/output_tests/static_init3.cc | 46 + lib/tsan/output_tests/static_init4.cc | 35 + lib/tsan/output_tests/static_init5.cc | 40 + lib/tsan/output_tests/suppress_same_address.cc | 27 + lib/tsan/output_tests/suppress_same_stacks.cc | 27 + lib/tsan/output_tests/test_output.sh | 49 + lib/tsan/output_tests/thread_leak.c | 15 + lib/tsan/output_tests/thread_leak2.c | 15 + lib/tsan/output_tests/thread_leak3.c | 14 + lib/tsan/output_tests/tiny_race.c | 14 + .../virtual_inheritance_compile_bug.cc | 13 + lib/tsan/output_tests/vptr_benign_race.cc | 50 + lib/tsan/output_tests/vptr_harmful_race.cc | 48 + lib/tsan/rtl/Makefile.mk | 23 + lib/tsan/rtl/Makefile.old | 59 + lib/tsan/rtl/tsan_clock.cc | 118 ++ lib/tsan/rtl/tsan_clock.h | 82 + lib/tsan/rtl/tsan_defs.h | 139 ++ lib/tsan/rtl/tsan_flags.cc | 79 + lib/tsan/rtl/tsan_flags.h | 71 + lib/tsan/rtl/tsan_interceptors.cc | 1596 ++++++++++++++++++++ lib/tsan/rtl/tsan_interface.cc | 42 + lib/tsan/rtl/tsan_interface.h | 51 + lib/tsan/rtl/tsan_interface_ann.cc | 352 +++++ lib/tsan/rtl/tsan_interface_ann.h | 31 + lib/tsan/rtl/tsan_interface_atomic.cc | 321 ++++ lib/tsan/rtl/tsan_interface_atomic.h | 121 ++ lib/tsan/rtl/tsan_interface_inl.h | 65 + lib/tsan/rtl/tsan_md5.cc | 245 +++ lib/tsan/rtl/tsan_mman.cc | 123 ++ lib/tsan/rtl/tsan_mman.h | 114 ++ lib/tsan/rtl/tsan_mutex.cc | 259 ++++ lib/tsan/rtl/tsan_mutex.h | 78 + lib/tsan/rtl/tsan_platform.h | 101 ++ lib/tsan/rtl/tsan_platform_linux.cc | 238 +++ lib/tsan/rtl/tsan_platform_mac.cc | 112 ++ lib/tsan/rtl/tsan_printf.cc | 39 + lib/tsan/rtl/tsan_report.cc | 167 ++ lib/tsan/rtl/tsan_report.h | 102 ++ lib/tsan/rtl/tsan_rtl.cc | 534 +++++++ lib/tsan/rtl/tsan_rtl.h | 491 ++++++ lib/tsan/rtl/tsan_rtl_amd64.S | 71 + lib/tsan/rtl/tsan_rtl_mutex.cc | 220 +++ lib/tsan/rtl/tsan_rtl_report.cc | 372 +++++ lib/tsan/rtl/tsan_rtl_thread.cc | 394 +++++ lib/tsan/rtl/tsan_stat.cc | 249 +++ lib/tsan/rtl/tsan_stat.h | 254 ++++ lib/tsan/rtl/tsan_suppressions.cc | 163 ++ lib/tsan/rtl/tsan_suppressions.h | 43 + lib/tsan/rtl/tsan_symbolize.cc | 78 + lib/tsan/rtl/tsan_symbolize.h | 31 + lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc | 193 +++ lib/tsan/rtl/tsan_sync.cc | 219 +++ lib/tsan/rtl/tsan_sync.h | 106 ++ lib/tsan/rtl/tsan_trace.h | 71 + lib/tsan/rtl/tsan_update_shadow_word_inl.h | 79 + lib/tsan/rtl/tsan_vector.h | 110 ++ lib/tsan/rtl_tests/tsan_bench.cc | 105 ++ lib/tsan/rtl_tests/tsan_mop.cc | 233 +++ lib/tsan/rtl_tests/tsan_mutex.cc | 221 +++ lib/tsan/rtl_tests/tsan_posix.cc | 146 ++ lib/tsan/rtl_tests/tsan_string.cc | 82 + lib/tsan/rtl_tests/tsan_test.cc | 44 + lib/tsan/rtl_tests/tsan_test_util.h | 122 ++ lib/tsan/rtl_tests/tsan_test_util_linux.cc | 465 ++++++ lib/tsan/rtl_tests/tsan_thread.cc | 59 + lib/tsan/unit_tests/tsan_clock_test.cc | 123 ++ lib/tsan/unit_tests/tsan_flags_test.cc | 38 + lib/tsan/unit_tests/tsan_mman_test.cc | 109 ++ lib/tsan/unit_tests/tsan_mutex_test.cc | 126 ++ lib/tsan/unit_tests/tsan_platform_test.cc | 88 ++ lib/tsan/unit_tests/tsan_printf_test.cc | 106 ++ lib/tsan/unit_tests/tsan_shadow_test.cc | 47 + lib/tsan/unit_tests/tsan_suppressions_test.cc | 128 ++ lib/tsan/unit_tests/tsan_sync_test.cc | 65 + lib/tsan/unit_tests/tsan_vector_test.cc | 45 + 104 files changed, 12516 insertions(+) create mode 100644 lib/tsan/CMakeLists.txt create mode 100644 lib/tsan/Makefile.mk create mode 100644 lib/tsan/Makefile.old create mode 100755 lib/tsan/analyze_libtsan.sh create mode 100644 lib/tsan/benchmarks/mini_bench_local.cc create mode 100644 lib/tsan/benchmarks/mini_bench_shared.cc create mode 100644 lib/tsan/benchmarks/start_many_threads.cc create mode 100644 lib/tsan/benchmarks/vts_many_threads_bench.cc create mode 100755 lib/tsan/check_analyze.sh create mode 100755 lib/tsan/go/buildgo.sh create mode 100644 lib/tsan/go/test.c create mode 100644 lib/tsan/go/tsan_go.cc create mode 100644 lib/tsan/output_tests/free_race.c create mode 100644 lib/tsan/output_tests/free_race2.c create mode 100644 lib/tsan/output_tests/heap_race.cc create mode 100644 lib/tsan/output_tests/memcpy_race.cc create mode 100644 lib/tsan/output_tests/mop_with_offset.cc create mode 100644 lib/tsan/output_tests/mop_with_offset2.cc create mode 100644 lib/tsan/output_tests/race_on_barrier.c create mode 100644 lib/tsan/output_tests/race_on_barrier2.c create mode 100644 lib/tsan/output_tests/race_on_mutex.c create mode 100644 lib/tsan/output_tests/race_with_finished_thread.cc create mode 100644 lib/tsan/output_tests/simple_race.c create mode 100644 lib/tsan/output_tests/simple_race.cc create mode 100644 lib/tsan/output_tests/simple_stack.c create mode 100644 lib/tsan/output_tests/simple_stack2.cc create mode 100644 lib/tsan/output_tests/static_init1.cc create mode 100644 lib/tsan/output_tests/static_init2.cc create mode 100644 lib/tsan/output_tests/static_init3.cc create mode 100644 lib/tsan/output_tests/static_init4.cc create mode 100644 lib/tsan/output_tests/static_init5.cc create mode 100644 lib/tsan/output_tests/suppress_same_address.cc create mode 100644 lib/tsan/output_tests/suppress_same_stacks.cc create mode 100755 lib/tsan/output_tests/test_output.sh create mode 100644 lib/tsan/output_tests/thread_leak.c create mode 100644 lib/tsan/output_tests/thread_leak2.c create mode 100644 lib/tsan/output_tests/thread_leak3.c create mode 100644 lib/tsan/output_tests/tiny_race.c create mode 100644 lib/tsan/output_tests/virtual_inheritance_compile_bug.cc create mode 100644 lib/tsan/output_tests/vptr_benign_race.cc create mode 100644 lib/tsan/output_tests/vptr_harmful_race.cc create mode 100644 lib/tsan/rtl/Makefile.mk create mode 100644 lib/tsan/rtl/Makefile.old create mode 100644 lib/tsan/rtl/tsan_clock.cc create mode 100644 lib/tsan/rtl/tsan_clock.h create mode 100644 lib/tsan/rtl/tsan_defs.h create mode 100644 lib/tsan/rtl/tsan_flags.cc create mode 100644 lib/tsan/rtl/tsan_flags.h create mode 100644 lib/tsan/rtl/tsan_interceptors.cc create mode 100644 lib/tsan/rtl/tsan_interface.cc create mode 100644 lib/tsan/rtl/tsan_interface.h create mode 100644 lib/tsan/rtl/tsan_interface_ann.cc create mode 100644 lib/tsan/rtl/tsan_interface_ann.h create mode 100644 lib/tsan/rtl/tsan_interface_atomic.cc create mode 100644 lib/tsan/rtl/tsan_interface_atomic.h create mode 100644 lib/tsan/rtl/tsan_interface_inl.h create mode 100644 lib/tsan/rtl/tsan_md5.cc create mode 100644 lib/tsan/rtl/tsan_mman.cc create mode 100644 lib/tsan/rtl/tsan_mman.h create mode 100644 lib/tsan/rtl/tsan_mutex.cc create mode 100644 lib/tsan/rtl/tsan_mutex.h create mode 100644 lib/tsan/rtl/tsan_platform.h create mode 100644 lib/tsan/rtl/tsan_platform_linux.cc create mode 100644 lib/tsan/rtl/tsan_platform_mac.cc create mode 100644 lib/tsan/rtl/tsan_printf.cc create mode 100644 lib/tsan/rtl/tsan_report.cc create mode 100644 lib/tsan/rtl/tsan_report.h create mode 100644 lib/tsan/rtl/tsan_rtl.cc create mode 100644 lib/tsan/rtl/tsan_rtl.h create mode 100644 lib/tsan/rtl/tsan_rtl_amd64.S create mode 100644 lib/tsan/rtl/tsan_rtl_mutex.cc create mode 100644 lib/tsan/rtl/tsan_rtl_report.cc create mode 100644 lib/tsan/rtl/tsan_rtl_thread.cc create mode 100644 lib/tsan/rtl/tsan_stat.cc create mode 100644 lib/tsan/rtl/tsan_stat.h create mode 100644 lib/tsan/rtl/tsan_suppressions.cc create mode 100644 lib/tsan/rtl/tsan_suppressions.h create mode 100644 lib/tsan/rtl/tsan_symbolize.cc create mode 100644 lib/tsan/rtl/tsan_symbolize.h create mode 100644 lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc create mode 100644 lib/tsan/rtl/tsan_sync.cc create mode 100644 lib/tsan/rtl/tsan_sync.h create mode 100644 lib/tsan/rtl/tsan_trace.h create mode 100644 lib/tsan/rtl/tsan_update_shadow_word_inl.h create mode 100644 lib/tsan/rtl/tsan_vector.h create mode 100644 lib/tsan/rtl_tests/tsan_bench.cc create mode 100644 lib/tsan/rtl_tests/tsan_mop.cc create mode 100644 lib/tsan/rtl_tests/tsan_mutex.cc create mode 100644 lib/tsan/rtl_tests/tsan_posix.cc create mode 100644 lib/tsan/rtl_tests/tsan_string.cc create mode 100644 lib/tsan/rtl_tests/tsan_test.cc create mode 100644 lib/tsan/rtl_tests/tsan_test_util.h create mode 100644 lib/tsan/rtl_tests/tsan_test_util_linux.cc create mode 100644 lib/tsan/rtl_tests/tsan_thread.cc create mode 100644 lib/tsan/unit_tests/tsan_clock_test.cc create mode 100644 lib/tsan/unit_tests/tsan_flags_test.cc create mode 100644 lib/tsan/unit_tests/tsan_mman_test.cc create mode 100644 lib/tsan/unit_tests/tsan_mutex_test.cc create mode 100644 lib/tsan/unit_tests/tsan_platform_test.cc create mode 100644 lib/tsan/unit_tests/tsan_printf_test.cc create mode 100644 lib/tsan/unit_tests/tsan_shadow_test.cc create mode 100644 lib/tsan/unit_tests/tsan_suppressions_test.cc create mode 100644 lib/tsan/unit_tests/tsan_sync_test.cc create mode 100644 lib/tsan/unit_tests/tsan_vector_test.cc (limited to 'lib/tsan') diff --git a/lib/tsan/CMakeLists.txt b/lib/tsan/CMakeLists.txt new file mode 100644 index 0000000..acfb854 --- /dev/null +++ b/lib/tsan/CMakeLists.txt @@ -0,0 +1,8 @@ +# Build for the AddressSanitizer runtime support library. + +file(GLOB TSAN_SOURCES "*.cc") + +if(CAN_TARGET_X86_64) + add_library(clang_rt.tsan-x86_64 STATIC ${TSAN_SOURCES}) + set_target_properties(clang_rt.tsan-x86_64 PROPERTIES COMPILE_FLAGS "${TARGET_X86_64_CFLAGS}") +endif() diff --git a/lib/tsan/Makefile.mk b/lib/tsan/Makefile.mk new file mode 100644 index 0000000..70fb610 --- /dev/null +++ b/lib/tsan/Makefile.mk @@ -0,0 +1,18 @@ +#===- lib/tsan/Makefile.mk ---------------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := tsan +SubDirs := rtl +Sources := +ObjNames := +Dependencies := + +Implementation := Generic + +TsanFunctions := diff --git a/lib/tsan/Makefile.old b/lib/tsan/Makefile.old new file mode 100644 index 0000000..2091f61 --- /dev/null +++ b/lib/tsan/Makefile.old @@ -0,0 +1,106 @@ +DEBUG=0 +LDFLAGS=-ldl -lpthread -pie +CXXFLAGS = -fPIE -g -Wall -Werror -DTSAN_DEBUG=$(DEBUG) +# Silence warnings that Clang produces for gtest code. +# Use -Wno-attributes so that gcc doesn't complain about unknown warning types. +CXXFLAGS += -Wno-attributes +ifeq ($(DEBUG), 0) + CXXFLAGS += -O3 +endif +ifeq ($(CXX), clang++) + CXXFLAGS+= -Wno-unused-private-field -Wno-static-in-inline +endif + +LIBTSAN=rtl/libtsan.a +GTEST_ROOT=third_party/googletest +GTEST_INCLUDE=-I$(GTEST_ROOT)/include +GTEST_BUILD_DIR=$(GTEST_ROOT)/build +GTEST_LIB=$(GTEST_BUILD_DIR)/gtest-all.o + +SANITIZER_COMMON_TESTS_SRC=$(wildcard ../sanitizer_common/tests/*_test.cc) +SANITIZER_COMMON_TESTS_OBJ=$(patsubst %.cc,%.o,$(SANITIZER_COMMON_TESTS_SRC)) +RTL_TEST_SRC=$(wildcard rtl_tests/*.cc) +RTL_TEST_OBJ=$(patsubst %.cc,%.o,$(RTL_TEST_SRC)) +UNIT_TEST_SRC=$(wildcard unit_tests/*_test.cc) +UNIT_TEST_OBJ=$(patsubst %.cc,%.o,$(UNIT_TEST_SRC)) +UNIT_TEST_HDR=$(wildcard rtl/*.h) $(wildcard ../sanitizer_common/*.h) + +INCLUDES=-Irtl -I.. $(GTEST_INCLUDE) + +all: libtsan test + +help: + @ echo "A little help is always welcome!" + @ echo "The most useful targets are:" + @ echo " make install_deps # Install third-party dependencies required for building" + @ echo " make presubmit # Run it every time before committing" + @ echo " make lint # Run the style checker" + @ echo + @ echo "For more info, see http://code.google.com/p/data-race-test/wiki/ThreadSanitizer2" + +$(LIBTSAN): libtsan + +libtsan: + $(MAKE) -C rtl -f Makefile.old DEBUG=$(DEBUG) + +%.o: %.cc $(UNIT_TEST_HDR) $(LIBTSAN) + $(CXX) $(CXXFLAGS) $(CFLAGS) $(INCLUDES) -o $@ -c $< + +tsan_test: $(UNIT_TEST_OBJ) $(RTL_TEST_OBJ) \ + $(SANITIZER_COMMON_TESTS_OBJ) $(LIBTSAN) $(GTEST_LIB) + $(CXX) $^ -o $@ $(LDFLAGS) + +test: libtsan tsan_test + +run: all + (ulimit -s 8192; ./tsan_test) + ./output_tests/test_output.sh + +presubmit: + $(MAKE) -f Makefile.old lint -j 4 + # Debug build with clang. + $(MAKE) -f Makefile.old clean + $(MAKE) -f Makefile.old run DEBUG=1 -j 16 CC=clang CXX=clang++ + # Release build with clang. + $(MAKE) -f Makefile.old clean + $(MAKE) -f Makefile.old run DEBUG=0 -j 16 CC=clang CXX=clang++ + # Debug build with gcc + $(MAKE) -f Makefile.old clean + $(MAKE) -f Makefile.old run DEBUG=1 -j 16 CC=gcc CXX=g++ + # Release build with gcc + $(MAKE) -f Makefile.old clean + $(MAKE) -f Makefile.old run DEBUG=0 -j 16 CC=gcc CXX=g++ + ./check_analyze.sh + @ echo PRESUBMIT PASSED + +RTL_LINT_FITLER=-legal/copyright,-build/include,-readability/casting,-build/header_guard,-build/namespaces + +lint: lint_tsan lint_tests +lint_tsan: + third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FITLER) rtl/*.{cc,h} \ + ../sanitizer_common/*.{cc,h} +lint_tests: + third_party/cpplint/cpplint.py --filter=$(RTL_LINT_FITLER) \ + rtl_tests/*.{cc,h} unit_tests/*.cc ../sanitizer_common/tests/*.cc + +install_deps: + rm -rf third_party + mkdir third_party + (cd third_party && \ + svn co -r613 http://googletest.googlecode.com/svn/trunk googletest && \ + svn co -r82 http://google-styleguide.googlecode.com/svn/trunk/cpplint cpplint \ + ) + +# Remove verbose printf from lint. Not strictly necessary. +hack_cpplint: + sed -i "s/ sys.stderr.write('Done processing.*//g" third_party/cpplint/cpplint.py + +$(GTEST_LIB): + mkdir -p $(GTEST_BUILD_DIR) && \ + cd $(GTEST_BUILD_DIR) && \ + $(MAKE) -f ../make/Makefile CXXFLAGS="$(CXXFLAGS)" CFLAGS="$(CFLAGS)" CC=$(CC) CXX=$(CXX) + +clean: + rm -f asm_*.s libtsan.nm libtsan.objdump */*.o tsan_test + rm -rf $(GTEST_BUILD_DIR) + $(MAKE) clean -C rtl -f Makefile.old diff --git a/lib/tsan/analyze_libtsan.sh b/lib/tsan/analyze_libtsan.sh new file mode 100755 index 0000000..e080561 --- /dev/null +++ b/lib/tsan/analyze_libtsan.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -e +set -u + +get_asm() { + grep tsan_$1.: -A 10000 libtsan.objdump | \ + awk "/[^:]$/ {print;} />:/ {c++; if (c == 2) {exit}}" +} + +list="write1 \ + write2 \ + write4 \ + write8 \ + read1 \ + read2 \ + read4 \ + read8 \ + func_entry \ + func_exit" + +BIN=`dirname $0`/tsan_test +objdump -d $BIN > libtsan.objdump +nm -S $BIN | grep "__tsan_" > libtsan.nm + +for f in $list; do + file=asm_$f.s + get_asm $f > $file + tot=$(wc -l < $file) + size=$(grep $f$ libtsan.nm | awk --non-decimal-data '{print ("0x"$2)+0}') + rsp=$(grep '(%rsp)' $file | wc -l) + push=$(grep 'push' $file | wc -l) + pop=$(grep 'pop' $file | wc -l) + call=$(grep 'call' $file | wc -l) + load=$(egrep 'mov .*\,.*\(.*\)|cmp .*\,.*\(.*\)' $file | wc -l) + store=$(egrep 'mov .*\(.*\),' $file | wc -l) + mov=$(grep 'mov' $file | wc -l) + lea=$(grep 'lea' $file | wc -l) + sh=$(grep 'shr\|shl' $file | wc -l) + cmp=$(grep 'cmp\|test' $file | wc -l) + printf "%10s tot %3d; size %4d; rsp %d; push %d; pop %d; call %d; load %2d; store %2d; sh %3d; mov %3d; lea %3d; cmp %3d\n" \ + $f $tot $size $rsp $push $pop $call $load $store $sh $mov $lea $cmp; +done diff --git a/lib/tsan/benchmarks/mini_bench_local.cc b/lib/tsan/benchmarks/mini_bench_local.cc new file mode 100644 index 0000000..accdcb6 --- /dev/null +++ b/lib/tsan/benchmarks/mini_bench_local.cc @@ -0,0 +1,49 @@ +// Mini-benchmark for tsan: non-shared memory writes. +#include +#include +#include +#include + +int len; +int *a; +const int kNumIter = 1000; + +__attribute__((noinline)) +void Run(int idx) { + for (int i = 0, n = len; i < n; i++) + a[i + idx * n] = i; +} + +void *Thread(void *arg) { + long idx = (long)arg; + printf("Thread %ld started\n", idx); + for (int i = 0; i < kNumIter; i++) + Run(idx); + printf("Thread %ld done\n", idx); + return 0; +} + +int main(int argc, char **argv) { + int n_threads = 0; + if (argc != 3) { + n_threads = 4; + len = 1000000; + } else { + n_threads = atoi(argv[1]); + assert(n_threads > 0 && n_threads <= 32); + len = atoi(argv[2]); + } + printf("%s: n_threads=%d len=%d iter=%d\n", + __FILE__, n_threads, len, kNumIter); + a = new int[n_threads * len]; + pthread_t *t = new pthread_t[n_threads]; + for (int i = 0; i < n_threads; i++) { + pthread_create(&t[i], 0, Thread, (void*)i); + } + for (int i = 0; i < n_threads; i++) { + pthread_join(t[i], 0); + } + delete [] t; + delete [] a; + return 0; +} diff --git a/lib/tsan/benchmarks/mini_bench_shared.cc b/lib/tsan/benchmarks/mini_bench_shared.cc new file mode 100644 index 0000000..f9b9f42 --- /dev/null +++ b/lib/tsan/benchmarks/mini_bench_shared.cc @@ -0,0 +1,51 @@ +// Mini-benchmark for tsan: shared memory reads. +#include +#include +#include +#include + +int len; +int *a; +const int kNumIter = 1000; + +__attribute__((noinline)) +void Run(int idx) { + for (int i = 0, n = len; i < n; i++) + if (a[i] != i) abort(); +} + +void *Thread(void *arg) { + long idx = (long)arg; + printf("Thread %ld started\n", idx); + for (int i = 0; i < kNumIter; i++) + Run(idx); + printf("Thread %ld done\n", idx); + return 0; +} + +int main(int argc, char **argv) { + int n_threads = 0; + if (argc != 3) { + n_threads = 4; + len = 1000000; + } else { + n_threads = atoi(argv[1]); + assert(n_threads > 0 && n_threads <= 32); + len = atoi(argv[2]); + } + printf("%s: n_threads=%d len=%d iter=%d\n", + __FILE__, n_threads, len, kNumIter); + a = new int[len]; + for (int i = 0, n = len; i < n; i++) + a[i] = i; + pthread_t *t = new pthread_t[n_threads]; + for (int i = 0; i < n_threads; i++) { + pthread_create(&t[i], 0, Thread, (void*)i); + } + for (int i = 0; i < n_threads; i++) { + pthread_join(t[i], 0); + } + delete [] t; + delete [] a; + return 0; +} diff --git a/lib/tsan/benchmarks/start_many_threads.cc b/lib/tsan/benchmarks/start_many_threads.cc new file mode 100644 index 0000000..1e86fa6 --- /dev/null +++ b/lib/tsan/benchmarks/start_many_threads.cc @@ -0,0 +1,52 @@ +// Mini-benchmark for creating a lot of threads. +// +// Some facts: +// a) clang -O1 takes <15ms to start N=500 threads, +// consuming ~4MB more RAM than N=1. +// b) clang -O1 -ftsan takes ~26s to start N=500 threads, +// eats 5GB more RAM than N=1 (which is somewhat expected but still a lot) +// but then it consumes ~4GB of extra memory when the threads shut down! +// (definitely not in the barrier_wait interceptor) +// Also, it takes 26s to run with N=500 vs just 1.1s to run with N=1. +#include +#include +#include +#include +#include + +pthread_barrier_t all_threads_ready; + +void* Thread(void *unused) { + pthread_barrier_wait(&all_threads_ready); + return 0; +} + +int main(int argc, char **argv) { + int n_threads; + if (argc == 1) { + n_threads = 100; + } else if (argc == 2) { + n_threads = atoi(argv[1]); + } else { + printf("Usage: %s n_threads\n", argv[0]); + return 1; + } + printf("%s: n_threads=%d\n", __FILE__, n_threads); + + pthread_barrier_init(&all_threads_ready, NULL, n_threads + 1); + + pthread_t *t = new pthread_t[n_threads]; + for (int i = 0; i < n_threads; i++) { + int status = pthread_create(&t[i], 0, Thread, (void*)i); + assert(status == 0); + } + // sleep(5); // FIXME: simplify measuring the memory usage. + pthread_barrier_wait(&all_threads_ready); + for (int i = 0; i < n_threads; i++) { + pthread_join(t[i], 0); + } + // sleep(5); // FIXME: simplify measuring the memory usage. + delete [] t; + + return 0; +} diff --git a/lib/tsan/benchmarks/vts_many_threads_bench.cc b/lib/tsan/benchmarks/vts_many_threads_bench.cc new file mode 100644 index 0000000..f1056e2 --- /dev/null +++ b/lib/tsan/benchmarks/vts_many_threads_bench.cc @@ -0,0 +1,120 @@ +// Mini-benchmark for tsan VTS worst case performance +// Idea: +// 1) Spawn M + N threads (M >> N) +// We'll call the 'M' threads as 'garbage threads'. +// 2) Make sure all threads have created thus no TIDs were reused +// 3) Join the garbage threads +// 4) Do many sync operations on the remaining N threads +// +// It turns out that due to O(M+N) VTS complexity the (4) is much slower with +// when N is large. +// +// Some numbers: +// a) clang++ native O1 with n_iterations=200kk takes +// 5s regardless of M +// clang++ tsanv2 O1 with n_iterations=20kk takes +// 23.5s with M=200 +// 11.5s with M=1 +// i.e. tsanv2 is ~23x to ~47x slower than native, depends on M. +// b) g++ native O1 with n_iterations=200kk takes +// 5.5s regardless of M +// g++ tsanv1 O1 with n_iterations=2kk takes +// 39.5s with M=200 +// 20.5s with M=1 +// i.e. tsanv1 is ~370x to ~720x slower than native, depends on M. + +#include +#include +#include +#include + +class __attribute__((aligned(64))) Mutex { + public: + Mutex() { pthread_mutex_init(&m_, NULL); } + ~Mutex() { pthread_mutex_destroy(&m_); } + void Lock() { pthread_mutex_lock(&m_); } + void Unlock() { pthread_mutex_unlock(&m_); } + + private: + pthread_mutex_t m_; +}; + +const int kNumMutexes = 1024; +Mutex mutexes[kNumMutexes]; + +int n_threads, n_iterations; + +pthread_barrier_t all_threads_ready, main_threads_ready; + +void* GarbageThread(void *unused) { + pthread_barrier_wait(&all_threads_ready); + return 0; +} + +void *Thread(void *arg) { + long idx = (long)arg; + pthread_barrier_wait(&all_threads_ready); + + // Wait for the main thread to join the garbage threads. + pthread_barrier_wait(&main_threads_ready); + + printf("Thread %ld go!\n", idx); + int offset = idx * kNumMutexes / n_threads; + for (int i = 0; i < n_iterations; i++) { + mutexes[(offset + i) % kNumMutexes].Lock(); + mutexes[(offset + i) % kNumMutexes].Unlock(); + } + printf("Thread %ld done\n", idx); + return 0; +} + +int main(int argc, char **argv) { + int n_garbage_threads; + if (argc == 1) { + n_threads = 2; + n_garbage_threads = 200; + n_iterations = 20000000; + } else if (argc == 4) { + n_threads = atoi(argv[1]); + assert(n_threads > 0 && n_threads <= 32); + n_garbage_threads = atoi(argv[2]); + assert(n_garbage_threads > 0 && n_garbage_threads <= 16000); + n_iterations = atoi(argv[3]); + } else { + printf("Usage: %s n_threads n_garbage_threads n_iterations\n", argv[0]); + return 1; + } + printf("%s: n_threads=%d n_garbage_threads=%d n_iterations=%d\n", + __FILE__, n_threads, n_garbage_threads, n_iterations); + + pthread_barrier_init(&all_threads_ready, NULL, n_garbage_threads + n_threads + 1); + pthread_barrier_init(&main_threads_ready, NULL, n_threads + 1); + + pthread_t *t = new pthread_t[n_threads]; + { + pthread_t *g_t = new pthread_t[n_garbage_threads]; + for (int i = 0; i < n_garbage_threads; i++) { + int status = pthread_create(&g_t[i], 0, GarbageThread, NULL); + assert(status == 0); + } + for (int i = 0; i < n_threads; i++) { + int status = pthread_create(&t[i], 0, Thread, (void*)i); + assert(status == 0); + } + pthread_barrier_wait(&all_threads_ready); + printf("All threads started! Killing the garbage threads.\n"); + for (int i = 0; i < n_garbage_threads; i++) { + pthread_join(g_t[i], 0); + } + delete [] g_t; + } + printf("Resuming the main threads.\n"); + pthread_barrier_wait(&main_threads_ready); + + + for (int i = 0; i < n_threads; i++) { + pthread_join(t[i], 0); + } + delete [] t; + return 0; +} diff --git a/lib/tsan/check_analyze.sh b/lib/tsan/check_analyze.sh new file mode 100755 index 0000000..39d570b --- /dev/null +++ b/lib/tsan/check_analyze.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -u + +RES=$(./analyze_libtsan.sh) +PrintRes() { + printf "%s\n" "$RES" +} + +PrintRes + +mops="write1 \ + write2 \ + write4 \ + write8 \ + read1 \ + read2 \ + read4 \ + read8" +func="func_entry \ + func_exit" + +check() { + res=$(PrintRes | egrep "$1 .* $2 $3; ") + if [ "$res" == "" ]; then + echo FAILED $1 must contain $2 $3 + exit 1 + fi +} + +for f in $mops; do + check $f rsp 1 # To read caller pc. + check $f push 0 + check $f pop 0 +done + +for f in $func; do + check $f rsp 0 + check $f push 0 + check $f pop 0 + check $f call 1 # TraceSwitch() +done + +echo LGTM diff --git a/lib/tsan/go/buildgo.sh b/lib/tsan/go/buildgo.sh new file mode 100755 index 0000000..a0d2f67 --- /dev/null +++ b/lib/tsan/go/buildgo.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -e + +if [ "`uname -a | grep Linux`" != "" ]; then + LINUX=1 + SUFFIX="linux_amd64" +elif [ "`uname -a | grep Darwin`" != "" ]; then + MAC=1 + SUFFIX="darwin_amd64" +else + echo Unknown platform + exit 1 +fi + +SRCS=" + tsan_go.cc + ../rtl/tsan_clock.cc + ../rtl/tsan_flags.cc + ../rtl/tsan_md5.cc + ../rtl/tsan_mutex.cc + ../rtl/tsan_printf.cc + ../rtl/tsan_report.cc + ../rtl/tsan_rtl.cc + ../rtl/tsan_rtl_mutex.cc + ../rtl/tsan_rtl_report.cc + ../rtl/tsan_rtl_thread.cc + ../rtl/tsan_stat.cc + ../rtl/tsan_suppressions.cc + ../rtl/tsan_sync.cc + ../../sanitizer_common/sanitizer_allocator.cc + ../../sanitizer_common/sanitizer_common.cc + ../../sanitizer_common/sanitizer_flags.cc + ../../sanitizer_common/sanitizer_libc.cc + ../../sanitizer_common/sanitizer_posix.cc + ../../sanitizer_common/sanitizer_printf.cc + ../../sanitizer_common/sanitizer_symbolizer.cc +" + +if [ "$LINUX" != "" ]; then + SRCS+=" + ../rtl/tsan_platform_linux.cc + ../../sanitizer_common/sanitizer_linux.cc + " +elif [ "$MAC" != "" ]; then + SRCS+=" + ../rtl/tsan_platform_mac.cc + ../../sanitizer_common/sanitizer_mac.cc + " +fi + +SRCS+=$ADD_SRCS +#ASMS="../rtl/tsan_rtl_amd64.S" + +rm -f gotsan.cc +for F in $SRCS; do + cat $F >> gotsan.cc +done + +FLAGS=" -I../rtl -I../.. -I../../sanitizer_common -fPIC -g -Wall -Werror -fno-exceptions -DTSAN_GO -DSANITIZER_GO -DTSAN_SHADOW_COUNT=4" +if [ "$DEBUG" == "" ]; then + FLAGS+=" -DTSAN_DEBUG=0 -O3 -fomit-frame-pointer" +else + FLAGS+=" -DTSAN_DEBUG=1 -g" +fi + +if [ "$LINUX" != "" ]; then + FLAGS+=" -ffreestanding" +fi + +echo gcc gotsan.cc -S -o tmp.s $FLAGS $CFLAGS +gcc gotsan.cc -S -o tmp.s $FLAGS $CFLAGS +cat tmp.s $ASMS > gotsan.s +echo as gotsan.s -o race_$SUFFIX.syso +as gotsan.s -o race_$SUFFIX.syso + +gcc test.c race_$SUFFIX.syso -lpthread -o test +TSAN_OPTIONS="exitcode=0" ./test + diff --git a/lib/tsan/go/test.c b/lib/tsan/go/test.c new file mode 100644 index 0000000..a9a5b3d --- /dev/null +++ b/lib/tsan/go/test.c @@ -0,0 +1,51 @@ +//===-- test.c ------------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Sanity test for Go runtime. +// +//===----------------------------------------------------------------------===// + +#include + +void __tsan_init(); +void __tsan_fini(); +void __tsan_go_start(int pgoid, int chgoid, void *pc); +void __tsan_go_end(int goid); +void __tsan_read(int goid, void *addr, void *pc); +void __tsan_write(int goid, void *addr, void *pc); +void __tsan_func_enter(int goid, void *pc); +void __tsan_func_exit(int goid); +void __tsan_malloc(int goid, void *p, unsigned long sz, void *pc); +void __tsan_free(void *p); +void __tsan_acquire(int goid, void *addr); +void __tsan_release(int goid, void *addr); +void __tsan_release_merge(int goid, void *addr); + +int __tsan_symbolize(void *pc, char **img, char **rtn, char **file, int *l) { + return 0; +} + +char buf[10]; + +int main(void) { + __tsan_init(); + __tsan_func_enter(0, &main); + __tsan_malloc(0, buf, 10, 0); + __tsan_release(0, buf); + __tsan_release_merge(0, buf); + __tsan_go_start(0, 1, 0); + __tsan_write(1, buf, 0); + __tsan_acquire(1, buf); + __tsan_go_end(1); + __tsan_read(0, buf, 0); + __tsan_free(buf); + __tsan_func_exit(0); + __tsan_fini(); + return 0; +} diff --git a/lib/tsan/go/tsan_go.cc b/lib/tsan/go/tsan_go.cc new file mode 100644 index 0000000..4b3076c --- /dev/null +++ b/lib/tsan/go/tsan_go.cc @@ -0,0 +1,185 @@ +//===-- tsan_go.cc --------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// ThreadSanitizer runtime for Go language. +// +//===----------------------------------------------------------------------===// + +#include "tsan_rtl.h" +#include "tsan_symbolize.h" +#include "sanitizer_common/sanitizer_common.h" +#include + +namespace __tsan { + +static ThreadState *goroutines[kMaxTid]; + +void InitializeInterceptors() { +} + +void InitializeDynamicAnnotations() { +} + +bool IsExpectedReport(uptr addr, uptr size) { + return false; +} + +void internal_start_thread(void(*func)(void*), void *arg) { +} + +ReportStack *SymbolizeData(uptr addr) { + return 0; +} + +ReportStack *NewReportStackEntry(uptr addr) { + ReportStack *ent = (ReportStack*)internal_alloc(MBlockReportStack, + sizeof(ReportStack)); + internal_memset(ent, 0, sizeof(*ent)); + ent->pc = addr; + return ent; +} + +void *internal_alloc(MBlockType typ, uptr sz) { + return InternalAlloc(sz); +} + +void internal_free(void *p) { + InternalFree(p); +} + +// Callback into Go. +extern "C" int __tsan_symbolize(uptr pc, char **func, char **file, + int *line, int *off); + +ReportStack *SymbolizeCode(uptr addr) { + ReportStack *s = (ReportStack*)internal_alloc(MBlockReportStack, + sizeof(ReportStack)); + internal_memset(s, 0, sizeof(*s)); + s->pc = addr; + char *func = 0, *file = 0; + int line = 0, off = 0; + if (__tsan_symbolize(addr, &func, &file, &line, &off)) { + s->offset = off; + s->func = internal_strdup(func ? func : "??"); + s->file = internal_strdup(file ? file : "-"); + s->line = line; + s->col = 0; + free(func); + free(file); + } + return s; +} + +extern "C" { + +static void AllocGoroutine(int tid) { + goroutines[tid] = (ThreadState*)internal_alloc(MBlockThreadContex, + sizeof(ThreadState)); + internal_memset(goroutines[tid], 0, sizeof(ThreadState)); +} + +void __tsan_init() { + AllocGoroutine(0); + ThreadState *thr = goroutines[0]; + thr->in_rtl++; + Initialize(thr); + thr->in_rtl--; +} + +void __tsan_fini() { + // FIXME: Not necessary thread 0. + ThreadState *thr = goroutines[0]; + thr->in_rtl++; + int res = Finalize(thr); + thr->in_rtl--; + exit(res); +} + +void __tsan_read(int goid, void *addr, void *pc) { + ThreadState *thr = goroutines[goid]; + MemoryAccess(thr, (uptr)pc, (uptr)addr, 0, false); +} + +void __tsan_write(int goid, void *addr, void *pc) { + ThreadState *thr = goroutines[goid]; + MemoryAccess(thr, (uptr)pc, (uptr)addr, 0, true); +} + +void __tsan_func_enter(int goid, void *pc) { + ThreadState *thr = goroutines[goid]; + FuncEntry(thr, (uptr)pc); +} + +void __tsan_func_exit(int goid) { + ThreadState *thr = goroutines[goid]; + FuncExit(thr); +} + +void __tsan_malloc(int goid, void *p, uptr sz, void *pc) { + ThreadState *thr = goroutines[goid]; + thr->in_rtl++; + MemoryResetRange(thr, (uptr)pc, (uptr)p, sz); + MemoryAccessRange(thr, (uptr)pc, (uptr)p, sz, true); + thr->in_rtl--; +} + +void __tsan_free(void *p) { + (void)p; +} + +void __tsan_go_start(int pgoid, int chgoid, void *pc) { + if (chgoid == 0) + return; + AllocGoroutine(chgoid); + ThreadState *thr = goroutines[chgoid]; + ThreadState *parent = goroutines[pgoid]; + thr->in_rtl++; + parent->in_rtl++; + int goid2 = ThreadCreate(parent, (uptr)pc, 0, true); + ThreadStart(thr, goid2); + parent->in_rtl--; + thr->in_rtl--; +} + +void __tsan_go_end(int goid) { + ThreadState *thr = goroutines[goid]; + thr->in_rtl++; + ThreadFinish(thr); + thr->in_rtl--; +} + +void __tsan_acquire(int goid, void *addr) { + ThreadState *thr = goroutines[goid]; + thr->in_rtl++; + Acquire(thr, 0, (uptr)addr); + thr->in_rtl--; + //internal_free(thr); +} + +void __tsan_release(int goid, void *addr) { + ThreadState *thr = goroutines[goid]; + thr->in_rtl++; + ReleaseStore(thr, 0, (uptr)addr); + thr->in_rtl--; +} + +void __tsan_release_merge(int goid, void *addr) { + ThreadState *thr = goroutines[goid]; + thr->in_rtl++; + Release(thr, 0, (uptr)addr); + thr->in_rtl--; +} + +void __tsan_finalizer_goroutine(int goid) { + ThreadState *thr = goroutines[goid]; + ThreadFinalizerGoroutine(thr); +} + +} // extern "C" +} // namespace __tsan diff --git a/lib/tsan/output_tests/free_race.c b/lib/tsan/output_tests/free_race.c new file mode 100644 index 0000000..fb7fbac --- /dev/null +++ b/lib/tsan/output_tests/free_race.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +int *mem; +pthread_mutex_t mtx; + +void *Thread1(void *x) { + pthread_mutex_lock(&mtx); + free(mem); + pthread_mutex_unlock(&mtx); + return NULL; +} + +void *Thread2(void *x) { + usleep(1000000); + pthread_mutex_lock(&mtx); + mem[0] = 42; + pthread_mutex_unlock(&mtx); + return NULL; +} + +int main() { + mem = (int*)malloc(100); + pthread_mutex_init(&mtx, 0); + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + pthread_mutex_destroy(&mtx); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: heap-use-after-free +// CHECK: Write of size 4 at {{.*}} by main thread: +// CHECK: #0 Thread2 +// CHECK: #1 main +// CHECK: Previous write of size 8 at {{.*}} by thread 1: +// CHECK: #0 free +// CHECK: #1 Thread1 + diff --git a/lib/tsan/output_tests/free_race2.c b/lib/tsan/output_tests/free_race2.c new file mode 100644 index 0000000..7b2bdec --- /dev/null +++ b/lib/tsan/output_tests/free_race2.c @@ -0,0 +1,26 @@ +#include + +void __attribute__((noinline)) foo(int *mem) { + free(mem); +} + +void __attribute__((noinline)) bar(int *mem) { + mem[0] = 42; +} + +int main() { + int *mem = (int*)malloc(100); + foo(mem); + bar(mem); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: heap-use-after-free +// CHECK: Write of size 4 at {{.*}} by main thread: +// CHECK: #0 bar +// CHECK: #1 main +// CHECK: Previous write of size 8 at {{.*}} by main thread: +// CHECK: #0 free +// CHECK: #1 foo +// CHECK: #2 main + diff --git a/lib/tsan/output_tests/heap_race.cc b/lib/tsan/output_tests/heap_race.cc new file mode 100644 index 0000000..e92bb37 --- /dev/null +++ b/lib/tsan/output_tests/heap_race.cc @@ -0,0 +1,19 @@ +#include +#include +#include + +void *Thread(void *a) { + ((int*)a)[0]++; + return NULL; +} + +int main() { + int *p = new int(42); + pthread_t t; + pthread_create(&t, NULL, Thread, p); + p[0]++; + pthread_join(t, NULL); + delete p; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/memcpy_race.cc b/lib/tsan/output_tests/memcpy_race.cc new file mode 100644 index 0000000..c6b79a7 --- /dev/null +++ b/lib/tsan/output_tests/memcpy_race.cc @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include + +char *data = new char[10]; +char *data1 = new char[10]; +char *data2 = new char[10]; + +void *Thread1(void *x) { + memcpy(data+5, data1, 1); + return NULL; +} + +void *Thread2(void *x) { + usleep(500*1000); + memcpy(data+3, data2, 4); + return NULL; +} + +int main() { + fprintf(stderr, "addr=%p\n", &data[5]); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: addr=[[ADDR:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 at [[ADDR]] by thread 2: +// CHECK: #0 memcpy +// CHECK: #1 Thread2 +// CHECK: Previous write of size 1 at [[ADDR]] by thread 1: +// CHECK: #0 memcpy +// CHECK: #1 Thread1 + diff --git a/lib/tsan/output_tests/mop_with_offset.cc b/lib/tsan/output_tests/mop_with_offset.cc new file mode 100644 index 0000000..fc497bf --- /dev/null +++ b/lib/tsan/output_tests/mop_with_offset.cc @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +void *Thread1(void *x) { + int *p = (int*)x; + p[0] = 1; + return NULL; +} + +void *Thread2(void *x) { + usleep(500*1000); + char *p = (char*)x; + p[2] = 1; + return NULL; +} + +int main() { + int *data = new int(42); + fprintf(stderr, "ptr1=%p\n", data); + fprintf(stderr, "ptr2=%p\n", (char*)data + 2); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, data); + pthread_create(&t[1], NULL, Thread2, data); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + delete data; +} + +// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] +// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 1 at [[PTR2]] by thread 2: +// CHECK: Previous write of size 4 at [[PTR1]] by thread 1: + diff --git a/lib/tsan/output_tests/mop_with_offset2.cc b/lib/tsan/output_tests/mop_with_offset2.cc new file mode 100644 index 0000000..bbeda55 --- /dev/null +++ b/lib/tsan/output_tests/mop_with_offset2.cc @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +void *Thread1(void *x) { + usleep(500*1000); + int *p = (int*)x; + p[0] = 1; + return NULL; +} + +void *Thread2(void *x) { + char *p = (char*)x; + p[2] = 1; + return NULL; +} + +int main() { + int *data = new int(42); + fprintf(stderr, "ptr1=%p\n", data); + fprintf(stderr, "ptr2=%p\n", (char*)data + 2); + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, data); + pthread_create(&t[1], NULL, Thread2, data); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + delete data; +} + +// CHECK: ptr1=[[PTR1:0x[0-9,a-f]+]] +// CHECK: ptr2=[[PTR2:0x[0-9,a-f]+]] +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at [[PTR1]] by thread 1: +// CHECK: Previous write of size 1 at [[PTR2]] by thread 2: + diff --git a/lib/tsan/output_tests/race_on_barrier.c b/lib/tsan/output_tests/race_on_barrier.c new file mode 100644 index 0000000..98d7a1d8 --- /dev/null +++ b/lib/tsan/output_tests/race_on_barrier.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +pthread_barrier_t B; +int Global; + +void *Thread1(void *x) { + pthread_barrier_init(&B, 0, 2); + pthread_barrier_wait(&B); + return NULL; +} + +void *Thread2(void *x) { + usleep(1000000); + pthread_barrier_wait(&B); + return NULL; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + pthread_barrier_destroy(&B); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race + diff --git a/lib/tsan/output_tests/race_on_barrier2.c b/lib/tsan/output_tests/race_on_barrier2.c new file mode 100644 index 0000000..dbdb6b5 --- /dev/null +++ b/lib/tsan/output_tests/race_on_barrier2.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +pthread_barrier_t B; +int Global; + +void *Thread1(void *x) { + if (pthread_barrier_wait(&B) == PTHREAD_BARRIER_SERIAL_THREAD) + pthread_barrier_destroy(&B); + return NULL; +} + +void *Thread2(void *x) { + if (pthread_barrier_wait(&B) == PTHREAD_BARRIER_SERIAL_THREAD) + pthread_barrier_destroy(&B); + return NULL; +} + +int main() { + pthread_barrier_init(&B, 0, 2); + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Thread2(0); + pthread_join(t, NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/race_on_mutex.c b/lib/tsan/output_tests/race_on_mutex.c new file mode 100644 index 0000000..45c75be --- /dev/null +++ b/lib/tsan/output_tests/race_on_mutex.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +pthread_mutex_t Mtx; +int Global; + +void *Thread1(void *x) { + pthread_mutex_init(&Mtx, 0); + pthread_mutex_lock(&Mtx); + Global = 42; + pthread_mutex_unlock(&Mtx); + return NULL; +} + +void *Thread2(void *x) { + usleep(1000000); + pthread_mutex_lock(&Mtx); + Global = 43; + pthread_mutex_unlock(&Mtx); + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + pthread_mutex_destroy(&Mtx); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Read of size 1 at {{.*}} by thread 2: +// CHECK-NEXT: #0 pthread_mutex_lock {{.*}} ({{.*}}) +// CHECK-NEXT: #1 Thread2 {{.*}}race_on_mutex.c:19{{(:3)?}} ({{.*}}) +// CHECK-NEXT: Previous write of size 1 at {{.*}} by thread 1: +// CHECK-NEXT: #0 pthread_mutex_init {{.*}} ({{.*}}) +// CHECK-NEXT: #1 Thread1 {{.*}}race_on_mutex.c:10{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/output_tests/race_with_finished_thread.cc b/lib/tsan/output_tests/race_with_finished_thread.cc new file mode 100644 index 0000000..1f60f4b --- /dev/null +++ b/lib/tsan/output_tests/race_with_finished_thread.cc @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +// Ensure that we can restore a stack of a finished thread. + +int g_data; + +void __attribute__((noinline)) foobar(int *p) { + *p = 42; +} + +void *Thread1(void *x) { + foobar(&g_data); + return NULL; +} + +void *Thread2(void *x) { + usleep(1000*1000); + g_data = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK: Write of size 4 at {{.*}} by thread 2: +// CHECK: Previous write of size 4 at {{.*}} by thread 1: +// CHECK: #0 foobar +// CHECK: #1 Thread1 +// CHECK: Thread 1 (finished) created at: +// CHECK: #0 pthread_create +// CHECK: #1 main + diff --git a/lib/tsan/output_tests/simple_race.c b/lib/tsan/output_tests/simple_race.c new file mode 100644 index 0000000..ed831fd --- /dev/null +++ b/lib/tsan/output_tests/simple_race.c @@ -0,0 +1,25 @@ +#include +#include + +int Global; + +void *Thread1(void *x) { + Global = 42; + return NULL; +} + +void *Thread2(void *x) { + Global = 43; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/simple_race.cc b/lib/tsan/output_tests/simple_race.cc new file mode 100644 index 0000000..8d2cabf --- /dev/null +++ b/lib/tsan/output_tests/simple_race.cc @@ -0,0 +1,24 @@ +#include +#include + +int Global; + +void *Thread1(void *x) { + Global++; + return NULL; +} + +void *Thread2(void *x) { + Global--; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/simple_stack.c b/lib/tsan/output_tests/simple_stack.c new file mode 100644 index 0000000..2e94f23 --- /dev/null +++ b/lib/tsan/output_tests/simple_stack.c @@ -0,0 +1,65 @@ +#include +#include +#include + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; (void)tmp; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int v = Global; (void)v; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; (void)tmp; + foo2(); +} + +void *Thread1(void *x) { + usleep(1000000); + bar1(); + return NULL; +} + +void *Thread2(void *x) { + bar2(); + return NULL; +} + +void StartThread(pthread_t *t, void *(*f)(void*)) { + pthread_create(t, NULL, f, NULL); +} + +int main() { + pthread_t t[2]; + StartThread(&t[0], Thread1); + StartThread(&t[1], Thread2); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1: +// CHECK-NEXT: #0 foo1 {{.*}}simple_stack.c:8{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar1 {{.*}}simple_stack.c:13{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread1 {{.*}}simple_stack.c:27{{(:3)?}} ({{.*}}) +// CHECK-NEXT: Previous read of size 4 at {{.*}} by thread 2: +// CHECK-NEXT: #0 foo2 {{.*}}simple_stack.c:17{{(:26)?}} ({{.*}}) +// CHECK-NEXT: #1 bar2 {{.*}}simple_stack.c:22{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread2 {{.*}}simple_stack.c:32{{(:3)?}} ({{.*}}) +// CHECK-NEXT: Thread 1 (running) created at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread {{.*}}simple_stack.c:37{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main {{.*}}simple_stack.c:42{{(:3)?}} ({{.*}}) +// CHECK-NEXT: Thread 2 ({{.*}}) created at: +// CHECK-NEXT: #0 pthread_create {{.*}} ({{.*}}) +// CHECK-NEXT: #1 StartThread {{.*}}simple_stack.c:37{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main {{.*}}simple_stack.c:43{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/output_tests/simple_stack2.cc b/lib/tsan/output_tests/simple_stack2.cc new file mode 100644 index 0000000..336cc9f --- /dev/null +++ b/lib/tsan/output_tests/simple_stack2.cc @@ -0,0 +1,46 @@ +#include +#include +#include + +int Global; + +void __attribute__((noinline)) foo1() { + Global = 42; +} + +void __attribute__((noinline)) bar1() { + volatile int tmp = 42; int tmp2 = tmp; (void)tmp2; + foo1(); +} + +void __attribute__((noinline)) foo2() { + volatile int tmp = Global; int tmp2 = tmp; (void)tmp2; +} + +void __attribute__((noinline)) bar2() { + volatile int tmp = 42; int tmp2 = tmp; (void)tmp2; + foo2(); +} + +void *Thread1(void *x) { + usleep(1000000); + bar1(); + return NULL; +} + +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + bar2(); + pthread_join(t, NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race +// CHECK-NEXT: Write of size 4 at {{.*}} by thread 1: +// CHECK-NEXT: #0 foo1{{.*}} {{.*}}simple_stack2.cc:8{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #1 bar1{{.*}} {{.*}}simple_stack2.cc:13{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 Thread1{{.*}} {{.*}}simple_stack2.cc:27{{(:3)?}} ({{.*}}) +// CHECK-NEXT: Previous read of size 4 at {{.*}} by main thread: +// CHECK-NEXT: #0 foo2{{.*}} {{.*}}simple_stack2.cc:17{{(:28)?}} ({{.*}}) +// CHECK-NEXT: #1 bar2{{.*}} {{.*}}simple_stack2.cc:22{{(:3)?}} ({{.*}}) +// CHECK-NEXT: #2 main{{.*}} {{.*}}simple_stack2.cc:34{{(:3)?}} ({{.*}}) diff --git a/lib/tsan/output_tests/static_init1.cc b/lib/tsan/output_tests/static_init1.cc new file mode 100644 index 0000000..75d2819 --- /dev/null +++ b/lib/tsan/output_tests/static_init1.cc @@ -0,0 +1,25 @@ +#include +#include +#include + +struct P { + int x; + int y; +}; + +void *Thread(void *x) { + static P p = {rand(), rand()}; + if (p.x > RAND_MAX || p.y > RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread, 0); + pthread_create(&t[1], 0, Thread, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init2.cc b/lib/tsan/output_tests/static_init2.cc new file mode 100644 index 0000000..f6e9596 --- /dev/null +++ b/lib/tsan/output_tests/static_init2.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +struct Cache { + int x; + Cache(int x) + : x(x) { + } +}; + +void foo(Cache *my) { + static Cache *c = my ? my : new Cache(rand()); + if (c->x >= RAND_MAX) + exit(1); +} + +void *Thread(void *x) { + foo(new Cache(rand())); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread, 0); + pthread_create(&t[1], 0, Thread, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init3.cc b/lib/tsan/output_tests/static_init3.cc new file mode 100644 index 0000000..718f811 --- /dev/null +++ b/lib/tsan/output_tests/static_init3.cc @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +struct Cache { + int x; +}; + +Cache g_cache; + +Cache *CreateCache() { + g_cache.x = rand(); + return &g_cache; +} + +_Atomic(Cache*) queue; + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + __c11_atomic_store(&queue, c, 0); + return 0; +} + +void *Thread2(void *x) { + Cache *c = 0; + for (;;) { + c = __c11_atomic_load(&queue, 0); + if (c) + break; + sched_yield(); + } + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread2, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init4.cc b/lib/tsan/output_tests/static_init4.cc new file mode 100644 index 0000000..cdacbce --- /dev/null +++ b/lib/tsan/output_tests/static_init4.cc @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +struct Cache { + int x; + Cache(int x) + : x(x) { + } +}; + +int g_other; + +Cache *CreateCache() { + g_other = rand(); + return new Cache(rand()); +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x == g_other) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/static_init5.cc b/lib/tsan/output_tests/static_init5.cc new file mode 100644 index 0000000..4b050c9 --- /dev/null +++ b/lib/tsan/output_tests/static_init5.cc @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +struct Cache { + int x; + Cache(int x) + : x(x) { + } +}; + +void *AsyncInit(void *p) { + return new Cache((int)(long)p); +} + +Cache *CreateCache() { + pthread_t t; + pthread_create(&t, 0, AsyncInit, (void*)rand()); + void *res; + pthread_join(t, &res); + return (Cache*)res; +} + +void *Thread1(void *x) { + static Cache *c = CreateCache(); + if (c->x >= RAND_MAX) + exit(1); + return 0; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], 0, Thread1, 0); + pthread_create(&t[1], 0, Thread1, 0); + pthread_join(t[0], 0); + pthread_join(t[1], 0); +} + +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/suppress_same_address.cc b/lib/tsan/output_tests/suppress_same_address.cc new file mode 100644 index 0000000..6e98970 --- /dev/null +++ b/lib/tsan/output_tests/suppress_same_address.cc @@ -0,0 +1,27 @@ +#include + +int X; + +void *Thread1(void *x) { + X = 42; + X = 66; + X = 78; + return 0; +} + +void *Thread2(void *x) { + X = 11; + X = 99; + X = 73; + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + Thread2(0); + pthread_join(t, 0); +} + +// CHECK: ThreadSanitizer: reported 1 warnings + diff --git a/lib/tsan/output_tests/suppress_same_stacks.cc b/lib/tsan/output_tests/suppress_same_stacks.cc new file mode 100644 index 0000000..6046a4e --- /dev/null +++ b/lib/tsan/output_tests/suppress_same_stacks.cc @@ -0,0 +1,27 @@ +#include + +volatile int N; // Prevent loop unrolling. +int **data; + +void *Thread1(void *x) { + for (int i = 0; i < N; i++) + data[i][0] = 42; + return 0; +} + +int main() { + N = 4; + data = new int*[N]; + for (int i = 0; i < N; i++) + data[i] = new int; + pthread_t t; + pthread_create(&t, 0, Thread1, 0); + Thread1(0); + pthread_join(t, 0); + for (int i = 0; i < N; i++) + delete data[i]; + delete[] data; +} + +// CHECK: ThreadSanitizer: reported 1 warnings + diff --git a/lib/tsan/output_tests/test_output.sh b/lib/tsan/output_tests/test_output.sh new file mode 100755 index 0000000..bd9cd91 --- /dev/null +++ b/lib/tsan/output_tests/test_output.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +ulimit -s 8192 +set -e # fail on any error + +ROOTDIR=$(dirname $0)/.. + +# Assuming clang is in path. +CC=clang +CXX=clang++ + +# TODO: add testing for all of -O0...-O3 +CFLAGS="-fthread-sanitizer -fPIE -O1 -g -fno-builtin -Wall" +LDFLAGS="-pie -lpthread -ldl $ROOTDIR/rtl/libtsan.a" + +test_file() { + SRC=$1 + COMPILER=$2 + echo ----- TESTING $(basename $1) + OBJ=$SRC.o + EXE=$SRC.exe + $COMPILER $SRC $CFLAGS -c -o $OBJ + $COMPILER $OBJ $LDFLAGS -o $EXE + RES=$(TSAN_OPTIONS="atexit_sleep_ms=0" $EXE 2>&1 || true) + if [ "$3" != "" ]; then + printf "%s\n" "$RES" + fi + printf "%s\n" "$RES" | FileCheck $SRC + if [ "$3" == "" ]; then + rm -f $EXE $OBJ + fi +} + +if [ "$1" == "" ]; then + for c in $ROOTDIR/output_tests/*.{c,cc}; do + if [[ $c == */failing_* ]]; then + echo SKIPPING FAILING TEST $c + continue + fi + COMPILER=$CXX + case $c in + *.c) COMPILER=$CC + esac + test_file $c $COMPILER + done + wait +else + test_file $ROOTDIR/output_tests/$1 $CXX "DUMP" +fi diff --git a/lib/tsan/output_tests/thread_leak.c b/lib/tsan/output_tests/thread_leak.c new file mode 100644 index 0000000..88a11be --- /dev/null +++ b/lib/tsan/output_tests/thread_leak.c @@ -0,0 +1,15 @@ +#include + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_join(t, 0); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak + diff --git a/lib/tsan/output_tests/thread_leak2.c b/lib/tsan/output_tests/thread_leak2.c new file mode 100644 index 0000000..71e9c50 --- /dev/null +++ b/lib/tsan/output_tests/thread_leak2.c @@ -0,0 +1,15 @@ +#include + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + pthread_detach(t); + return 0; +} + +// CHECK-NOT: WARNING: ThreadSanitizer: thread leak + diff --git a/lib/tsan/output_tests/thread_leak3.c b/lib/tsan/output_tests/thread_leak3.c new file mode 100644 index 0000000..058b6e5 --- /dev/null +++ b/lib/tsan/output_tests/thread_leak3.c @@ -0,0 +1,14 @@ +#include + +void *Thread(void *x) { + return 0; +} + +int main() { + pthread_t t; + pthread_create(&t, 0, Thread, 0); + return 0; +} + +// CHECK: WARNING: ThreadSanitizer: thread leak + diff --git a/lib/tsan/output_tests/tiny_race.c b/lib/tsan/output_tests/tiny_race.c new file mode 100644 index 0000000..3a8d192 --- /dev/null +++ b/lib/tsan/output_tests/tiny_race.c @@ -0,0 +1,14 @@ +#include +int Global; +void *Thread1(void *x) { + Global = 42; + return x; +} +int main() { + pthread_t t; + pthread_create(&t, NULL, Thread1, NULL); + Global = 43; + pthread_join(t, NULL); + return Global; +} +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/virtual_inheritance_compile_bug.cc b/lib/tsan/output_tests/virtual_inheritance_compile_bug.cc new file mode 100644 index 0000000..fd2febe --- /dev/null +++ b/lib/tsan/output_tests/virtual_inheritance_compile_bug.cc @@ -0,0 +1,13 @@ +// Regression test for http://code.google.com/p/thread-sanitizer/issues/detail?id=3. +// The C++ variant is much more compact that the LLVM IR equivalent. +#include +struct AAA { virtual long aaa () { return 0; } }; +struct BBB: virtual AAA { unsigned long bbb; }; +struct CCC: virtual AAA { }; +struct DDD: CCC, BBB { DDD (); }; +DDD::DDD() { } +int main() { + DDD d; + printf("OK\n"); +} +// CHECK: OK diff --git a/lib/tsan/output_tests/vptr_benign_race.cc b/lib/tsan/output_tests/vptr_benign_race.cc new file mode 100644 index 0000000..fec4ffb --- /dev/null +++ b/lib/tsan/output_tests/vptr_benign_race.cc @@ -0,0 +1,50 @@ +#include +#include +#include + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { + sem_wait(&sem_); + sem_destroy(&sem_); + } +}; + +static A *obj = new B; + +void *Thread1(void *x) { + obj->F(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); + fprintf(stderr, "PASS\n"); +} +// CHECK: PASS +// CHECK-NOT: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/output_tests/vptr_harmful_race.cc b/lib/tsan/output_tests/vptr_harmful_race.cc new file mode 100644 index 0000000..a19e6ab --- /dev/null +++ b/lib/tsan/output_tests/vptr_harmful_race.cc @@ -0,0 +1,48 @@ +#include +#include +#include + +struct A { + A() { + sem_init(&sem_, 0, 0); + } + virtual void F() { + } + void Done() { + sem_post(&sem_); + } + virtual ~A() { + sem_wait(&sem_); + sem_destroy(&sem_); + } + sem_t sem_; +}; + +struct B : A { + virtual void F() { + } + virtual ~B() { } +}; + +static A *obj = new B; + +void *Thread1(void *x) { + obj->F(); + obj->Done(); + return NULL; +} + +void *Thread2(void *x) { + delete obj; + return NULL; +} + +int main() { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, NULL); + pthread_create(&t[1], NULL, Thread2, NULL); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} + +// CHECK: WARNING: ThreadSanitizer: data race diff --git a/lib/tsan/rtl/Makefile.mk b/lib/tsan/rtl/Makefile.mk new file mode 100644 index 0000000..d5d6327 --- /dev/null +++ b/lib/tsan/rtl/Makefile.mk @@ -0,0 +1,23 @@ +#===- lib/tsan/rtl/Makefile.mk -----------------------------*- Makefile -*--===# +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +#===------------------------------------------------------------------------===# + +ModuleName := tsan +SubDirs := + +Sources := $(foreach file,$(wildcard $(Dir)/*.cc),$(notdir $(file))) +AsmSources := $(foreach file,$(wildcard $(Dir)/*.S),$(notdir $(file))) +ObjNames := $(Sources:%.cc=%.o) $(AsmSources:%.S=%.o) + +Implementation := Generic + +# FIXME: use automatic dependencies? +Dependencies := $(wildcard $(Dir)/*.h) + +# Define a convenience variable for all the tsan functions. +TsanFunctions += $(Sources:%.cc=%) $(AsmSources:%.S=%) diff --git a/lib/tsan/rtl/Makefile.old b/lib/tsan/rtl/Makefile.old new file mode 100644 index 0000000..9b79f57 --- /dev/null +++ b/lib/tsan/rtl/Makefile.old @@ -0,0 +1,59 @@ +CXXFLAGS = -fPIE -g -Wall -Werror -fno-builtin -DTSAN_DEBUG=$(DEBUG) +ifeq ($(DEBUG), 0) + CXXFLAGS += -O3 +endif + +# For interception. FIXME: move interception one level higher. +INTERCEPTION=../../interception +COMMON=../../sanitizer_common +INCLUDES= -I../.. +EXTRA_CXXFLAGS=-fno-exceptions +NO_SYSROOT=--sysroot=. +CXXFLAGS+=$(EXTRA_CXXFLAGS) +CXXFLAGS+=$(CFLAGS) +ifeq ($(DEBUG), 0) + CXXFLAGS+=-fomit-frame-pointer +ifeq ($(CXX), g++) + CXXFLAGS+=-Wframe-larger-than=512 +endif # CXX=g++ +endif # DEBUG=0 + +ifeq ($(CXX), clang++) + # Global constructors are banned. + CXXFLAGS+=-Wglobal-constructors +endif + + + +all: libtsan.a + +LIBTSAN_HEADERS=$(wildcard *.h) \ + $(wildcard $(INTERCEPTION)/*.h) \ + $(wildcard $(COMMON)/*.h) +LIBTSAN_SRC=$(wildcard *.cc) +LIBTSAN_ASM_SRC=$(wildcard *.S) +INTERCEPTION_SRC=$(wildcard $(INTERCEPTION)/*.cc) +COMMON_SRC=$(wildcard $(COMMON)/*.cc) + +LIBTSAN_OBJ=$(patsubst %.cc,%.o,$(LIBTSAN_SRC)) \ + $(patsubst %.S,%.o,$(LIBTSAN_ASM_SRC)) \ + $(patsubst $(INTERCEPTION)/%.cc,%.o,$(INTERCEPTION_SRC)) \ + $(patsubst $(COMMON)/%.cc,%.o,$(COMMON_SRC)) + +%_linux.o: %_linux.cc Makefile.old $(LIBTSAN_HEADERS) + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< +%.o: %.cc Makefile.old $(LIBTSAN_HEADERS) + $(CXX) $(CXXFLAGS) $(INCLUDES) $(NO_SYSROOT) -c $< +%.o: $(INTERCEPTION)/%.cc Makefile.old $(LIBTSAN_HEADERS) + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ +%.o: $(COMMON)/%.cc Makefile.old $(LIBTSAN_HEADERS) + $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +libtsan.a: $(LIBTSAN_OBJ) + ar ru $@ $(LIBTSAN_OBJ) + +libtsan_dummy.a: tsan_dummy_rtl.o + ar ru $@ $< + +clean: + rm -f *.o *.a diff --git a/lib/tsan/rtl/tsan_clock.cc b/lib/tsan/rtl/tsan_clock.cc new file mode 100644 index 0000000..32ed91d --- /dev/null +++ b/lib/tsan/rtl/tsan_clock.cc @@ -0,0 +1,118 @@ +//===-- tsan_clock.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_clock.h" +#include "tsan_rtl.h" + +// It's possible to optimize clock operations for some important cases +// so that they are O(1). The cases include singletons, once's, local mutexes. +// First, SyncClock must be re-implemented to allow indexing by tid. +// It must not necessarily be a full vector clock, though. For example it may +// be a multi-level table. +// Then, each slot in SyncClock must contain a dirty bit (it's united with +// the clock value, so no space increase). The acquire algorithm looks +// as follows: +// void acquire(thr, tid, thr_clock, sync_clock) { +// if (!sync_clock[tid].dirty) +// return; // No new info to acquire. +// // This handles constant reads of singleton pointers and +// // stop-flags. +// acquire_impl(thr_clock, sync_clock); // As usual, O(N). +// sync_clock[tid].dirty = false; +// sync_clock.dirty_count--; +// } +// The release operation looks as follows: +// void release(thr, tid, thr_clock, sync_clock) { +// // thr->sync_cache is a simple fixed-size hash-based cache that holds +// // several previous sync_clock's. +// if (thr->sync_cache[sync_clock] >= thr->last_acquire_epoch) { +// // The thread did no acquire operations since last release on this clock. +// // So update only the thread's slot (other slots can't possibly change). +// sync_clock[tid].clock = thr->epoch; +// if (sync_clock.dirty_count == sync_clock.cnt +// || (sync_clock.dirty_count == sync_clock.cnt - 1 +// && sync_clock[tid].dirty == false)) +// // All dirty flags are set, bail out. +// return; +// set all dirty bits, but preserve the thread's bit. // O(N) +// update sync_clock.dirty_count; +// return; +// } +// release_impl(thr_clock, sync_clock); // As usual, O(N). +// set all dirty bits, but preserve the thread's bit. +// // The previous step is combined with release_impl(), so that +// // we scan the arrays only once. +// update sync_clock.dirty_count; +// } + +namespace __tsan { + +ThreadClock::ThreadClock() { + nclk_ = 0; + for (uptr i = 0; i < (uptr)kMaxTidInClock; i++) + clk_[i] = 0; +} + +void ThreadClock::acquire(const SyncClock *src) { + DCHECK(nclk_ <= kMaxTid); + DCHECK(src->clk_.Size() <= kMaxTid); + + const uptr nclk = src->clk_.Size(); + if (nclk == 0) + return; + nclk_ = max(nclk_, nclk); + for (uptr i = 0; i < nclk; i++) { + if (clk_[i] < src->clk_[i]) + clk_[i] = src->clk_[i]; + } +} + +void ThreadClock::release(SyncClock *dst) const { + DCHECK(nclk_ <= kMaxTid); + DCHECK(dst->clk_.Size() <= kMaxTid); + + if (dst->clk_.Size() < nclk_) + dst->clk_.Resize(nclk_); + for (uptr i = 0; i < nclk_; i++) { + if (dst->clk_[i] < clk_[i]) + dst->clk_[i] = clk_[i]; + } +} + +void ThreadClock::ReleaseStore(SyncClock *dst) const { + DCHECK(nclk_ <= kMaxTid); + DCHECK(dst->clk_.Size() <= kMaxTid); + + if (dst->clk_.Size() < nclk_) + dst->clk_.Resize(nclk_); + for (uptr i = 0; i < nclk_; i++) + dst->clk_[i] = clk_[i]; + for (uptr i = nclk_; i < dst->clk_.Size(); i++) + dst->clk_[i] = 0; +} + +void ThreadClock::acq_rel(SyncClock *dst) { + acquire(dst); + release(dst); +} + +void ThreadClock::Disable(unsigned tid) { + u64 c0 = clk_[tid]; + for (uptr i = 0; i < kMaxTidInClock; i++) + clk_[i] = (u64)-1; + clk_[tid] = c0; +} + +SyncClock::SyncClock() + : clk_(MBlockClock) { +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_clock.h b/lib/tsan/rtl/tsan_clock.h new file mode 100644 index 0000000..02ddb9a --- /dev/null +++ b/lib/tsan/rtl/tsan_clock.h @@ -0,0 +1,82 @@ +//===-- tsan_clock.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_CLOCK_H +#define TSAN_CLOCK_H + +#include "tsan_defs.h" +#include "tsan_vector.h" + +namespace __tsan { + +// The clock that lives in sync variables (mutexes, atomics, etc). +class SyncClock { + public: + SyncClock(); + + uptr size() const { + return clk_.Size(); + } + + void Reset() { + clk_.Reset(); + } + + private: + Vector clk_; + friend struct ThreadClock; +}; + +// The clock that lives in threads. +struct ThreadClock { + public: + ThreadClock(); + + u64 get(unsigned tid) const { + DCHECK_LT(tid, kMaxTidInClock); + return clk_[tid]; + } + + void set(unsigned tid, u64 v) { + DCHECK_LT(tid, kMaxTid); + DCHECK_GE(v, clk_[tid]); + clk_[tid] = v; + if (nclk_ <= tid) + nclk_ = tid + 1; + } + + void tick(unsigned tid) { + DCHECK_LT(tid, kMaxTid); + clk_[tid]++; + if (nclk_ <= tid) + nclk_ = tid + 1; + } + + void Disable(unsigned tid); + + uptr size() const { + return nclk_; + } + + void acquire(const SyncClock *src); + void release(SyncClock *dst) const; + void acq_rel(SyncClock *dst); + void ReleaseStore(SyncClock *dst) const; + + private: + uptr nclk_; + u64 clk_[kMaxTidInClock]; +}; + +} // namespace __tsan + +#endif // TSAN_CLOCK_H diff --git a/lib/tsan/rtl/tsan_defs.h b/lib/tsan/rtl/tsan_defs.h new file mode 100644 index 0000000..ca8f0ae --- /dev/null +++ b/lib/tsan/rtl/tsan_defs.h @@ -0,0 +1,139 @@ +//===-- tsan_defs.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_DEFS_H +#define TSAN_DEFS_H + +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_stat.h" + +#ifndef TSAN_DEBUG +#define TSAN_DEBUG 0 +#endif // TSAN_DEBUG + +namespace __tsan { + +const int kTidBits = 13; +const unsigned kMaxTid = 1 << kTidBits; +const unsigned kMaxTidInClock = kMaxTid * 2; // This includes msb 'freed' bit. +const int kClkBits = 43; +#ifndef TSAN_GO +const int kShadowStackSize = 1024; +#endif + +#ifdef TSAN_SHADOW_COUNT +# if TSAN_SHADOW_COUNT == 2 \ + || TSAN_SHADOW_COUNT == 4 || TSAN_SHADOW_COUNT == 8 +const unsigned kShadowCnt = TSAN_SHADOW_COUNT; +# else +# error "TSAN_SHADOW_COUNT must be one of 2,4,8" +# endif +#else +// Count of shadow values in a shadow cell. +const unsigned kShadowCnt = 8; +#endif + +// That many user bytes are mapped onto a single shadow cell. +const unsigned kShadowCell = 8; + +// Size of a single shadow value (u64). +const unsigned kShadowSize = 8; + +#if defined(TSAN_COLLECT_STATS) && TSAN_COLLECT_STATS +const bool kCollectStats = true; +#else +const bool kCollectStats = false; +#endif + +// The following "build consistency" machinery ensures that all source files +// are built in the same configuration. Inconsistent builds lead to +// hard to debug crashes. +#if TSAN_DEBUG +void build_consistency_debug(); +#else +void build_consistency_release(); +#endif + +#if TSAN_COLLECT_STATS +void build_consistency_stats(); +#else +void build_consistency_nostats(); +#endif + +#if TSAN_SHADOW_COUNT == 1 +void build_consistency_shadow1(); +#elif TSAN_SHADOW_COUNT == 2 +void build_consistency_shadow2(); +#elif TSAN_SHADOW_COUNT == 4 +void build_consistency_shadow4(); +#else +void build_consistency_shadow8(); +#endif + +static inline void USED build_consistency() { +#if TSAN_DEBUG + build_consistency_debug(); +#else + build_consistency_release(); +#endif +#if TSAN_COLLECT_STATS + build_consistency_stats(); +#else + build_consistency_nostats(); +#endif +#if TSAN_SHADOW_COUNT == 1 + build_consistency_shadow1(); +#elif TSAN_SHADOW_COUNT == 2 + build_consistency_shadow2(); +#elif TSAN_SHADOW_COUNT == 4 + build_consistency_shadow4(); +#else + build_consistency_shadow8(); +#endif +} + +template +T min(T a, T b) { + return a < b ? a : b; +} + +template +T max(T a, T b) { + return a > b ? a : b; +} + +template +T RoundUp(T p, int align) { + DCHECK_EQ(align & (align - 1), 0); + return (T)(((u64)p + align - 1) & ~(align - 1)); +} + +struct MD5Hash { + u64 hash[2]; + bool operator==(const MD5Hash &other) const; +}; + +MD5Hash md5_hash(const void *data, uptr size); + +struct ThreadState; +struct ThreadContext; +struct Context; +struct ReportStack; +class ReportDesc; +class RegionAlloc; +class StackTrace; + +} // namespace __tsan + +#endif // TSAN_DEFS_H diff --git a/lib/tsan/rtl/tsan_flags.cc b/lib/tsan/rtl/tsan_flags.cc new file mode 100644 index 0000000..8f91939 --- /dev/null +++ b/lib/tsan/rtl/tsan_flags.cc @@ -0,0 +1,79 @@ +//===-- tsan_flags.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_flags.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_flags.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" + +namespace __tsan { + +Flags *flags() { + return &CTX()->flags; +} + +// Can be overriden in frontend. +#ifdef TSAN_EXTERNAL_HOOKS +void OverrideFlags(Flags *f); +#else +void WEAK OverrideFlags(Flags *f) { + (void)f; +} +#endif + +void InitializeFlags(Flags *f, const char *env) { + internal_memset(f, 0, sizeof(*f)); + + // Default values. + f->enable_annotations = true; + f->suppress_equal_stacks = true; + f->suppress_equal_addresses = true; + f->report_thread_leaks = true; + f->report_signal_unsafe = true; + f->force_seq_cst_atomics = false; + f->strip_path_prefix = ""; + f->suppressions = ""; + f->exitcode = 66; + f->log_fileno = 2; + f->atexit_sleep_ms = 1000; + f->verbosity = 0; + f->profile_memory = ""; + f->flush_memory_ms = 0; + f->stop_on_start = false; + f->running_on_valgrind = false; + f->use_internal_symbolizer = false; + + // Let a frontend override. + OverrideFlags(f); + + // Override from command line. + ParseFlag(env, &f->enable_annotations, "enable_annotations"); + ParseFlag(env, &f->suppress_equal_stacks, "suppress_equal_stacks"); + ParseFlag(env, &f->suppress_equal_addresses, "suppress_equal_addresses"); + ParseFlag(env, &f->report_thread_leaks, "report_thread_leaks"); + ParseFlag(env, &f->report_signal_unsafe, "report_signal_unsafe"); + ParseFlag(env, &f->force_seq_cst_atomics, "force_seq_cst_atomics"); + ParseFlag(env, &f->strip_path_prefix, "strip_path_prefix"); + ParseFlag(env, &f->suppressions, "suppressions"); + ParseFlag(env, &f->exitcode, "exitcode"); + ParseFlag(env, &f->log_fileno, "log_fileno"); + ParseFlag(env, &f->atexit_sleep_ms, "atexit_sleep_ms"); + ParseFlag(env, &f->verbosity, "verbosity"); + ParseFlag(env, &f->profile_memory, "profile_memory"); + ParseFlag(env, &f->flush_memory_ms, "flush_memory_ms"); + ParseFlag(env, &f->stop_on_start, "stop_on_start"); + ParseFlag(env, &f->use_internal_symbolizer, "use_internal_symbolizer"); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_flags.h b/lib/tsan/rtl/tsan_flags.h new file mode 100644 index 0000000..c22132f --- /dev/null +++ b/lib/tsan/rtl/tsan_flags.h @@ -0,0 +1,71 @@ +//===-- tsan_flags.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// NOTE: This file may be included into user code. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_FLAGS_H +#define TSAN_FLAGS_H + +// ----------- ATTENTION ------------- +// ThreadSanitizer user may provide its implementation of weak +// symbol __tsan::OverrideFlags(__tsan::Flags). Therefore, this +// header may be included in the user code, and shouldn't include +// other headers from TSan or common sanitizer runtime. + +namespace __tsan { + +struct Flags { + // Enable dynamic annotations, otherwise they are no-ops. + bool enable_annotations; + // Supress a race report if we've already output another race report + // with the same stack. + bool suppress_equal_stacks; + // Supress a race report if we've already output another race report + // on the same address. + bool suppress_equal_addresses; + // Report thread leaks at exit? + bool report_thread_leaks; + // Report violations of async signal-safety + // (e.g. malloc() call from a signal handler). + bool report_signal_unsafe; + // If set, all atomics are effectively sequentially consistent (seq_cst), + // regardless of what user actually specified. + bool force_seq_cst_atomics; + // Strip that prefix from file paths in reports. + const char *strip_path_prefix; + // Suppressions filename. + const char *suppressions; + // Override exit status if something was reported. + int exitcode; + // Log fileno (1 - stdout, 2 - stderr). + int log_fileno; + // Sleep in main thread before exiting for that many ms + // (useful to catch "at exit" races). + int atexit_sleep_ms; + // Verbosity level (0 - silent, 1 - a bit of output, 2+ - more output). + int verbosity; + // If set, periodically write memory profile to that file. + const char *profile_memory; + // Flush shadow memory every X ms. + int flush_memory_ms; + // Stops on start until __tsan_resume() is called (for debugging). + bool stop_on_start; + // Controls whether RunningOnValgrind() returns true or false. + bool running_on_valgrind; + // If set, uses in-process symbolizer from common sanitizer runtime. + bool use_internal_symbolizer; +}; + +Flags *flags(); +void InitializeFlags(Flags *flags, const char *env); +} + +#endif // TSAN_FLAGS_H diff --git a/lib/tsan/rtl/tsan_interceptors.cc b/lib/tsan/rtl/tsan_interceptors.cc new file mode 100644 index 0000000..a962250 --- /dev/null +++ b/lib/tsan/rtl/tsan_interceptors.cc @@ -0,0 +1,1596 @@ +//===-- tsan_interceptors_linux.cc ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_rtl.h" +#include "tsan_interface.h" +#include "tsan_platform.h" +#include "tsan_mman.h" + +using namespace __tsan; // NOLINT + +const int kSigCount = 128; + +struct my_siginfo_t { + int opaque[128]; +}; + +struct sigset_t { + u64 val[1024 / 8 / sizeof(u64)]; +}; + +struct ucontext_t { + uptr opaque[117]; +}; + +extern "C" int pthread_attr_init(void *attr); +extern "C" int pthread_attr_destroy(void *attr); +extern "C" int pthread_attr_getdetachstate(void *attr, int *v); +extern "C" int pthread_attr_setstacksize(void *attr, uptr stacksize); +extern "C" int pthread_attr_getstacksize(void *attr, uptr *stacksize); +extern "C" int pthread_key_create(unsigned *key, void (*destructor)(void* v)); +extern "C" int pthread_setspecific(unsigned key, const void *v); +extern "C" int pthread_mutexattr_gettype(void *a, int *type); +extern "C" int pthread_yield(); +extern "C" int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset); +extern "C" int sigfillset(sigset_t *set); +extern "C" void *pthread_self(); +extern "C" void _exit(int status); +extern "C" int __cxa_atexit(void (*func)(void *arg), void *arg, void *dso); +extern "C" int *__errno_location(); +extern "C" int usleep(unsigned usec); +const int PTHREAD_MUTEX_RECURSIVE = 1; +const int PTHREAD_MUTEX_RECURSIVE_NP = 1; +const int kPthreadAttrSize = 56; +const int EINVAL = 22; +const int EBUSY = 16; +const int EPOLL_CTL_ADD = 1; +const int SIGILL = 4; +const int SIGABRT = 6; +const int SIGFPE = 8; +const int SIGSEGV = 11; +const int SIGPIPE = 13; +const int SIGBUS = 7; +void *const MAP_FAILED = (void*)-1; +const int PTHREAD_BARRIER_SERIAL_THREAD = -1; +const int MAP_FIXED = 0x10; +typedef long long_t; // NOLINT + +typedef void (*sighandler_t)(int sig); + +#define errno (*__errno_location()) + +union pthread_attr_t { + char size[kPthreadAttrSize]; + void *align; +}; + +struct sigaction_t { + union { + sighandler_t sa_handler; + void (*sa_sigaction)(int sig, my_siginfo_t *siginfo, void *uctx); + }; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(); +}; + +const sighandler_t SIG_DFL = (sighandler_t)0; +const sighandler_t SIG_IGN = (sighandler_t)1; +const sighandler_t SIG_ERR = (sighandler_t)-1; +const int SA_SIGINFO = 4; +const int SIG_SETMASK = 2; + +static sigaction_t sigactions[kSigCount]; + +namespace __tsan { +struct SignalDesc { + bool armed; + bool sigaction; + my_siginfo_t siginfo; + ucontext_t ctx; +}; + +struct SignalContext { + int int_signal_send; + int pending_signal_count; + SignalDesc pending_signals[kSigCount]; +}; +} + +static SignalContext *SigCtx(ThreadState *thr) { + SignalContext *ctx = (SignalContext*)thr->signal_ctx; + if (ctx == 0 && thr->is_alive) { + ScopedInRtl in_rtl; + ctx = (SignalContext*)internal_alloc( + MBlockSignal, sizeof(*ctx)); + MemoryResetRange(thr, 0, (uptr)ctx, sizeof(*ctx)); + internal_memset(ctx, 0, sizeof(*ctx)); + thr->signal_ctx = ctx; + } + return ctx; +} + +static unsigned g_thread_finalize_key; + +static void process_pending_signals(ThreadState *thr); + +class ScopedInterceptor { + public: + ScopedInterceptor(ThreadState *thr, const char *fname, uptr pc) + : thr_(thr) + , in_rtl_(thr->in_rtl) { + if (thr_->in_rtl == 0) { + Initialize(thr); + FuncEntry(thr, pc); + thr_->in_rtl++; + DPrintf("#%d: intercept %s()\n", thr_->tid, fname); + } else { + thr_->in_rtl++; + } + } + + ~ScopedInterceptor() { + thr_->in_rtl--; + if (thr_->in_rtl == 0) { + FuncExit(thr_); + process_pending_signals(thr_); + } + CHECK_EQ(in_rtl_, thr_->in_rtl); + } + + private: + ThreadState *const thr_; + const int in_rtl_; +}; + +#define SCOPED_INTERCEPTOR_RAW(func, ...) \ + ThreadState *thr = cur_thread(); \ + StatInc(thr, StatInterceptor); \ + StatInc(thr, StatInt_##func); \ + ScopedInterceptor si(thr, #func, \ + (__sanitizer::uptr)__builtin_return_address(0)); \ + const uptr pc = (uptr)&func; \ + (void)pc; \ +/**/ + +#define SCOPED_TSAN_INTERCEPTOR(func, ...) \ + SCOPED_INTERCEPTOR_RAW(func, __VA_ARGS__); \ + if (thr->in_rtl > 1) \ + return REAL(func)(__VA_ARGS__); \ +/**/ + +#define SCOPED_INTERCEPTOR_LIBC(func, ...) \ + ThreadState *thr = cur_thread(); \ + StatInc(thr, StatInterceptor); \ + StatInc(thr, StatInt_##func); \ + ScopedInterceptor si(thr, #func, callpc); \ + const uptr pc = (uptr)&func; \ + (void)pc; \ + if (thr->in_rtl > 1) \ + return REAL(func)(__VA_ARGS__); \ +/**/ + +#define TSAN_INTERCEPTOR(ret, func, ...) INTERCEPTOR(ret, func, __VA_ARGS__) +#define TSAN_INTERCEPT(func) INTERCEPT_FUNCTION(func) + +// May be overriden by front-end. +extern "C" void WEAK __tsan_malloc_hook(void *ptr, uptr size) { + (void)ptr; + (void)size; +} + +extern "C" void WEAK __tsan_free_hook(void *ptr) { + (void)ptr; +} + +static void invoke_malloc_hook(void *ptr, uptr size) { + Context *ctx = CTX(); + ThreadState *thr = cur_thread(); + if (ctx == 0 || !ctx->initialized || thr->in_rtl) + return; + __tsan_malloc_hook(ptr, size); +} + +static void invoke_free_hook(void *ptr) { + Context *ctx = CTX(); + ThreadState *thr = cur_thread(); + if (ctx == 0 || !ctx->initialized || thr->in_rtl) + return; + __tsan_free_hook(ptr); +} + +class AtExitContext { + public: + AtExitContext() + : mtx_(MutexTypeAtExit, StatMtxAtExit) + , pos_() { + } + + typedef void(*atexit_t)(); + + int atexit(ThreadState *thr, uptr pc, atexit_t f) { + Lock l(&mtx_); + if (pos_ == kMaxAtExit) + return 1; + Release(thr, pc, (uptr)this); + stack_[pos_] = f; + pos_++; + return 0; + } + + void exit(ThreadState *thr, uptr pc) { + CHECK_EQ(thr->in_rtl, 0); + for (;;) { + atexit_t f = 0; + { + Lock l(&mtx_); + if (pos_) { + pos_--; + f = stack_[pos_]; + ScopedInRtl in_rtl; + Acquire(thr, pc, (uptr)this); + } + } + if (f == 0) + break; + DPrintf("#%d: executing atexit func %p\n", thr->tid, f); + CHECK_EQ(thr->in_rtl, 0); + f(); + } + } + + private: + static const int kMaxAtExit = 128; + Mutex mtx_; + atexit_t stack_[kMaxAtExit]; + int pos_; +}; + +static AtExitContext *atexit_ctx; + +static void finalize(void *arg) { + ThreadState * thr = cur_thread(); + uptr pc = 0; + atexit_ctx->exit(thr, pc); + { + ScopedInRtl in_rtl; + DestroyAndFree(atexit_ctx); + usleep(flags()->atexit_sleep_ms * 1000); + } + int status = Finalize(cur_thread()); + if (status) + _exit(status); +} + +TSAN_INTERCEPTOR(int, atexit, void (*f)()) { + SCOPED_TSAN_INTERCEPTOR(atexit, f); + return atexit_ctx->atexit(thr, pc, f); + return 0; +} + +TSAN_INTERCEPTOR(void, longjmp, void *env, int val) { + SCOPED_TSAN_INTERCEPTOR(longjmp, env, val); + TsanPrintf("ThreadSanitizer: longjmp() is not supported\n"); + Die(); +} + +TSAN_INTERCEPTOR(void, siglongjmp, void *env, int val) { + SCOPED_TSAN_INTERCEPTOR(siglongjmp, env, val); + TsanPrintf("ThreadSanitizer: siglongjmp() is not supported\n"); + Die(); +} + +static uptr fd2addr(int fd) { + (void)fd; + static u64 addr; + return (uptr)&addr; +} + +static uptr epollfd2addr(int fd) { + (void)fd; + static u64 addr; + return (uptr)&addr; +} + +static uptr file2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +static uptr dir2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +TSAN_INTERCEPTOR(void*, malloc, uptr size) { + void *p = 0; + { + SCOPED_INTERCEPTOR_RAW(malloc, size); + p = user_alloc(thr, pc, size); + } + invoke_malloc_hook(p, size); + return p; +} + +TSAN_INTERCEPTOR(void*, calloc, uptr size, uptr n) { + void *p = 0; + { + SCOPED_INTERCEPTOR_RAW(calloc, size, n); + p = user_alloc(thr, pc, n * size); + internal_memset(p, 0, n * size); + } + invoke_malloc_hook(p, n * size); + return p; +} + +TSAN_INTERCEPTOR(void*, realloc, void *p, uptr size) { + if (p) + invoke_free_hook(p); + { + SCOPED_INTERCEPTOR_RAW(realloc, p, size); + p = user_realloc(thr, pc, p, size); + } + invoke_malloc_hook(p, size); + return p; +} + +TSAN_INTERCEPTOR(void, free, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_INTERCEPTOR_RAW(free, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(void, cfree, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_INTERCEPTOR_RAW(cfree, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(uptr, strlen, const char *s) { + SCOPED_TSAN_INTERCEPTOR(strlen, s); + uptr len = internal_strlen(s); + MemoryAccessRange(thr, pc, (uptr)s, len + 1, false); + return len; +} + +TSAN_INTERCEPTOR(void*, memset, void *dst, int v, uptr size) { + SCOPED_TSAN_INTERCEPTOR(memset, dst, v, size); + MemoryAccessRange(thr, pc, (uptr)dst, size, true); + return internal_memset(dst, v, size); +} + +TSAN_INTERCEPTOR(void*, memcpy, void *dst, const void *src, uptr size) { + SCOPED_TSAN_INTERCEPTOR(memcpy, dst, src, size); + MemoryAccessRange(thr, pc, (uptr)dst, size, true); + MemoryAccessRange(thr, pc, (uptr)src, size, false); + return internal_memcpy(dst, src, size); +} + +TSAN_INTERCEPTOR(int, memcmp, const void *s1, const void *s2, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memcmp, s1, s2, n); + int res = 0; + uptr len = 0; + for (; len < n; len++) { + if ((res = ((unsigned char*)s1)[len] - ((unsigned char*)s2)[len])) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len < n ? len + 1 : n, false); + MemoryAccessRange(thr, pc, (uptr)s2, len < n ? len + 1 : n, false); + return res; +} + +TSAN_INTERCEPTOR(int, strcmp, const char *s1, const char *s2) { + SCOPED_TSAN_INTERCEPTOR(strcmp, s1, s2); + uptr len = 0; + for (; s1[len] && s2[len]; len++) { + if (s1[len] != s2[len]) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len + 1, false); + MemoryAccessRange(thr, pc, (uptr)s2, len + 1, false); + return s1[len] - s2[len]; +} + +TSAN_INTERCEPTOR(int, strncmp, const char *s1, const char *s2, uptr n) { + SCOPED_TSAN_INTERCEPTOR(strncmp, s1, s2, n); + uptr len = 0; + for (; len < n && s1[len] && s2[len]; len++) { + if (s1[len] != s2[len]) + break; + } + MemoryAccessRange(thr, pc, (uptr)s1, len < n ? len + 1 : n, false); + MemoryAccessRange(thr, pc, (uptr)s2, len < n ? len + 1 : n, false); + return len == n ? 0 : s1[len] - s2[len]; +} + +TSAN_INTERCEPTOR(void*, memchr, void *s, int c, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memchr, s, c, n); + void *res = REAL(memchr)(s, c, n); + uptr len = res ? (char*)res - (char*)s + 1 : n; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(void*, memrchr, char *s, int c, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memrchr, s, c, n); + MemoryAccessRange(thr, pc, (uptr)s, n, false); + return REAL(memrchr)(s, c, n); +} + +TSAN_INTERCEPTOR(void*, memmove, void *dst, void *src, uptr n) { + SCOPED_TSAN_INTERCEPTOR(memmove, dst, src, n); + MemoryAccessRange(thr, pc, (uptr)dst, n, true); + MemoryAccessRange(thr, pc, (uptr)src, n, false); + return REAL(memmove)(dst, src, n); +} + +TSAN_INTERCEPTOR(char*, strchr, char *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strchr, s, c); + char *res = REAL(strchr)(s, c); + uptr len = res ? (char*)res - (char*)s + 1 : internal_strlen(s) + 1; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(char*, strchrnul, char *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strchrnul, s, c); + char *res = REAL(strchrnul)(s, c); + uptr len = (char*)res - (char*)s + 1; + MemoryAccessRange(thr, pc, (uptr)s, len, false); + return res; +} + +TSAN_INTERCEPTOR(char*, strrchr, char *s, int c) { + SCOPED_TSAN_INTERCEPTOR(strrchr, s, c); + MemoryAccessRange(thr, pc, (uptr)s, internal_strlen(s) + 1, false); + return REAL(strrchr)(s, c); +} + +TSAN_INTERCEPTOR(char*, strcpy, char *dst, const char *src) { // NOLINT + SCOPED_TSAN_INTERCEPTOR(strcpy, dst, src); // NOLINT + uptr srclen = internal_strlen(src); + MemoryAccessRange(thr, pc, (uptr)dst, srclen + 1, true); + MemoryAccessRange(thr, pc, (uptr)src, srclen + 1, false); + return REAL(strcpy)(dst, src); // NOLINT +} + +TSAN_INTERCEPTOR(char*, strncpy, char *dst, char *src, uptr n) { + SCOPED_TSAN_INTERCEPTOR(strncpy, dst, src, n); + uptr srclen = internal_strnlen(src, n); + MemoryAccessRange(thr, pc, (uptr)dst, n, true); + MemoryAccessRange(thr, pc, (uptr)src, min(srclen + 1, n), false); + return REAL(strncpy)(dst, src, n); +} + +TSAN_INTERCEPTOR(const char*, strstr, const char *s1, const char *s2) { + SCOPED_TSAN_INTERCEPTOR(strstr, s1, s2); + const char *res = REAL(strstr)(s1, s2); + uptr len1 = internal_strlen(s1); + uptr len2 = internal_strlen(s2); + MemoryAccessRange(thr, pc, (uptr)s1, len1 + 1, false); + MemoryAccessRange(thr, pc, (uptr)s2, len2 + 1, false); + return res; +} + +static bool fix_mmap_addr(void **addr, long_t sz, int flags) { + if (*addr) { + if (!IsAppMem((uptr)*addr) || !IsAppMem((uptr)*addr + sz - 1)) { + if (flags & MAP_FIXED) { + errno = EINVAL; + return false; + } else { + *addr = 0; + } + } + } + return true; +} + +TSAN_INTERCEPTOR(void*, mmap, void *addr, long_t sz, int prot, + int flags, int fd, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(mmap, addr, sz, prot, flags, fd, off); + if (!fix_mmap_addr(&addr, sz, flags)) + return MAP_FAILED; + void *res = REAL(mmap)(addr, sz, prot, flags, fd, off); + if (res != MAP_FAILED) { + MemoryResetRange(thr, pc, (uptr)res, sz); + } + return res; +} + +TSAN_INTERCEPTOR(void*, mmap64, void *addr, long_t sz, int prot, + int flags, int fd, u64 off) { + SCOPED_TSAN_INTERCEPTOR(mmap64, addr, sz, prot, flags, fd, off); + if (!fix_mmap_addr(&addr, sz, flags)) + return MAP_FAILED; + void *res = REAL(mmap64)(addr, sz, prot, flags, fd, off); + if (res != MAP_FAILED) { + MemoryResetRange(thr, pc, (uptr)res, sz); + } + return res; +} + +TSAN_INTERCEPTOR(int, munmap, void *addr, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(munmap, addr, sz); + int res = REAL(munmap)(addr, sz); + return res; +} + +#ifdef __LP64__ + +// void *operator new(size_t) +TSAN_INTERCEPTOR(void*, _Znwm, uptr sz) { + void *p = 0; + { + SCOPED_TSAN_INTERCEPTOR(_Znwm, sz); + p = user_alloc(thr, pc, sz); + } + invoke_malloc_hook(p, sz); + return p; +} + +// void *operator new(size_t, nothrow_t) +TSAN_INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, uptr sz) { + void *p = 0; + { + SCOPED_TSAN_INTERCEPTOR(_ZnwmRKSt9nothrow_t, sz); + p = user_alloc(thr, pc, sz); + } + invoke_malloc_hook(p, sz); + return p; +} + +// void *operator new[](size_t) +TSAN_INTERCEPTOR(void*, _Znam, uptr sz) { + void *p = 0; + { + SCOPED_TSAN_INTERCEPTOR(_Znam, sz); + p = user_alloc(thr, pc, sz); + } + invoke_malloc_hook(p, sz); + return p; +} + +// void *operator new[](size_t, nothrow_t) +TSAN_INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, uptr sz) { + void *p = 0; + { + SCOPED_TSAN_INTERCEPTOR(_ZnamRKSt9nothrow_t, sz); + p = user_alloc(thr, pc, sz); + } + invoke_malloc_hook(p, sz); + return p; +} + +#else +#error "Not implemented" +#endif + +// void operator delete(void*) +TSAN_INTERCEPTOR(void, _ZdlPv, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_TSAN_INTERCEPTOR(_ZdlPv, p); + user_free(thr, pc, p); +} + +// void operator delete(void*, nothrow_t) +TSAN_INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_TSAN_INTERCEPTOR(_ZdlPvRKSt9nothrow_t, p); + user_free(thr, pc, p); +} + +// void operator delete[](void*) +TSAN_INTERCEPTOR(void, _ZdaPv, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_TSAN_INTERCEPTOR(_ZdaPv, p); + user_free(thr, pc, p); +} + +// void operator delete[](void*, nothrow_t) +TSAN_INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void *p) { + if (p == 0) + return; + invoke_free_hook(p); + SCOPED_TSAN_INTERCEPTOR(_ZdaPvRKSt9nothrow_t, p); + user_free(thr, pc, p); +} + +TSAN_INTERCEPTOR(void*, memalign, uptr align, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(memalign, align, sz); + return user_alloc_aligned(thr, pc, sz, align); +} + +TSAN_INTERCEPTOR(void*, valloc, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(valloc, sz); + return user_alloc_aligned(thr, pc, sz, kPageSize); +} + +TSAN_INTERCEPTOR(void*, pvalloc, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(pvalloc, sz); + sz = RoundUp(sz, kPageSize); + return user_alloc_aligned(thr, pc, sz, kPageSize); +} + +TSAN_INTERCEPTOR(int, posix_memalign, void **memptr, uptr align, uptr sz) { + SCOPED_TSAN_INTERCEPTOR(posix_memalign, memptr, align, sz); + *memptr = user_alloc_aligned(thr, pc, sz, align); + return 0; +} + +// Used in thread-safe function static initialization. +TSAN_INTERCEPTOR(int, __cxa_guard_acquire, char *m) { + SCOPED_TSAN_INTERCEPTOR(__cxa_guard_acquire, m); + int res = REAL(__cxa_guard_acquire)(m); + if (res) { + // This thread does the init. + } else { + Acquire(thr, pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(void, __cxa_guard_release, char *m) { + SCOPED_TSAN_INTERCEPTOR(__cxa_guard_release, m); + Release(thr, pc, (uptr)m); + REAL(__cxa_guard_release)(m); +} + +static void thread_finalize(void *v) { + uptr iter = (uptr)v; + if (iter > 1) { + if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) { + TsanPrintf("ThreadSanitizer: failed to set thread key\n"); + Die(); + } + return; + } + { + ScopedInRtl in_rtl; + ThreadState *thr = cur_thread(); + ThreadFinish(thr); + SignalContext *sctx = thr->signal_ctx; + if (sctx) { + thr->signal_ctx = 0; + internal_free(sctx); + } + } +} + + +struct ThreadParam { + void* (*callback)(void *arg); + void *param; + atomic_uintptr_t tid; +}; + +extern "C" void *__tsan_thread_start_func(void *arg) { + ThreadParam *p = (ThreadParam*)arg; + void* (*callback)(void *arg) = p->callback; + void *param = p->param; + int tid = 0; + { + ThreadState *thr = cur_thread(); + ScopedInRtl in_rtl; + if (pthread_setspecific(g_thread_finalize_key, (void*)4)) { + TsanPrintf("ThreadSanitizer: failed to set thread key\n"); + Die(); + } + while ((tid = atomic_load(&p->tid, memory_order_acquire)) == 0) + pthread_yield(); + atomic_store(&p->tid, 0, memory_order_release); + ThreadStart(thr, tid); + CHECK_EQ(thr->in_rtl, 1); + } + void *res = callback(param); + // Prevent the callback from being tail called, + // it mixes up stack traces. + volatile int foo = 42; + foo++; + return res; +} + +TSAN_INTERCEPTOR(int, pthread_create, + void *th, void *attr, void *(*callback)(void*), void * param) { + SCOPED_TSAN_INTERCEPTOR(pthread_create, th, attr, callback, param); + pthread_attr_t myattr; + if (attr == 0) { + pthread_attr_init(&myattr); + attr = &myattr; + } + int detached = 0; + pthread_attr_getdetachstate(attr, &detached); + uptr stacksize = 0; + pthread_attr_getstacksize(attr, &stacksize); + // We place the huge ThreadState object into TLS, account for that. + const uptr minstacksize = GetTlsSize() + 128*1024; + if (stacksize < minstacksize) { + DPrintf("ThreadSanitizer: stacksize %zu->%zu\n", stacksize, minstacksize); + pthread_attr_setstacksize(attr, minstacksize); + } + ThreadParam p; + p.callback = callback; + p.param = param; + atomic_store(&p.tid, 0, memory_order_relaxed); + int res = REAL(pthread_create)(th, attr, __tsan_thread_start_func, &p); + if (res == 0) { + int tid = ThreadCreate(cur_thread(), pc, *(uptr*)th, detached); + CHECK_NE(tid, 0); + atomic_store(&p.tid, tid, memory_order_release); + while (atomic_load(&p.tid, memory_order_acquire) != 0) + pthread_yield(); + } + if (attr == &myattr) + pthread_attr_destroy(&myattr); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_join, void *th, void **ret) { + SCOPED_TSAN_INTERCEPTOR(pthread_join, th, ret); + int tid = ThreadTid(thr, pc, (uptr)th); + int res = REAL(pthread_join)(th, ret); + if (res == 0) { + ThreadJoin(cur_thread(), pc, tid); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_detach, void *th) { + SCOPED_TSAN_INTERCEPTOR(pthread_detach, th); + int tid = ThreadTid(thr, pc, (uptr)th); + int res = REAL(pthread_detach)(th); + if (res == 0) { + ThreadDetach(cur_thread(), pc, tid); + } + 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); + if (res == 0) { + bool recursive = false; + if (a) { + int type = 0; + if (pthread_mutexattr_gettype(a, &type) == 0) + recursive = (type == PTHREAD_MUTEX_RECURSIVE + || type == PTHREAD_MUTEX_RECURSIVE_NP); + } + MutexCreate(cur_thread(), pc, (uptr)m, false, recursive); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_destroy, m); + int res = REAL(pthread_mutex_destroy)(m); + if (res == 0 || res == EBUSY) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_lock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_lock, m); + int res = REAL(pthread_mutex_lock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_trylock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_trylock, m); + int res = REAL(pthread_mutex_trylock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_timedlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_timedlock, m, abstime); + int res = REAL(pthread_mutex_timedlock)(m, abstime); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_mutex_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_mutex_unlock, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_mutex_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_init, void *m, int pshared) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_init, m, pshared); + int res = REAL(pthread_spin_init)(m, pshared); + if (res == 0) { + MutexCreate(cur_thread(), pc, (uptr)m, false, false); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_destroy, m); + int res = REAL(pthread_spin_destroy)(m); + if (res == 0) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_lock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_lock, m); + int res = REAL(pthread_spin_lock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_trylock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_trylock, m); + int res = REAL(pthread_spin_trylock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_spin_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_spin_unlock, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_spin_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_init, void *m, void *a) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_init, m, a); + int res = REAL(pthread_rwlock_init)(m, a); + if (res == 0) { + MutexCreate(cur_thread(), pc, (uptr)m, true, false); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_destroy, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_destroy, m); + int res = REAL(pthread_rwlock_destroy)(m); + if (res == 0) { + MutexDestroy(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_rdlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_rdlock, m); + int res = REAL(pthread_rwlock_rdlock)(m); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_tryrdlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_tryrdlock, m); + int res = REAL(pthread_rwlock_tryrdlock)(m); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_timedrdlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedrdlock, m, abstime); + int res = REAL(pthread_rwlock_timedrdlock)(m, abstime); + if (res == 0) { + MutexReadLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_wrlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_wrlock, m); + int res = REAL(pthread_rwlock_wrlock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_trywrlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_trywrlock, m); + int res = REAL(pthread_rwlock_trywrlock)(m); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_timedwrlock, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_timedwrlock, m, abstime); + int res = REAL(pthread_rwlock_timedwrlock)(m, abstime); + if (res == 0) { + MutexLock(cur_thread(), pc, (uptr)m); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_rwlock_unlock, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_rwlock_unlock, m); + MutexReadOrWriteUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_rwlock_unlock)(m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_init, void *c, void *a) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, c, a); + int res = REAL(pthread_cond_init)(c, a); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_destroy, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, c); + int res = REAL(pthread_cond_destroy)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_signal, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_signal, c); + int res = REAL(pthread_cond_signal)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_broadcast, void *c) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_broadcast, c); + int res = REAL(pthread_cond_broadcast)(c); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_wait, void *c, void *m) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_wait, c, m); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_cond_wait)(c, m); + MutexLock(cur_thread(), pc, (uptr)m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_cond_timedwait, void *c, void *m, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(pthread_cond_timedwait, c, m, abstime); + MutexUnlock(cur_thread(), pc, (uptr)m); + int res = REAL(pthread_cond_timedwait)(c, m, abstime); + MutexLock(cur_thread(), pc, (uptr)m); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_init, void *b, void *a, unsigned count) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_init, b, a, count); + MemoryWrite1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_init)(b, a, count); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_destroy, void *b) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_destroy, b); + MemoryWrite1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_destroy)(b); + return res; +} + +TSAN_INTERCEPTOR(int, pthread_barrier_wait, void *b) { + SCOPED_TSAN_INTERCEPTOR(pthread_barrier_wait, b); + Release(cur_thread(), pc, (uptr)b); + MemoryRead1Byte(thr, pc, (uptr)b); + int res = REAL(pthread_barrier_wait)(b); + MemoryRead1Byte(thr, pc, (uptr)b); + if (res == 0 || res == PTHREAD_BARRIER_SERIAL_THREAD) { + Acquire(cur_thread(), pc, (uptr)b); + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_once, void *o, void (*f)()) { + SCOPED_TSAN_INTERCEPTOR(pthread_once, o, f); + if (o == 0 || f == 0) + return EINVAL; + atomic_uint32_t *a = static_cast(o); + u32 v = atomic_load(a, memory_order_acquire); + if (v == 0 && atomic_compare_exchange_strong(a, &v, 1, + memory_order_relaxed)) { + const int old_in_rtl = thr->in_rtl; + thr->in_rtl = 0; + (*f)(); + CHECK_EQ(thr->in_rtl, 0); + thr->in_rtl = old_in_rtl; + Release(cur_thread(), pc, (uptr)o); + atomic_store(a, 2, memory_order_release); + } else { + while (v != 2) { + pthread_yield(); + v = atomic_load(a, memory_order_acquire); + } + Acquire(cur_thread(), pc, (uptr)o); + } + return 0; +} + +TSAN_INTERCEPTOR(int, sem_init, void *s, int pshared, unsigned value) { + SCOPED_TSAN_INTERCEPTOR(sem_init, s, pshared, value); + int res = REAL(sem_init)(s, pshared, value); + return res; +} + +TSAN_INTERCEPTOR(int, sem_destroy, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_destroy, s); + int res = REAL(sem_destroy)(s); + return res; +} + +TSAN_INTERCEPTOR(int, sem_wait, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_wait, s); + int res = REAL(sem_wait)(s); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_trywait, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_trywait, s); + int res = REAL(sem_trywait)(s); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_timedwait, void *s, void *abstime) { + SCOPED_TSAN_INTERCEPTOR(sem_timedwait, s, abstime); + int res = REAL(sem_timedwait)(s, abstime); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(int, sem_post, void *s) { + SCOPED_TSAN_INTERCEPTOR(sem_post, s); + Release(cur_thread(), pc, (uptr)s); + int res = REAL(sem_post)(s); + return res; +} + +TSAN_INTERCEPTOR(int, sem_getvalue, void *s, int *sval) { + SCOPED_TSAN_INTERCEPTOR(sem_getvalue, s, sval); + int res = REAL(sem_getvalue)(s, sval); + if (res == 0) { + Acquire(cur_thread(), pc, (uptr)s); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(read, fd, buf, sz); + int res = REAL(read)(fd, buf, sz); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pread, fd, buf, sz, off); + int res = REAL(pread)(fd, buf, sz, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) { + SCOPED_TSAN_INTERCEPTOR(pread64, fd, buf, sz, off); + int res = REAL(pread64)(fd, buf, sz, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { + SCOPED_TSAN_INTERCEPTOR(readv, fd, vec, cnt); + int res = REAL(readv)(fd, vec, cnt); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, preadv64, int fd, void *vec, int cnt, u64 off) { + SCOPED_TSAN_INTERCEPTOR(preadv64, fd, vec, cnt, off); + int res = REAL(preadv64)(fd, vec, cnt, off); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, write, int fd, void *buf, long_t sz) { + SCOPED_TSAN_INTERCEPTOR(write, fd, buf, sz); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(write)(fd, buf, sz); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwrite, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pwrite, fd, buf, sz, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwrite)(fd, buf, sz, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwrite64, int fd, void *buf, long_t sz, unsigned off) { + SCOPED_TSAN_INTERCEPTOR(pwrite64, fd, buf, sz, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwrite64)(fd, buf, sz, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, writev, int fd, void *vec, int cnt) { + SCOPED_TSAN_INTERCEPTOR(writev, fd, vec, cnt); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(writev)(fd, vec, cnt); + return res; +} + +TSAN_INTERCEPTOR(long_t, pwritev64, int fd, void *vec, int cnt, u64 off) { + SCOPED_TSAN_INTERCEPTOR(pwritev64, fd, vec, cnt, off); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(pwritev64)(fd, vec, cnt, off); + return res; +} + +TSAN_INTERCEPTOR(long_t, send, int fd, void *buf, long_t len, int flags) { + SCOPED_TSAN_INTERCEPTOR(send, fd, buf, len, flags); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(send)(fd, buf, len, flags); + return res; +} + +TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { + SCOPED_TSAN_INTERCEPTOR(sendmsg, fd, msg, flags); + Release(cur_thread(), pc, fd2addr(fd)); + int res = REAL(sendmsg)(fd, msg, flags); + return res; +} + +TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { + SCOPED_TSAN_INTERCEPTOR(recv, fd, buf, len, flags); + int res = REAL(recv)(fd, buf, len, flags); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(long_t, recvmsg, int fd, void *msg, int flags) { + SCOPED_TSAN_INTERCEPTOR(recvmsg, fd, msg, flags); + int res = REAL(recvmsg)(fd, msg, flags); + if (res >= 0) { + Acquire(cur_thread(), pc, fd2addr(fd)); + } + return res; +} + +TSAN_INTERCEPTOR(int, unlink, char *path) { + SCOPED_TSAN_INTERCEPTOR(unlink, path); + Release(cur_thread(), pc, file2addr(path)); + int res = REAL(unlink)(path); + return res; +} + +TSAN_INTERCEPTOR(void*, fopen, char *path, char *mode) { + SCOPED_TSAN_INTERCEPTOR(fopen, path, mode); + void *res = REAL(fopen)(path, mode); + Acquire(cur_thread(), pc, file2addr(path)); + return res; +} + +TSAN_INTERCEPTOR(uptr, fread, void *ptr, uptr size, uptr nmemb, void *f) { + SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + return REAL(fread)(ptr, size, nmemb, f); +} + +TSAN_INTERCEPTOR(uptr, fwrite, const void *p, uptr size, uptr nmemb, void *f) { + SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + return REAL(fwrite)(p, size, nmemb, f); +} + +TSAN_INTERCEPTOR(int, puts, const char *s) { + SCOPED_TSAN_INTERCEPTOR(puts, s); + MemoryAccessRange(thr, pc, (uptr)s, internal_strlen(s), false); + return REAL(puts)(s); +} + +TSAN_INTERCEPTOR(int, rmdir, char *path) { + SCOPED_TSAN_INTERCEPTOR(rmdir, path); + Release(cur_thread(), pc, dir2addr(path)); + int res = REAL(rmdir)(path); + return res; +} + +TSAN_INTERCEPTOR(void*, opendir, char *path) { + SCOPED_TSAN_INTERCEPTOR(opendir, path); + void *res = REAL(opendir)(path); + Acquire(cur_thread(), pc, dir2addr(path)); + return res; +} + +TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) { + SCOPED_TSAN_INTERCEPTOR(epoll_ctl, epfd, op, fd, ev); + if (op == EPOLL_CTL_ADD) { + Release(cur_thread(), pc, epollfd2addr(epfd)); + } + int res = REAL(epoll_ctl)(epfd, op, fd, ev); + return res; +} + +TSAN_INTERCEPTOR(int, epoll_wait, int epfd, void *ev, int cnt, int timeout) { + SCOPED_TSAN_INTERCEPTOR(epoll_wait, epfd, ev, cnt, timeout); + int res = REAL(epoll_wait)(epfd, ev, cnt, timeout); + if (res > 0) { + Acquire(cur_thread(), pc, epollfd2addr(epfd)); + } + return res; +} + +static void ALWAYS_INLINE rtl_generic_sighandler(bool sigact, int sig, + my_siginfo_t *info, void *ctx) { + ThreadState *thr = cur_thread(); + SignalContext *sctx = SigCtx(thr); + // Don't mess with synchronous signals. + if (sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || + sig == SIGABRT || sig == SIGFPE || sig == SIGPIPE || + (sctx && sig == sctx->int_signal_send)) { + CHECK(thr->in_rtl == 0 || thr->in_rtl == 1); + int in_rtl = thr->in_rtl; + thr->in_rtl = 0; + CHECK_EQ(thr->in_signal_handler, false); + thr->in_signal_handler = true; + if (sigact) + sigactions[sig].sa_sigaction(sig, info, ctx); + else + sigactions[sig].sa_handler(sig); + CHECK_EQ(thr->in_signal_handler, true); + thr->in_signal_handler = false; + thr->in_rtl = in_rtl; + return; + } + + if (sctx == 0) + return; + SignalDesc *signal = &sctx->pending_signals[sig]; + if (signal->armed == false) { + signal->armed = true; + signal->sigaction = sigact; + if (info) + internal_memcpy(&signal->siginfo, info, sizeof(*info)); + if (ctx) + internal_memcpy(&signal->ctx, ctx, sizeof(signal->ctx)); + sctx->pending_signal_count++; + } +} + +static void rtl_sighandler(int sig) { + rtl_generic_sighandler(false, sig, 0, 0); +} + +static void rtl_sigaction(int sig, my_siginfo_t *info, void *ctx) { + rtl_generic_sighandler(true, sig, info, ctx); +} + +TSAN_INTERCEPTOR(int, sigaction, int sig, sigaction_t *act, sigaction_t *old) { + SCOPED_TSAN_INTERCEPTOR(sigaction, sig, act, old); + if (old) + internal_memcpy(old, &sigactions[sig], sizeof(*old)); + if (act == 0) + return 0; + internal_memcpy(&sigactions[sig], act, sizeof(*act)); + sigaction_t newact; + internal_memcpy(&newact, act, sizeof(newact)); + sigfillset(&newact.sa_mask); + if (act->sa_handler != SIG_IGN && act->sa_handler != SIG_DFL) { + if (newact.sa_flags & SA_SIGINFO) + newact.sa_sigaction = rtl_sigaction; + else + newact.sa_handler = rtl_sighandler; + } + int res = REAL(sigaction)(sig, &newact, 0); + return res; +} + +TSAN_INTERCEPTOR(sighandler_t, signal, int sig, sighandler_t h) { + sigaction_t act = {}; + act.sa_handler = h; + REAL(memset)(&act.sa_mask, -1, sizeof(act.sa_mask)); + act.sa_flags = 0; + sigaction_t old = {}; + int res = sigaction(sig, &act, &old); + if (res) + return SIG_ERR; + return old.sa_handler; +} + +TSAN_INTERCEPTOR(int, raise, int sig) { + SCOPED_TSAN_INTERCEPTOR(raise, sig); + SignalContext *sctx = SigCtx(thr); + CHECK_NE(sctx, 0); + int prev = sctx->int_signal_send; + sctx->int_signal_send = sig; + int res = REAL(raise)(sig); + CHECK_EQ(sctx->int_signal_send, sig); + sctx->int_signal_send = prev; + return res; +} + +TSAN_INTERCEPTOR(int, kill, int pid, int sig) { + SCOPED_TSAN_INTERCEPTOR(kill, pid, sig); + SignalContext *sctx = SigCtx(thr); + CHECK_NE(sctx, 0); + int prev = sctx->int_signal_send; + if (pid == GetPid()) { + sctx->int_signal_send = sig; + } + int res = REAL(kill)(pid, sig); + if (pid == GetPid()) { + CHECK_EQ(sctx->int_signal_send, sig); + sctx->int_signal_send = prev; + } + return res; +} + +TSAN_INTERCEPTOR(int, pthread_kill, void *tid, int sig) { + SCOPED_TSAN_INTERCEPTOR(pthread_kill, tid, sig); + SignalContext *sctx = SigCtx(thr); + CHECK_NE(sctx, 0); + int prev = sctx->int_signal_send; + if (tid == pthread_self()) { + sctx->int_signal_send = sig; + } + int res = REAL(pthread_kill)(tid, sig); + if (tid == pthread_self()) { + CHECK_EQ(sctx->int_signal_send, sig); + sctx->int_signal_send = prev; + } + return res; +} + +static void process_pending_signals(ThreadState *thr) { + CHECK_EQ(thr->in_rtl, 0); + SignalContext *sctx = SigCtx(thr); + if (sctx == 0 || sctx->pending_signal_count == 0 || thr->in_signal_handler) + return; + thr->in_signal_handler = true; + sctx->pending_signal_count = 0; + // These are too big for stack. + static THREADLOCAL sigset_t emptyset, oldset; + sigfillset(&emptyset); + pthread_sigmask(SIG_SETMASK, &emptyset, &oldset); + for (int sig = 0; sig < kSigCount; sig++) { + SignalDesc *signal = &sctx->pending_signals[sig]; + if (signal->armed) { + signal->armed = false; + if (sigactions[sig].sa_handler != SIG_DFL + && sigactions[sig].sa_handler != SIG_IGN) { + // Insure that the handler does not spoil errno. + const int saved_errno = errno; + errno = 0; + if (signal->sigaction) + sigactions[sig].sa_sigaction(sig, &signal->siginfo, &signal->ctx); + else + sigactions[sig].sa_handler(sig); + if (errno != 0) { + ScopedInRtl in_rtl; + StackTrace stack; + uptr pc = signal->sigaction ? + (uptr)sigactions[sig].sa_sigaction : + (uptr)sigactions[sig].sa_handler; + stack.Init(&pc, 1); + ScopedReport rep(ReportTypeErrnoInSignal); + rep.AddStack(&stack); + OutputReport(rep, rep.GetReport()->stacks[0]); + } + errno = saved_errno; + } + } + } + pthread_sigmask(SIG_SETMASK, &oldset, 0); + CHECK_EQ(thr->in_signal_handler, true); + thr->in_signal_handler = false; +} + +namespace __tsan { + +void InitializeInterceptors() { + CHECK_GT(cur_thread()->in_rtl, 0); + + // We need to setup it early, because functions like dlsym() can call it. + REAL(memset) = internal_memset; + REAL(memcpy) = internal_memcpy; + REAL(memcmp) = internal_memcmp; + + TSAN_INTERCEPT(longjmp); + TSAN_INTERCEPT(siglongjmp); + + TSAN_INTERCEPT(malloc); + TSAN_INTERCEPT(calloc); + TSAN_INTERCEPT(realloc); + TSAN_INTERCEPT(free); + TSAN_INTERCEPT(cfree); + TSAN_INTERCEPT(mmap); + TSAN_INTERCEPT(mmap64); + TSAN_INTERCEPT(munmap); + TSAN_INTERCEPT(memalign); + TSAN_INTERCEPT(valloc); + TSAN_INTERCEPT(pvalloc); + TSAN_INTERCEPT(posix_memalign); + + TSAN_INTERCEPT(_Znwm); + TSAN_INTERCEPT(_ZnwmRKSt9nothrow_t); + TSAN_INTERCEPT(_Znam); + TSAN_INTERCEPT(_ZnamRKSt9nothrow_t); + TSAN_INTERCEPT(_ZdlPv); + TSAN_INTERCEPT(_ZdlPvRKSt9nothrow_t); + TSAN_INTERCEPT(_ZdaPv); + TSAN_INTERCEPT(_ZdaPvRKSt9nothrow_t); + + TSAN_INTERCEPT(strlen); + TSAN_INTERCEPT(memset); + TSAN_INTERCEPT(memcpy); + TSAN_INTERCEPT(strcmp); + TSAN_INTERCEPT(memchr); + TSAN_INTERCEPT(memrchr); + TSAN_INTERCEPT(memmove); + TSAN_INTERCEPT(memcmp); + TSAN_INTERCEPT(strchr); + TSAN_INTERCEPT(strchrnul); + TSAN_INTERCEPT(strrchr); + TSAN_INTERCEPT(strncmp); + TSAN_INTERCEPT(strcpy); // NOLINT + TSAN_INTERCEPT(strncpy); + TSAN_INTERCEPT(strstr); + + TSAN_INTERCEPT(__cxa_guard_acquire); + TSAN_INTERCEPT(__cxa_guard_release); + + TSAN_INTERCEPT(pthread_create); + TSAN_INTERCEPT(pthread_join); + TSAN_INTERCEPT(pthread_detach); + + TSAN_INTERCEPT(pthread_mutex_init); + TSAN_INTERCEPT(pthread_mutex_destroy); + TSAN_INTERCEPT(pthread_mutex_lock); + TSAN_INTERCEPT(pthread_mutex_trylock); + TSAN_INTERCEPT(pthread_mutex_timedlock); + TSAN_INTERCEPT(pthread_mutex_unlock); + + TSAN_INTERCEPT(pthread_spin_init); + TSAN_INTERCEPT(pthread_spin_destroy); + TSAN_INTERCEPT(pthread_spin_lock); + TSAN_INTERCEPT(pthread_spin_trylock); + TSAN_INTERCEPT(pthread_spin_unlock); + + TSAN_INTERCEPT(pthread_rwlock_init); + TSAN_INTERCEPT(pthread_rwlock_destroy); + TSAN_INTERCEPT(pthread_rwlock_rdlock); + TSAN_INTERCEPT(pthread_rwlock_tryrdlock); + TSAN_INTERCEPT(pthread_rwlock_timedrdlock); + TSAN_INTERCEPT(pthread_rwlock_wrlock); + TSAN_INTERCEPT(pthread_rwlock_trywrlock); + TSAN_INTERCEPT(pthread_rwlock_timedwrlock); + TSAN_INTERCEPT(pthread_rwlock_unlock); + + TSAN_INTERCEPT(pthread_cond_init); + TSAN_INTERCEPT(pthread_cond_destroy); + TSAN_INTERCEPT(pthread_cond_signal); + TSAN_INTERCEPT(pthread_cond_broadcast); + TSAN_INTERCEPT(pthread_cond_wait); + TSAN_INTERCEPT(pthread_cond_timedwait); + + TSAN_INTERCEPT(pthread_barrier_init); + TSAN_INTERCEPT(pthread_barrier_destroy); + TSAN_INTERCEPT(pthread_barrier_wait); + + TSAN_INTERCEPT(pthread_once); + + TSAN_INTERCEPT(sem_init); + TSAN_INTERCEPT(sem_destroy); + TSAN_INTERCEPT(sem_wait); + TSAN_INTERCEPT(sem_trywait); + TSAN_INTERCEPT(sem_timedwait); + TSAN_INTERCEPT(sem_post); + TSAN_INTERCEPT(sem_getvalue); + + TSAN_INTERCEPT(read); + TSAN_INTERCEPT(pread); + TSAN_INTERCEPT(pread64); + TSAN_INTERCEPT(readv); + TSAN_INTERCEPT(preadv64); + TSAN_INTERCEPT(write); + TSAN_INTERCEPT(pwrite); + TSAN_INTERCEPT(pwrite64); + TSAN_INTERCEPT(writev); + TSAN_INTERCEPT(pwritev64); + TSAN_INTERCEPT(send); + TSAN_INTERCEPT(sendmsg); + TSAN_INTERCEPT(recv); + TSAN_INTERCEPT(recvmsg); + + TSAN_INTERCEPT(unlink); + TSAN_INTERCEPT(fopen); + TSAN_INTERCEPT(fread); + TSAN_INTERCEPT(fwrite); + TSAN_INTERCEPT(puts); + TSAN_INTERCEPT(rmdir); + TSAN_INTERCEPT(opendir); + + TSAN_INTERCEPT(epoll_ctl); + TSAN_INTERCEPT(epoll_wait); + + TSAN_INTERCEPT(sigaction); + TSAN_INTERCEPT(signal); + TSAN_INTERCEPT(raise); + TSAN_INTERCEPT(kill); + TSAN_INTERCEPT(pthread_kill); + + atexit_ctx = new(internal_alloc(MBlockAtExit, sizeof(AtExitContext))) + AtExitContext(); + + if (__cxa_atexit(&finalize, 0, 0)) { + TsanPrintf("ThreadSanitizer: failed to setup atexit callback\n"); + Die(); + } + + if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) { + TsanPrintf("ThreadSanitizer: failed to create thread key\n"); + Die(); + } +} + +void internal_start_thread(void(*func)(void *arg), void *arg) { + void *th; + REAL(pthread_create)(&th, 0, (void*(*)(void *arg))func, arg); + REAL(pthread_detach)(th); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_interface.cc b/lib/tsan/rtl/tsan_interface.cc new file mode 100644 index 0000000..6d09546 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface.cc @@ -0,0 +1,42 @@ +//===-- tsan_interface.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface.h" +#include "tsan_interface_ann.h" +#include "tsan_rtl.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +void __tsan_init() { + Initialize(cur_thread()); +} + +void __tsan_read16(void *addr) { + MemoryRead8Byte(cur_thread(), CALLERPC, (uptr)addr); + MemoryRead8Byte(cur_thread(), CALLERPC, (uptr)addr + 8); +} + +void __tsan_write16(void *addr) { + MemoryWrite8Byte(cur_thread(), CALLERPC, (uptr)addr); + MemoryWrite8Byte(cur_thread(), CALLERPC, (uptr)addr + 8); +} + +void __tsan_acquire(void *addr) { + Acquire(cur_thread(), CALLERPC, (uptr)addr); +} + +void __tsan_release(void *addr) { + Release(cur_thread(), CALLERPC, (uptr)addr); +} diff --git a/lib/tsan/rtl/tsan_interface.h b/lib/tsan/rtl/tsan_interface.h new file mode 100644 index 0000000..ed21ec6 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface.h @@ -0,0 +1,51 @@ +//===-- tsan_interface.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// The functions declared in this header will be inserted by the instrumentation +// module. +// This header can be included by the instrumented program or by TSan tests. +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_H +#define TSAN_INTERFACE_H + +// This header should NOT include any other headers. +// All functions in this header are extern "C" and start with __tsan_. + +#ifdef __cplusplus +extern "C" { +#endif + +// This function should be called at the very beginning of the process, +// before any instrumented code is executed and before any call to malloc. +void __tsan_init(); + +void __tsan_read1(void *addr); +void __tsan_read2(void *addr); +void __tsan_read4(void *addr); +void __tsan_read8(void *addr); +void __tsan_read16(void *addr); + +void __tsan_write1(void *addr); +void __tsan_write2(void *addr); +void __tsan_write4(void *addr); +void __tsan_write8(void *addr); +void __tsan_write16(void *addr); + +void __tsan_vptr_update(void **vptr_p, void *new_val); + +void __tsan_func_entry(void *call_pc); +void __tsan_func_exit(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // TSAN_INTERFACE_H diff --git a/lib/tsan/rtl/tsan_interface_ann.cc b/lib/tsan/rtl/tsan_interface_ann.cc new file mode 100644 index 0000000..a605b6c --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_ann.cc @@ -0,0 +1,352 @@ +//===-- tsan_interface_ann.cc ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_interface_ann.h" +#include "tsan_mutex.h" +#include "tsan_report.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "tsan_flags.h" +#include "tsan_platform.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +namespace __tsan { + +class ScopedAnnotation { + public: + ScopedAnnotation(ThreadState *thr, const char *aname, const char *f, int l, + uptr pc) + : thr_(thr) + , in_rtl_(thr->in_rtl) { + CHECK_EQ(thr_->in_rtl, 0); + FuncEntry(thr_, pc); + thr_->in_rtl++; + DPrintf("#%d: annotation %s() %s:%d\n", thr_->tid, aname, f, l); + } + + ~ScopedAnnotation() { + thr_->in_rtl--; + CHECK_EQ(in_rtl_, thr_->in_rtl); + FuncExit(thr_); + } + private: + ThreadState *const thr_; + const int in_rtl_; +}; + +#define SCOPED_ANNOTATION(typ) \ + if (!flags()->enable_annotations) \ + return; \ + ThreadState *thr = cur_thread(); \ + StatInc(thr, StatAnnotation); \ + StatInc(thr, Stat##typ); \ + ScopedAnnotation sa(thr, __FUNCTION__, f, l, \ + (uptr)__builtin_return_address(0)); \ + const uptr pc = (uptr)&__FUNCTION__; \ + (void)pc; \ +/**/ + +static const int kMaxDescLen = 128; + +struct ExpectRace { + ExpectRace *next; + ExpectRace *prev; + int hitcount; + uptr addr; + uptr size; + char *file; + int line; + char desc[kMaxDescLen]; +}; + +struct DynamicAnnContext { + Mutex mtx; + ExpectRace expect; + ExpectRace benign; + + DynamicAnnContext() + : mtx(MutexTypeAnnotations, StatMtxAnnotations) { + } +}; + +static DynamicAnnContext *dyn_ann_ctx; +static char dyn_ann_ctx_placeholder[sizeof(DynamicAnnContext)] ALIGNED(64); + +static void AddExpectRace(ExpectRace *list, + char *f, int l, uptr addr, uptr size, char *desc) { + ExpectRace *race = list->next; + for (; race != list; race = race->next) { + if (race->addr == addr && race->size == size) + return; + } + race = (ExpectRace*)internal_alloc(MBlockExpectRace, sizeof(ExpectRace)); + race->hitcount = 0; + race->addr = addr; + race->size = size; + race->file = f; + race->line = l; + race->desc[0] = 0; + if (desc) { + int i = 0; + for (; i < kMaxDescLen - 1 && desc[i]; i++) + race->desc[i] = desc[i]; + race->desc[i] = 0; + } + race->prev = list; + race->next = list->next; + race->next->prev = race; + list->next = race; +} + +static ExpectRace *FindRace(ExpectRace *list, uptr addr, uptr size) { + for (ExpectRace *race = list->next; race != list; race = race->next) { + uptr maxbegin = max(race->addr, addr); + uptr minend = min(race->addr + race->size, addr + size); + if (maxbegin < minend) + return race; + } + return 0; +} + +static bool CheckContains(ExpectRace *list, uptr addr, uptr size) { + ExpectRace *race = FindRace(list, addr, size); + if (race == 0 && AlternativeAddress(addr)) + race = FindRace(list, AlternativeAddress(addr), size); + if (race == 0) + return false; + DPrintf("Hit expected/benign race: %s addr=%zx:%d %s:%d\n", + race->desc, race->addr, (int)race->size, race->file, race->line); + race->hitcount++; + return true; +} + +static void InitList(ExpectRace *list) { + list->next = list; + list->prev = list; +} + +void InitializeDynamicAnnotations() { + dyn_ann_ctx = new(dyn_ann_ctx_placeholder) DynamicAnnContext; + InitList(&dyn_ann_ctx->expect); + InitList(&dyn_ann_ctx->benign); +} + +bool IsExpectedReport(uptr addr, uptr size) { + Lock lock(&dyn_ann_ctx->mtx); + if (CheckContains(&dyn_ann_ctx->expect, addr, size)) + return true; + if (CheckContains(&dyn_ann_ctx->benign, addr, size)) + return true; + return false; +} + +} // namespace __tsan + +using namespace __tsan; // NOLINT + +extern "C" { +void AnnotateHappensBefore(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensBefore); + Release(cur_thread(), CALLERPC, addr); +} + +void AnnotateHappensAfter(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensAfter); + Acquire(cur_thread(), CALLERPC, addr); +} + +void AnnotateCondVarSignal(char *f, int l, uptr cv) { + SCOPED_ANNOTATION(AnnotateCondVarSignal); +} + +void AnnotateCondVarSignalAll(char *f, int l, uptr cv) { + SCOPED_ANNOTATION(AnnotateCondVarSignalAll); +} + +void AnnotateMutexIsNotPHB(char *f, int l, uptr mu) { + SCOPED_ANNOTATION(AnnotateMutexIsNotPHB); +} + +void AnnotateCondVarWait(char *f, int l, uptr cv, uptr lock) { + SCOPED_ANNOTATION(AnnotateCondVarWait); +} + +void AnnotateRWLockCreate(char *f, int l, uptr lock) { + SCOPED_ANNOTATION(AnnotateRWLockCreate); +} + +void AnnotateRWLockDestroy(char *f, int l, uptr lock) { + SCOPED_ANNOTATION(AnnotateRWLockDestroy); +} + +void AnnotateRWLockAcquired(char *f, int l, uptr lock, uptr is_w) { + SCOPED_ANNOTATION(AnnotateRWLockAcquired); +} + +void AnnotateRWLockReleased(char *f, int l, uptr lock, uptr is_w) { + SCOPED_ANNOTATION(AnnotateRWLockReleased); +} + +void AnnotateTraceMemory(char *f, int l, uptr mem) { + SCOPED_ANNOTATION(AnnotateTraceMemory); +} + +void AnnotateFlushState(char *f, int l) { + SCOPED_ANNOTATION(AnnotateFlushState); +} + +void AnnotateNewMemory(char *f, int l, uptr mem, uptr size) { + SCOPED_ANNOTATION(AnnotateNewMemory); +} + +void AnnotateNoOp(char *f, int l, uptr mem) { + SCOPED_ANNOTATION(AnnotateNoOp); +} + +static void ReportMissedExpectedRace(ExpectRace *race) { + TsanPrintf("==================\n"); + TsanPrintf("WARNING: ThreadSanitizer: missed expected data race\n"); + TsanPrintf(" %s addr=%zx %s:%d\n", + race->desc, race->addr, race->file, race->line); + TsanPrintf("==================\n"); +} + +void AnnotateFlushExpectedRaces(char *f, int l) { + SCOPED_ANNOTATION(AnnotateFlushExpectedRaces); + Lock lock(&dyn_ann_ctx->mtx); + while (dyn_ann_ctx->expect.next != &dyn_ann_ctx->expect) { + ExpectRace *race = dyn_ann_ctx->expect.next; + if (race->hitcount == 0) { + CTX()->nmissed_expected++; + ReportMissedExpectedRace(race); + } + race->prev->next = race->next; + race->next->prev = race->prev; + internal_free(race); + } +} + +void AnnotateEnableRaceDetection(char *f, int l, int enable) { + SCOPED_ANNOTATION(AnnotateEnableRaceDetection); + // FIXME: Reconsider this functionality later. It may be irrelevant. +} + +void AnnotateMutexIsUsedAsCondVar(char *f, int l, uptr mu) { + SCOPED_ANNOTATION(AnnotateMutexIsUsedAsCondVar); +} + +void AnnotatePCQGet(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQGet); +} + +void AnnotatePCQPut(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQPut); +} + +void AnnotatePCQDestroy(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQDestroy); +} + +void AnnotatePCQCreate(char *f, int l, uptr pcq) { + SCOPED_ANNOTATION(AnnotatePCQCreate); +} + +void AnnotateExpectRace(char *f, int l, uptr mem, char *desc) { + SCOPED_ANNOTATION(AnnotateExpectRace); + Lock lock(&dyn_ann_ctx->mtx); + AddExpectRace(&dyn_ann_ctx->expect, + f, l, mem, 1, desc); + DPrintf("Add expected race: %s addr=%zx %s:%d\n", desc, mem, f, l); +} + +static void BenignRaceImpl(char *f, int l, uptr mem, uptr size, char *desc) { + Lock lock(&dyn_ann_ctx->mtx); + AddExpectRace(&dyn_ann_ctx->benign, + f, l, mem, size, desc); + DPrintf("Add benign race: %s addr=%zx %s:%d\n", desc, mem, f, l); +} + +// FIXME: Turn it off later. WTF is benign race?1?? Go talk to Hans Boehm. +void AnnotateBenignRaceSized(char *f, int l, uptr mem, uptr size, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRaceSized); + BenignRaceImpl(f, l, mem, size, desc); +} + +void AnnotateBenignRace(char *f, int l, uptr mem, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRace); + BenignRaceImpl(f, l, mem, 1, desc); +} + +void AnnotateIgnoreReadsBegin(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreReadsBegin); + IgnoreCtl(cur_thread(), false, true); +} + +void AnnotateIgnoreReadsEnd(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreReadsEnd); + IgnoreCtl(cur_thread(), false, false); +} + +void AnnotateIgnoreWritesBegin(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreWritesBegin); + IgnoreCtl(cur_thread(), true, true); +} + +void AnnotateIgnoreWritesEnd(char *f, int l) { + SCOPED_ANNOTATION(AnnotateIgnoreWritesEnd); + IgnoreCtl(cur_thread(), true, false); +} + +void AnnotatePublishMemoryRange(char *f, int l, uptr addr, uptr size) { + SCOPED_ANNOTATION(AnnotatePublishMemoryRange); +} + +void AnnotateUnpublishMemoryRange(char *f, int l, uptr addr, uptr size) { + SCOPED_ANNOTATION(AnnotateUnpublishMemoryRange); +} + +void AnnotateThreadName(char *f, int l, char *name) { + SCOPED_ANNOTATION(AnnotateThreadName); +} + +void WTFAnnotateHappensBefore(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensBefore); +} + +void WTFAnnotateHappensAfter(char *f, int l, uptr addr) { + SCOPED_ANNOTATION(AnnotateHappensAfter); +} + +void WTFAnnotateBenignRaceSized(char *f, int l, uptr mem, uptr sz, char *desc) { + SCOPED_ANNOTATION(AnnotateBenignRaceSized); +} + +int RunningOnValgrind() { + return flags()->running_on_valgrind; +} + +double __attribute__((weak)) ValgrindSlowdown(void) { + return 10.0; +} + +const char *ThreadSanitizerQuery(const char *query) { + if (internal_strcmp(query, "pure_happens_before") == 0) + return "1"; + else + return "0"; +} +} // extern "C" diff --git a/lib/tsan/rtl/tsan_interface_ann.h b/lib/tsan/rtl/tsan_interface_ann.h new file mode 100644 index 0000000..09e807a --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_ann.h @@ -0,0 +1,31 @@ +//===-- tsan_interface_ann.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Interface for dynamic annotations. +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_ANN_H +#define TSAN_INTERFACE_ANN_H + +// This header should NOT include any other headers. +// All functions in this header are extern "C" and start with __tsan_. + +#ifdef __cplusplus +extern "C" { +#endif + +void __tsan_acquire(void *addr); +void __tsan_release(void *addr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // TSAN_INTERFACE_ANN_H diff --git a/lib/tsan/rtl/tsan_interface_atomic.cc b/lib/tsan/rtl/tsan_interface_atomic.cc new file mode 100644 index 0000000..a3982a1 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_atomic.cc @@ -0,0 +1,321 @@ +//===-- tsan_interface_atomic.cc ------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_interface_atomic.h" +#include "tsan_flags.h" +#include "tsan_rtl.h" + +using namespace __tsan; // NOLINT + +class ScopedAtomic { + public: + ScopedAtomic(ThreadState *thr, uptr pc, const char *func) + : thr_(thr) { + CHECK_EQ(thr_->in_rtl, 1); // 1 due to our own ScopedInRtl member. + DPrintf("#%d: %s\n", thr_->tid, func); + } + ~ScopedAtomic() { + CHECK_EQ(thr_->in_rtl, 1); + } + private: + ThreadState *thr_; + ScopedInRtl in_rtl_; +}; + +// Some shortcuts. +typedef __tsan_memory_order morder; +typedef __tsan_atomic8 a8; +typedef __tsan_atomic16 a16; +typedef __tsan_atomic32 a32; +typedef __tsan_atomic64 a64; +const int mo_relaxed = __tsan_memory_order_relaxed; +const int mo_consume = __tsan_memory_order_consume; +const int mo_acquire = __tsan_memory_order_acquire; +const int mo_release = __tsan_memory_order_release; +const int mo_acq_rel = __tsan_memory_order_acq_rel; +const int mo_seq_cst = __tsan_memory_order_seq_cst; + +static void AtomicStatInc(ThreadState *thr, uptr size, morder mo, StatType t) { + StatInc(thr, StatAtomic); + StatInc(thr, t); + StatInc(thr, size == 1 ? StatAtomic1 + : size == 2 ? StatAtomic2 + : size == 4 ? StatAtomic4 + : StatAtomic8); + StatInc(thr, mo == mo_relaxed ? StatAtomicRelaxed + : mo == mo_consume ? StatAtomicConsume + : mo == mo_acquire ? StatAtomicAcquire + : mo == mo_release ? StatAtomicRelease + : mo == mo_acq_rel ? StatAtomicAcq_Rel + : StatAtomicSeq_Cst); +} + +#define SCOPED_ATOMIC(func, ...) \ + mo = flags()->force_seq_cst_atomics ? (morder)mo_seq_cst : mo; \ + ThreadState *const thr = cur_thread(); \ + const uptr pc = (uptr)__builtin_return_address(0); \ + AtomicStatInc(thr, sizeof(*a), mo, StatAtomic##func); \ + ScopedAtomic sa(thr, pc, __FUNCTION__); \ + return Atomic##func(thr, pc, __VA_ARGS__); \ +/**/ + +template +static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a, + morder mo) { + CHECK(mo & (mo_relaxed | mo_consume | mo_acquire | mo_seq_cst)); + T v = *a; + if (mo & (mo_consume | mo_acquire | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + CHECK(mo & (mo_relaxed | mo_release | mo_seq_cst)); + if (mo & (mo_release | mo_seq_cst)) + Release(thr, pc, (uptr)a); + *a = v; +} + +template +static T AtomicExchange(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_lock_test_and_set(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static T AtomicFetchAdd(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_fetch_and_add(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static T AtomicFetchAnd(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_fetch_and_and(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static T AtomicFetchOr(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_fetch_and_or(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static T AtomicFetchXor(ThreadState *thr, uptr pc, volatile T *a, T v, + morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + v = __sync_fetch_and_xor(a, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + return v; +} + +template +static bool AtomicCAS(ThreadState *thr, uptr pc, + volatile T *a, T *c, T v, morder mo) { + if (mo & (mo_release | mo_acq_rel | mo_seq_cst)) + Release(thr, pc, (uptr)a); + T cc = *c; + T pr = __sync_val_compare_and_swap(a, cc, v); + if (mo & (mo_consume | mo_acquire | mo_acq_rel | mo_seq_cst)) + Acquire(thr, pc, (uptr)a); + if (pr == cc) + return true; + *c = pr; + return false; +} + +static void AtomicFence(ThreadState *thr, uptr pc, morder mo) { + __sync_synchronize(); +} + +a8 __tsan_atomic8_load(const volatile a8 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a16 __tsan_atomic16_load(const volatile a16 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a32 __tsan_atomic32_load(const volatile a32 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +a64 __tsan_atomic64_load(const volatile a64 *a, morder mo) { + SCOPED_ATOMIC(Load, a, mo); +} + +void __tsan_atomic8_store(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic16_store(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic32_store(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +void __tsan_atomic64_store(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(Store, a, v, mo); +} + +a8 __tsan_atomic8_exchange(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a16 __tsan_atomic16_exchange(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a32 __tsan_atomic32_exchange(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a64 __tsan_atomic64_exchange(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(Exchange, a, v, mo); +} + +a8 __tsan_atomic8_fetch_add(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +a16 __tsan_atomic16_fetch_add(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +a32 __tsan_atomic32_fetch_add(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +a64 __tsan_atomic64_fetch_add(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchAdd, a, v, mo); +} + +a8 __tsan_atomic8_fetch_and(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchAnd, a, v, mo); +} + +a16 __tsan_atomic16_fetch_and(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchAnd, a, v, mo); +} + +a32 __tsan_atomic32_fetch_and(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchAnd, a, v, mo); +} + +a64 __tsan_atomic64_fetch_and(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchAnd, a, v, mo); +} + +a8 __tsan_atomic8_fetch_or(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchOr, a, v, mo); +} + +a16 __tsan_atomic16_fetch_or(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchOr, a, v, mo); +} + +a32 __tsan_atomic32_fetch_or(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchOr, a, v, mo); +} + +a64 __tsan_atomic64_fetch_or(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchOr, a, v, mo); +} + +a8 __tsan_atomic8_fetch_xor(volatile a8 *a, a8 v, morder mo) { + SCOPED_ATOMIC(FetchXor, a, v, mo); +} + +a16 __tsan_atomic16_fetch_xor(volatile a16 *a, a16 v, morder mo) { + SCOPED_ATOMIC(FetchXor, a, v, mo); +} + +a32 __tsan_atomic32_fetch_xor(volatile a32 *a, a32 v, morder mo) { + SCOPED_ATOMIC(FetchXor, a, v, mo); +} + +a64 __tsan_atomic64_fetch_xor(volatile a64 *a, a64 v, morder mo) { + SCOPED_ATOMIC(FetchXor, a, v, mo); +} + +int __tsan_atomic8_compare_exchange_strong(volatile a8 *a, a8 *c, a8 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic16_compare_exchange_strong(volatile a16 *a, a16 *c, a16 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic32_compare_exchange_strong(volatile a32 *a, a32 *c, a32 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic64_compare_exchange_strong(volatile a64 *a, a64 *c, a64 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic8_compare_exchange_weak(volatile a8 *a, a8 *c, a8 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic16_compare_exchange_weak(volatile a16 *a, a16 *c, a16 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic32_compare_exchange_weak(volatile a32 *a, a32 *c, a32 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +int __tsan_atomic64_compare_exchange_weak(volatile a64 *a, a64 *c, a64 v, + morder mo) { + SCOPED_ATOMIC(CAS, a, c, v, mo); +} + +void __tsan_atomic_thread_fence(morder mo) { + char* a; + SCOPED_ATOMIC(Fence, mo); +} diff --git a/lib/tsan/rtl/tsan_interface_atomic.h b/lib/tsan/rtl/tsan_interface_atomic.h new file mode 100644 index 0000000..dff32b1 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_atomic.h @@ -0,0 +1,121 @@ +//===-- tsan_interface_atomic.h ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_ATOMIC_H +#define TSAN_INTERFACE_ATOMIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char __tsan_atomic8; +typedef short __tsan_atomic16; // NOLINT +typedef int __tsan_atomic32; +typedef long __tsan_atomic64; // NOLINT + +typedef enum { + __tsan_memory_order_relaxed = 1 << 0, + __tsan_memory_order_consume = 1 << 1, + __tsan_memory_order_acquire = 1 << 2, + __tsan_memory_order_release = 1 << 3, + __tsan_memory_order_acq_rel = 1 << 4, + __tsan_memory_order_seq_cst = 1 << 5, +} __tsan_memory_order; + +__tsan_atomic8 __tsan_atomic8_load(const volatile __tsan_atomic8 *a, + __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_load(const volatile __tsan_atomic16 *a, + __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_load(const volatile __tsan_atomic32 *a, + __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_load(const volatile __tsan_atomic64 *a, + __tsan_memory_order mo); + +void __tsan_atomic8_store(volatile __tsan_atomic8 *a, __tsan_atomic8 v, + __tsan_memory_order mo); +void __tsan_atomic16_store(volatile __tsan_atomic16 *a, __tsan_atomic16 v, + __tsan_memory_order mo); +void __tsan_atomic32_store(volatile __tsan_atomic32 *a, __tsan_atomic32 v, + __tsan_memory_order mo); +void __tsan_atomic64_store(volatile __tsan_atomic64 *a, __tsan_atomic64 v, + __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_exchange(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_exchange(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_exchange(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_exchange(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_add(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_add(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_add(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_add(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_and(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_and(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_and(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_and(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_or(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_or(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_or(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_or(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +__tsan_atomic8 __tsan_atomic8_fetch_xor(volatile __tsan_atomic8 *a, + __tsan_atomic8 v, __tsan_memory_order mo); +__tsan_atomic16 __tsan_atomic16_fetch_xor(volatile __tsan_atomic16 *a, + __tsan_atomic16 v, __tsan_memory_order mo); +__tsan_atomic32 __tsan_atomic32_fetch_xor(volatile __tsan_atomic32 *a, + __tsan_atomic32 v, __tsan_memory_order mo); +__tsan_atomic64 __tsan_atomic64_fetch_xor(volatile __tsan_atomic64 *a, + __tsan_atomic64 v, __tsan_memory_order mo); + +int __tsan_atomic8_compare_exchange_weak(volatile __tsan_atomic8 *a, + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo); +int __tsan_atomic16_compare_exchange_weak(volatile __tsan_atomic16 *a, + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo); +int __tsan_atomic32_compare_exchange_weak(volatile __tsan_atomic32 *a, + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo); +int __tsan_atomic64_compare_exchange_weak(volatile __tsan_atomic64 *a, + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo); + +int __tsan_atomic8_compare_exchange_strong(volatile __tsan_atomic8 *a, + __tsan_atomic8 *c, __tsan_atomic8 v, __tsan_memory_order mo); +int __tsan_atomic16_compare_exchange_strong(volatile __tsan_atomic16 *a, + __tsan_atomic16 *c, __tsan_atomic16 v, __tsan_memory_order mo); +int __tsan_atomic32_compare_exchange_strong(volatile __tsan_atomic32 *a, + __tsan_atomic32 *c, __tsan_atomic32 v, __tsan_memory_order mo); +int __tsan_atomic64_compare_exchange_strong(volatile __tsan_atomic64 *a, + __tsan_atomic64 *c, __tsan_atomic64 v, __tsan_memory_order mo); + +void __tsan_atomic_thread_fence(__tsan_memory_order mo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // #ifndef TSAN_INTERFACE_ATOMIC_H diff --git a/lib/tsan/rtl/tsan_interface_inl.h b/lib/tsan/rtl/tsan_interface_inl.h new file mode 100644 index 0000000..233f902 --- /dev/null +++ b/lib/tsan/rtl/tsan_interface_inl.h @@ -0,0 +1,65 @@ +//===-- tsan_interface_inl.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface.h" +#include "tsan_rtl.h" + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +using namespace __tsan; // NOLINT + +void __tsan_read1(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 0, 0); +} + +void __tsan_read2(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 1, 0); +} + +void __tsan_read4(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 2, 0); +} + +void __tsan_read8(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 3, 0); +} + +void __tsan_write1(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 0, 1); +} + +void __tsan_write2(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 1, 1); +} + +void __tsan_write4(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 2, 1); +} + +void __tsan_write8(void *addr) { + MemoryAccess(cur_thread(), CALLERPC, (uptr)addr, 3, 1); +} + +void __tsan_vptr_update(void **vptr_p, void *new_val) { + CHECK_EQ(sizeof(vptr_p), 8); + if (*vptr_p != new_val) + MemoryAccess(cur_thread(), CALLERPC, (uptr)vptr_p, 3, 1); +} + +void __tsan_func_entry(void *pc) { + FuncEntry(cur_thread(), (uptr)pc); +} + +void __tsan_func_exit() { + FuncExit(cur_thread()); +} diff --git a/lib/tsan/rtl/tsan_md5.cc b/lib/tsan/rtl/tsan_md5.cc new file mode 100644 index 0000000..c9d671f --- /dev/null +++ b/lib/tsan/rtl/tsan_md5.cc @@ -0,0 +1,245 @@ +//===-- tsan_md5.cc -------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_defs.h" + +namespace __tsan { + +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | ~(z))) + +#define STEP(f, a, b, c, d, x, t, s) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +#define SET(n) \ + (*(MD5_u32plus *)&ptr[(n) * 4]) +#define GET(n) \ + SET(n) + +typedef unsigned int MD5_u32plus; +typedef unsigned long ulong_t; // NOLINT + +typedef struct { + MD5_u32plus lo, hi; + MD5_u32plus a, b, c, d; + unsigned char buffer[64]; + MD5_u32plus block[16]; +} MD5_CTX; + +static void *body(MD5_CTX *ctx, void *data, ulong_t size) { + unsigned char *ptr; + MD5_u32plus a, b, c, d; + MD5_u32plus saved_a, saved_b, saved_c, saved_d; + + ptr = (unsigned char*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + + STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) + STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) + STEP(F, c, d, a, b, SET(2), 0x242070db, 17) + STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) + STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) + STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) + STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) + STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) + STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) + STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) + STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) + STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) + STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) + STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) + STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) + STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) + + STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) + STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) + STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) + STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) + STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) + STEP(G, d, a, b, c, GET(10), 0x02441453, 9) + STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) + STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) + STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) + STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) + STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) + STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) + STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) + STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) + STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) + STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) + + STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) + STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) + STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) + STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) + STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) + STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) + STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) + STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) + STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) + STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) + STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) + STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) + STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) + STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) + STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) + STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) + + STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) + STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) + STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) + STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) + STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) + STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) + STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) + STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) + STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) + STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) + STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) + STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) + STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) + STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) + STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) + STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while (size -= 64); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + return ptr; +} + +void MD5_Init(MD5_CTX *ctx) { + ctx->a = 0x67452301; + ctx->b = 0xefcdab89; + ctx->c = 0x98badcfe; + ctx->d = 0x10325476; + + ctx->lo = 0; + ctx->hi = 0; +} + +void MD5_Update(MD5_CTX *ctx, void *data, ulong_t size) { + MD5_u32plus saved_lo; + ulong_t used, free; + + saved_lo = ctx->lo; + if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) + ctx->hi++; + ctx->hi += size >> 29; + + used = saved_lo & 0x3f; + + if (used) { + free = 64 - used; + + if (size < free) { + internal_memcpy(&ctx->buffer[used], data, size); + return; + } + + internal_memcpy(&ctx->buffer[used], data, free); + data = (unsigned char *)data + free; + size -= free; + body(ctx, ctx->buffer, 64); + } + + if (size >= 64) { + data = body(ctx, data, size & ~(ulong_t)0x3f); + size &= 0x3f; + } + + internal_memcpy(ctx->buffer, data, size); +} + +void MD5_Final(unsigned char *result, MD5_CTX *ctx) { + ulong_t used, free; + + used = ctx->lo & 0x3f; + + ctx->buffer[used++] = 0x80; + + free = 64 - used; + + if (free < 8) { + internal_memset(&ctx->buffer[used], 0, free); + body(ctx, ctx->buffer, 64); + used = 0; + free = 64; + } + + internal_memset(&ctx->buffer[used], 0, free - 8); + + ctx->lo <<= 3; + ctx->buffer[56] = ctx->lo; + ctx->buffer[57] = ctx->lo >> 8; + ctx->buffer[58] = ctx->lo >> 16; + ctx->buffer[59] = ctx->lo >> 24; + ctx->buffer[60] = ctx->hi; + ctx->buffer[61] = ctx->hi >> 8; + ctx->buffer[62] = ctx->hi >> 16; + ctx->buffer[63] = ctx->hi >> 24; + + body(ctx, ctx->buffer, 64); + + result[0] = ctx->a; + result[1] = ctx->a >> 8; + result[2] = ctx->a >> 16; + result[3] = ctx->a >> 24; + result[4] = ctx->b; + result[5] = ctx->b >> 8; + result[6] = ctx->b >> 16; + result[7] = ctx->b >> 24; + result[8] = ctx->c; + result[9] = ctx->c >> 8; + result[10] = ctx->c >> 16; + result[11] = ctx->c >> 24; + result[12] = ctx->d; + result[13] = ctx->d >> 8; + result[14] = ctx->d >> 16; + result[15] = ctx->d >> 24; + + internal_memset(ctx, 0, sizeof(*ctx)); +} + +MD5Hash md5_hash(const void *data, uptr size) { + MD5Hash res; + MD5_CTX ctx; + MD5_Init(&ctx); + MD5_Update(&ctx, (void*)data, size); + MD5_Final((unsigned char*)&res.hash[0], &ctx); + return res; +} +} diff --git a/lib/tsan/rtl/tsan_mman.cc b/lib/tsan/rtl/tsan_mman.cc new file mode 100644 index 0000000..7f956df --- /dev/null +++ b/lib/tsan/rtl/tsan_mman.cc @@ -0,0 +1,123 @@ +//===-- tsan_mman.cc ------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_common.h" +#include "tsan_mman.h" +#include "tsan_rtl.h" +#include "tsan_report.h" +#include "tsan_flags.h" + +namespace __tsan { + +static void SignalUnsafeCall(ThreadState *thr, uptr pc) { + if (!thr->in_signal_handler || !flags()->report_signal_unsafe) + return; + StackTrace stack; + stack.ObtainCurrent(thr, pc); + ScopedReport rep(ReportTypeSignalUnsafe); + rep.AddStack(&stack); + OutputReport(rep, rep.GetReport()->stacks[0]); +} + +void *user_alloc(ThreadState *thr, uptr pc, uptr sz) { + CHECK_GT(thr->in_rtl, 0); + if (sz + sizeof(MBlock) < sz) + return 0; + MBlock *b = (MBlock*)InternalAlloc(sz + sizeof(MBlock)); + if (b == 0) + return 0; + b->size = sz; + void *p = b + 1; + if (CTX() && CTX()->initialized) { + MemoryResetRange(thr, pc, (uptr)p, sz); + } + DPrintf("#%d: alloc(%zu) = %p\n", thr->tid, sz, p); + SignalUnsafeCall(thr, pc); + return p; +} + +void user_free(ThreadState *thr, uptr pc, void *p) { + CHECK_GT(thr->in_rtl, 0); + CHECK_NE(p, (void*)0); + DPrintf("#%d: free(%p)\n", thr->tid, p); + MBlock *b = user_mblock(thr, p); + p = b + 1; + if (CTX() && CTX()->initialized && thr->in_rtl == 1) { + MemoryRangeFreed(thr, pc, (uptr)p, b->size); + } + InternalFree(b); + SignalUnsafeCall(thr, pc); +} + +void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) { + CHECK_GT(thr->in_rtl, 0); + void *p2 = 0; + // FIXME: Handle "shrinking" more efficiently, + // it seems that some software actually does this. + if (sz) { + p2 = user_alloc(thr, pc, sz); + if (p2 == 0) + return 0; + if (p) { + MBlock *b = user_mblock(thr, p); + internal_memcpy(p2, p, min(b->size, sz)); + } + } + if (p) { + user_free(thr, pc, p); + } + return p2; +} + +void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align) { + CHECK_GT(thr->in_rtl, 0); + void *p = user_alloc(thr, pc, sz + align); + void *pa = RoundUp(p, align); + DCHECK_LE((uptr)pa + sz, (uptr)p + sz + align); + return pa; +} + +MBlock *user_mblock(ThreadState *thr, void *p) { + CHECK_GT(thr->in_rtl, 0); + CHECK_NE(p, (void*)0); + MBlock *b = (MBlock*)InternalAllocBlock(p); + // FIXME: Output a warning, it's a user error. + if (p < (char*)(b + 1) || p > (char*)(b + 1) + b->size) { + TsanPrintf("user_mblock p=%p b=%p size=%zu beg=%p end=%p\n", + p, b, b->size, (char*)(b + 1), (char*)(b + 1) + b->size); + CHECK_GE(p, (char*)(b + 1)); + CHECK_LE(p, (char*)(b + 1) + b->size); + } + return b; +} + +void *internal_alloc(MBlockType typ, uptr sz) { + ThreadState *thr = cur_thread(); + CHECK_GT(thr->in_rtl, 0); + if (thr->nomalloc) { + thr->nomalloc = 0; // CHECK calls internal_malloc(). + CHECK(0); + } + return InternalAlloc(sz); +} + +void internal_free(void *p) { + ThreadState *thr = cur_thread(); + CHECK_GT(thr->in_rtl, 0); + if (thr->nomalloc) { + thr->nomalloc = 0; // CHECK calls internal_malloc(). + CHECK(0); + } + InternalFree(p); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mman.h b/lib/tsan/rtl/tsan_mman.h new file mode 100644 index 0000000..53f147e --- /dev/null +++ b/lib/tsan/rtl/tsan_mman.h @@ -0,0 +1,114 @@ +//===-- tsan_mman.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_MMAN_H +#define TSAN_MMAN_H + +#include "tsan_defs.h" + +namespace __tsan { + +// Descriptor of user's memory block. +struct MBlock { + uptr size; +}; + +// For user allocations. +void *user_alloc(ThreadState *thr, uptr pc, uptr sz); +// Does not accept NULL. +void user_free(ThreadState *thr, uptr pc, void *p); +void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz); +void *user_alloc_aligned(ThreadState *thr, uptr pc, uptr sz, uptr align); +// Given the pointer p into a valid allocated block, +// returns the descriptor of the block. +MBlock *user_mblock(ThreadState *thr, void *p); + +enum MBlockType { + MBlockScopedBuf, + MBlockString, + MBlockStackTrace, + MBlockShadowStack, + MBlockSync, + MBlockClock, + MBlockThreadContex, + MBlockDeadInfo, + MBlockRacyStacks, + MBlockRacyAddresses, + MBlockAtExit, + MBlockFlag, + MBlockReport, + MBlockReportMop, + MBlockReportThread, + MBlockReportMutex, + MBlockReportLoc, + MBlockReportStack, + MBlockSuppression, + MBlockExpectRace, + MBlockSignal, + + // This must be the last. + MBlockTypeCount, +}; + +// For internal data structures. +void *internal_alloc(MBlockType typ, uptr sz); +void internal_free(void *p); + +template +void DestroyAndFree(T *&p) { + p->~T(); + internal_free(p); + p = 0; +} + +template +class InternalScopedBuf { + public: + explicit InternalScopedBuf(uptr cnt) { + cnt_ = cnt; + ptr_ = (T*)internal_alloc(MBlockScopedBuf, cnt * sizeof(T)); + } + + ~InternalScopedBuf() { + internal_free(ptr_); + } + + operator T *() { + return ptr_; + } + + T &operator[](uptr i) { + return ptr_[i]; + } + + T *Ptr() { + return ptr_; + } + + uptr Count() { + return cnt_; + } + + uptr Size() { + return cnt_ * sizeof(T); + } + + private: + T *ptr_; + uptr cnt_; + + InternalScopedBuf(const InternalScopedBuf&); + void operator = (const InternalScopedBuf&); +}; + +} // namespace __tsan +#endif // TSAN_MMAN_H diff --git a/lib/tsan/rtl/tsan_mutex.cc b/lib/tsan/rtl/tsan_mutex.cc new file mode 100644 index 0000000..1a70f8f --- /dev/null +++ b/lib/tsan/rtl/tsan_mutex.cc @@ -0,0 +1,259 @@ +//===-- tsan_mutex.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_mutex.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" + +namespace __tsan { + +// Simple reader-writer spin-mutex. Optimized for not-so-contended case. +// Readers have preference, can possibly starvate writers. + +// The table fixes what mutexes can be locked under what mutexes. +// E.g. if the row for MutexTypeThreads contains MutexTypeReport, +// then Report mutex can be locked while under Threads mutex. +// The leaf mutexes can be locked under any other mutexes. +// Recursive locking is not supported. +const MutexType MutexTypeLeaf = (MutexType)-1; +static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = { + /*0 MutexTypeInvalid*/ {}, + /*1 MutexTypeTrace*/ {MutexTypeLeaf}, + /*2 MutexTypeThreads*/ {MutexTypeReport}, + /*3 MutexTypeReport*/ {}, + /*4 MutexTypeSyncVar*/ {}, + /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, + /*6 MutexTypeSlab*/ {MutexTypeLeaf}, + /*7 MutexTypeAnnotations*/ {}, + /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, +}; + +static bool CanLockAdj[MutexTypeCount][MutexTypeCount]; + +void InitializeMutex() { + // Build the "can lock" adjacency matrix. + // If [i][j]==true, then one can lock mutex j while under mutex i. + const int N = MutexTypeCount; + int cnt[N] = {}; + bool leaf[N] = {}; + for (int i = 1; i < N; i++) { + for (int j = 0; j < N; j++) { + int z = CanLockTab[i][j]; + if (z == MutexTypeInvalid) + continue; + if (z == MutexTypeLeaf) { + CHECK(!leaf[i]); + leaf[i] = true; + continue; + } + CHECK(!CanLockAdj[i][z]); + CanLockAdj[i][z] = true; + cnt[i]++; + } + } + for (int i = 0; i < N; i++) { + CHECK(!leaf[i] || cnt[i] == 0); + } + // Add leaf mutexes. + for (int i = 0; i < N; i++) { + if (!leaf[i]) + continue; + for (int j = 0; j < N; j++) { + if (i == j || leaf[j] || j == MutexTypeInvalid) + continue; + CHECK(!CanLockAdj[j][i]); + CanLockAdj[j][i] = true; + } + } + // Build the transitive closure. + bool CanLockAdj2[MutexTypeCount][MutexTypeCount]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + CanLockAdj2[i][j] = CanLockAdj[i][j]; + } + } + for (int k = 0; k < N; k++) { + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + if (CanLockAdj2[i][k] && CanLockAdj2[k][j]) { + CanLockAdj2[i][j] = true; + } + } + } + } +#if 0 + TsanPrintf("Can lock graph:\n"); + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + TsanPrintf("%d ", CanLockAdj[i][j]); + } + TsanPrintf("\n"); + } + TsanPrintf("Can lock graph closure:\n"); + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + TsanPrintf("%d ", CanLockAdj2[i][j]); + } + TsanPrintf("\n"); + } +#endif + // Verify that the graph is acyclic. + for (int i = 0; i < N; i++) { + if (CanLockAdj2[i][i]) { + TsanPrintf("Mutex %d participates in a cycle\n", i); + Die(); + } + } +} + +DeadlockDetector::DeadlockDetector() { + // Rely on zero initialization because some mutexes can be locked before ctor. +} + +void DeadlockDetector::Lock(MutexType t) { + // TsanPrintf("LOCK %d @%zu\n", t, seq_ + 1); + u64 max_seq = 0; + u64 max_idx = MutexTypeInvalid; + for (int i = 0; i != MutexTypeCount; i++) { + if (locked_[i] == 0) + continue; + CHECK_NE(locked_[i], max_seq); + if (max_seq < locked_[i]) { + max_seq = locked_[i]; + max_idx = i; + } + } + locked_[t] = ++seq_; + if (max_idx == MutexTypeInvalid) + return; + // TsanPrintf(" last %d @%zu\n", max_idx, max_seq); + if (!CanLockAdj[max_idx][t]) { + TsanPrintf("ThreadSanitizer: internal deadlock detected\n"); + TsanPrintf("ThreadSanitizer: can't lock %d while under %zu\n", + t, (uptr)max_idx); + Die(); + } +} + +void DeadlockDetector::Unlock(MutexType t) { + // TsanPrintf("UNLO %d @%zu #%zu\n", t, seq_, locked_[t]); + CHECK(locked_[t]); + locked_[t] = 0; +} + +const uptr kUnlocked = 0; +const uptr kWriteLock = 1; +const uptr kReadLock = 2; + +class Backoff { + public: + Backoff() + : iter_() { + } + + bool Do() { + if (iter_++ < kActiveSpinIters) + proc_yield(kActiveSpinCnt); + else + internal_sched_yield(); + return true; + } + + u64 Contention() const { + u64 active = iter_ % kActiveSpinIters; + u64 passive = iter_ - active; + return active + 10 * passive; + } + + private: + int iter_; + static const int kActiveSpinIters = 10; + static const int kActiveSpinCnt = 20; +}; + +Mutex::Mutex(MutexType type, StatType stat_type) { + CHECK_GT(type, MutexTypeInvalid); + CHECK_LT(type, MutexTypeCount); +#if TSAN_DEBUG + type_ = type; +#endif +#if TSAN_COLLECT_STATS + stat_type_ = stat_type; +#endif + atomic_store(&state_, kUnlocked, memory_order_relaxed); +} + +Mutex::~Mutex() { + CHECK_EQ(atomic_load(&state_, memory_order_relaxed), kUnlocked); +} + +void Mutex::Lock() { +#if TSAN_DEBUG && !TSAN_GO + cur_thread()->deadlock_detector.Lock(type_); +#endif + uptr cmp = kUnlocked; + if (atomic_compare_exchange_strong(&state_, &cmp, kWriteLock, + memory_order_acquire)) + return; + for (Backoff backoff; backoff.Do();) { + if (atomic_load(&state_, memory_order_relaxed) == kUnlocked) { + cmp = kUnlocked; + if (atomic_compare_exchange_weak(&state_, &cmp, kWriteLock, + memory_order_acquire)) { +#if TSAN_COLLECT_STATS + StatInc(cur_thread(), stat_type_, backoff.Contention()); +#endif + return; + } + } + } +} + +void Mutex::Unlock() { + uptr prev = atomic_fetch_sub(&state_, kWriteLock, memory_order_release); + (void)prev; + DCHECK_NE(prev & kWriteLock, 0); +#if TSAN_DEBUG && !TSAN_GO + cur_thread()->deadlock_detector.Unlock(type_); +#endif +} + +void Mutex::ReadLock() { +#if TSAN_DEBUG && !TSAN_GO + cur_thread()->deadlock_detector.Lock(type_); +#endif + uptr prev = atomic_fetch_add(&state_, kReadLock, memory_order_acquire); + if ((prev & kWriteLock) == 0) + return; + for (Backoff backoff; backoff.Do();) { + prev = atomic_load(&state_, memory_order_acquire); + if ((prev & kWriteLock) == 0) { +#if TSAN_COLLECT_STATS + StatInc(cur_thread(), stat_type_, backoff.Contention()); +#endif + return; + } + } +} + +void Mutex::ReadUnlock() { + uptr prev = atomic_fetch_sub(&state_, kReadLock, memory_order_release); + (void)prev; + DCHECK_EQ(prev & kWriteLock, 0); + DCHECK_GT(prev & ~kWriteLock, 0); +#if TSAN_DEBUG && !TSAN_GO + cur_thread()->deadlock_detector.Unlock(type_); +#endif +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_mutex.h b/lib/tsan/rtl/tsan_mutex.h new file mode 100644 index 0000000..5b22a41 --- /dev/null +++ b/lib/tsan/rtl/tsan_mutex.h @@ -0,0 +1,78 @@ +//===-- tsan_mutex.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_MUTEX_H +#define TSAN_MUTEX_H + +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_mutex.h" +#include "tsan_defs.h" + +namespace __tsan { + +enum MutexType { + MutexTypeInvalid, + MutexTypeTrace, + MutexTypeThreads, + MutexTypeReport, + MutexTypeSyncVar, + MutexTypeSyncTab, + MutexTypeSlab, + MutexTypeAnnotations, + MutexTypeAtExit, + + // This must be the last. + MutexTypeCount, +}; + +class Mutex { + public: + explicit Mutex(MutexType type, StatType stat_type); + ~Mutex(); + + void Lock(); + void Unlock(); + + void ReadLock(); + void ReadUnlock(); + + private: + atomic_uintptr_t state_; +#if TSAN_DEBUG + MutexType type_; +#endif +#if TSAN_COLLECT_STATS + StatType stat_type_; +#endif + + Mutex(const Mutex&); + void operator = (const Mutex&); +}; + +typedef GenericScopedLock Lock; +typedef GenericScopedReadLock ReadLock; + +class DeadlockDetector { + public: + DeadlockDetector(); + void Lock(MutexType t); + void Unlock(MutexType t); + private: + u64 seq_; + u64 locked_[MutexTypeCount]; +}; + +void InitializeMutex(); + +} // namespace __tsan + +#endif // TSAN_MUTEX_H diff --git a/lib/tsan/rtl/tsan_platform.h b/lib/tsan/rtl/tsan_platform.h new file mode 100644 index 0000000..b557fa1 --- /dev/null +++ b/lib/tsan/rtl/tsan_platform.h @@ -0,0 +1,101 @@ +//===-- tsan_platform.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Platform-specific code. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_PLATFORM_H +#define TSAN_PLATFORM_H + +#include "tsan_rtl.h" + +#if __LP64__ +namespace __tsan { + +#if defined(TSAN_GO) +static const uptr kLinuxAppMemBeg = 0x000000000000ULL; +static const uptr kLinuxAppMemEnd = 0x00fcffffffffULL; +static const uptr kLinuxShadowMsk = 0x100000000000ULL; +// TSAN_COMPAT_SHADOW is intended for COMPAT virtual memory layout, +// when memory addresses are of the 0x2axxxxxxxxxx form. +// The option is enabled with 'setarch x86_64 -L'. +#elif defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW +static const uptr kLinuxAppMemBeg = 0x2a0000000000ULL; +static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; +#else +static const uptr kLinuxAppMemBeg = 0x7ef000000000ULL; +static const uptr kLinuxAppMemEnd = 0x7fffffffffffULL; +#endif + +static const uptr kLinuxAppMemMsk = 0x7c0000000000ULL; + +// This has to be a macro to allow constant initialization of constants below. +#ifndef TSAN_GO +#define MemToShadow(addr) \ + (((addr) & ~(kLinuxAppMemMsk | (kShadowCell - 1))) * kShadowCnt) +#else +#define MemToShadow(addr) \ + ((((addr) & ~(kShadowCell - 1)) * kShadowCnt) | kLinuxShadowMsk) +#endif + +static const uptr kLinuxShadowBeg = MemToShadow(kLinuxAppMemBeg); +static const uptr kLinuxShadowEnd = + MemToShadow(kLinuxAppMemEnd) | (kPageSize - 1); + +static inline bool IsAppMem(uptr mem) { + return mem >= kLinuxAppMemBeg && mem <= kLinuxAppMemEnd; +} + +static inline bool IsShadowMem(uptr mem) { + return mem >= kLinuxShadowBeg && mem <= kLinuxShadowEnd; +} + +static inline uptr ShadowToMem(uptr shadow) { + CHECK(IsShadowMem(shadow)); +#ifdef TSAN_GO + return (shadow & ~kLinuxShadowMsk) / kShadowCnt; +#elif defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW + // COMPAT mapping is not quite one-to-one. + return (shadow / kShadowCnt) | 0x280000000000ULL; +#else + return (shadow / kShadowCnt) | kLinuxAppMemMsk; +#endif +} + +// For COMPAT mapping returns an alternative address +// that mapped to the same shadow address. +static inline uptr AlternativeAddress(uptr addr) { +#if defined(TSAN_COMPAT_SHADOW) && TSAN_COMPAT_SHADOW + return addr | kLinuxAppMemMsk; +#else + return 0; +#endif +} + +uptr GetShadowMemoryConsumption(); +void FlushShadowMemory(); + +const char *InitializePlatform(); +void FinalizePlatform(); + +void internal_start_thread(void(*func)(void*), void *arg); + +uptr GetTlsSize(); +void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size); + +} // namespace __tsan + +#else // __LP64__ +# error "Only 64-bit is supported" +#endif + +#endif // TSAN_PLATFORM_H diff --git a/lib/tsan/rtl/tsan_platform_linux.cc b/lib/tsan/rtl/tsan_platform_linux.cc new file mode 100644 index 0000000..c791c96 --- /dev/null +++ b/lib/tsan/rtl/tsan_platform_linux.cc @@ -0,0 +1,238 @@ +//===-- tsan_platform_linux.cc --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Linux-specific code. +//===----------------------------------------------------------------------===// + +#ifdef __linux__ + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_procmaps.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_flags.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int arch_prctl(int code, __sanitizer::uptr *addr); + +namespace __sanitizer { + +void Die() { + _exit(1); +} + +} // namespace __sanitizer + +namespace __tsan { + +#ifndef TSAN_GO +ScopedInRtl::ScopedInRtl() + : thr_(cur_thread()) { + in_rtl_ = thr_->in_rtl; + thr_->in_rtl++; + errno_ = errno; +} + +ScopedInRtl::~ScopedInRtl() { + thr_->in_rtl--; + errno = errno_; + CHECK_EQ(in_rtl_, thr_->in_rtl); +} +#else +ScopedInRtl::ScopedInRtl() { +} + +ScopedInRtl::~ScopedInRtl() { +} +#endif + +uptr GetShadowMemoryConsumption() { + return 0; +} + +void FlushShadowMemory() { + madvise((void*)kLinuxShadowBeg, + kLinuxShadowEnd - kLinuxShadowBeg, + MADV_DONTNEED); +} + +#ifndef TSAN_GO +static void ProtectRange(uptr beg, uptr end) { + ScopedInRtl in_rtl; + CHECK_LE(beg, end); + if (beg == end) + return; + if (beg != (uptr)Mprotect(beg, end - beg)) { + TsanPrintf("FATAL: ThreadSanitizer can not protect [%zx,%zx]\n", beg, end); + TsanPrintf("FATAL: Make sure you are not using unlimited stack\n"); + Die(); + } +} +#endif + +void InitializeShadowMemory() { + uptr shadow = (uptr)MmapFixedNoReserve(kLinuxShadowBeg, + kLinuxShadowEnd - kLinuxShadowBeg); + if (shadow != kLinuxShadowBeg) { + TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); + TsanPrintf("FATAL: Make sure to compile with -fPIE and " + "to link with -pie.\n"); + Die(); + } +#ifndef TSAN_GO + const uptr kClosedLowBeg = 0x200000; + const uptr kClosedLowEnd = kLinuxShadowBeg - 1; + const uptr kClosedMidBeg = kLinuxShadowEnd + 1; + const uptr kClosedMidEnd = kLinuxAppMemBeg - 1; + ProtectRange(kClosedLowBeg, kClosedLowEnd); + ProtectRange(kClosedMidBeg, kClosedMidEnd); +#endif +#ifndef TSAN_GO + DPrintf("kClosedLow %zx-%zx (%zuGB)\n", + kClosedLowBeg, kClosedLowEnd, (kClosedLowEnd - kClosedLowBeg) >> 30); +#endif + DPrintf("kLinuxShadow %zx-%zx (%zuGB)\n", + kLinuxShadowBeg, kLinuxShadowEnd, + (kLinuxShadowEnd - kLinuxShadowBeg) >> 30); +#ifndef TSAN_GO + DPrintf("kClosedMid %zx-%zx (%zuGB)\n", + kClosedMidBeg, kClosedMidEnd, (kClosedMidEnd - kClosedMidBeg) >> 30); +#endif + DPrintf("kLinuxAppMem %zx-%zx (%zuGB)\n", + kLinuxAppMemBeg, kLinuxAppMemEnd, + (kLinuxAppMemEnd - kLinuxAppMemBeg) >> 30); + DPrintf("stack %zx\n", (uptr)&shadow); +} + +#ifndef TSAN_GO +static void CheckPIE() { + // Ensure that the binary is indeed compiled with -pie. + ProcessMaps proc_maps; + uptr start, end; + if (proc_maps.Next(&start, &end, + /*offset*/0, /*filename*/0, /*filename_size*/0)) { + if ((u64)start < kLinuxAppMemBeg) { + TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory (" + "something is mapped at 0x%zx < 0x%zx)\n", + start, kLinuxAppMemBeg); + TsanPrintf("FATAL: Make sure to compile with -fPIE" + " and to link with -pie.\n"); + Die(); + } + } +} + +static uptr g_tls_size; + +#ifdef __i386__ +# define INTERNAL_FUNCTION __attribute__((regparm(3), stdcall)) +#else +# define INTERNAL_FUNCTION +#endif +extern "C" void _dl_get_tls_static_info(size_t*, size_t*) + __attribute__((weak)) INTERNAL_FUNCTION; + +static int InitTlsSize() { + typedef void (*get_tls_func)(size_t*, size_t*) INTERNAL_FUNCTION; + get_tls_func get_tls = &_dl_get_tls_static_info; + if (get_tls == 0) + get_tls = (get_tls_func)dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); + CHECK_NE(get_tls, 0); + size_t tls_size = 0; + size_t tls_align = 0; + get_tls(&tls_size, &tls_align); + return tls_size; +} +#endif // #ifndef TSAN_GO + +const char *InitializePlatform() { + void *p = 0; + if (sizeof(p) == 8) { + // Disable core dumps, dumping of 16TB usually takes a bit long. + // The following magic is to prevent clang from replacing it with memset. + volatile rlimit lim; + lim.rlim_cur = 0; + lim.rlim_max = 0; + setrlimit(RLIMIT_CORE, (rlimit*)&lim); + } + +#ifndef TSAN_GO + CheckPIE(); + g_tls_size = (uptr)InitTlsSize(); +#endif + return getenv("TSAN_OPTIONS"); +} + +void FinalizePlatform() { + fflush(0); +} + +uptr GetTlsSize() { +#ifndef TSAN_GO + return g_tls_size; +#else + return 0; +#endif +} + +void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size) { +#ifndef TSAN_GO + arch_prctl(ARCH_GET_FS, tls_addr); + *tls_addr -= g_tls_size; + *tls_size = g_tls_size; + + uptr stack_top, stack_bottom; + GetThreadStackTopAndBottom(main, &stack_top, &stack_bottom); + *stk_addr = stack_bottom; + *stk_size = stack_top - stack_bottom; + + if (!main) { + // If stack and tls intersect, make them non-intersecting. + if (*tls_addr > *stk_addr && *tls_addr < *stk_addr + *stk_size) { + CHECK_GT(*tls_addr + *tls_size, *stk_addr); + CHECK_LE(*tls_addr + *tls_size, *stk_addr + *stk_size); + *stk_size -= *tls_size; + *tls_addr = *stk_addr + *stk_size; + } + } +#else + *stk_addr = 0; + *stk_size = 0; + *tls_addr = 0; + *tls_size = 0; +#endif +} + + +} // namespace __tsan + +#endif // #ifdef __linux__ diff --git a/lib/tsan/rtl/tsan_platform_mac.cc b/lib/tsan/rtl/tsan_platform_mac.cc new file mode 100644 index 0000000..7451492 --- /dev/null +++ b/lib/tsan/rtl/tsan_platform_mac.cc @@ -0,0 +1,112 @@ +//===-- tsan_platform_mac.cc ----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Mac-specific code. +//===----------------------------------------------------------------------===// + +#ifdef __APPLE__ + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_procmaps.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_flags.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace __sanitizer { + +void Die() { + _exit(1); +} + +} // namespace __sanitizer + +namespace __tsan { + +ScopedInRtl::ScopedInRtl() { +} + +ScopedInRtl::~ScopedInRtl() { +} + +uptr GetShadowMemoryConsumption() { + return 0; +} + +void FlushShadowMemory() { +} + +void InitializeShadowMemory() { + uptr shadow = (uptr)MmapFixedNoReserve(kLinuxShadowBeg, + kLinuxShadowEnd - kLinuxShadowBeg); + if (shadow != kLinuxShadowBeg) { + TsanPrintf("FATAL: ThreadSanitizer can not mmap the shadow memory\n"); + TsanPrintf("FATAL: Make sure to compile with -fPIE and " + "to link with -pie.\n"); + Die(); + } + DPrintf("kLinuxShadow %zx-%zx (%zuGB)\n", + kLinuxShadowBeg, kLinuxShadowEnd, + (kLinuxShadowEnd - kLinuxShadowBeg) >> 30); + DPrintf("kLinuxAppMem %zx-%zx (%zuGB)\n", + kLinuxAppMemBeg, kLinuxAppMemEnd, + (kLinuxAppMemEnd - kLinuxAppMemBeg) >> 30); +} + +const char *InitializePlatform() { + void *p = 0; + if (sizeof(p) == 8) { + // Disable core dumps, dumping of 16TB usually takes a bit long. + // The following magic is to prevent clang from replacing it with memset. + volatile rlimit lim; + lim.rlim_cur = 0; + lim.rlim_max = 0; + setrlimit(RLIMIT_CORE, (rlimit*)&lim); + } + + return getenv("TSAN_OPTIONS"); +} + +void FinalizePlatform() { + fflush(0); +} + +uptr GetTlsSize() { + return 0; +} + +void GetThreadStackAndTls(bool main, uptr *stk_addr, uptr *stk_size, + uptr *tls_addr, uptr *tls_size) { + *stk_addr = 0; + *stk_size = 0; + *tls_addr = 0; + *tls_size = 0; +} + +} // namespace __tsan + +#endif // #ifdef __APPLE__ diff --git a/lib/tsan/rtl/tsan_printf.cc b/lib/tsan/rtl/tsan_printf.cc new file mode 100644 index 0000000..6f41440 --- /dev/null +++ b/lib/tsan/rtl/tsan_printf.cc @@ -0,0 +1,39 @@ +//===-- tsan_printf.cc ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_defs.h" +#include "tsan_mman.h" +#include "tsan_platform.h" + +#include // va_list + +namespace __sanitizer { +int VSNPrintf(char *buff, int buff_length, const char *format, va_list args); +} // namespace __sanitizer + +namespace __tsan { + +void TsanPrintf(const char *format, ...) { + ScopedInRtl in_rtl; + const uptr kMaxLen = 16 * 1024; + InternalScopedBuf buffer(kMaxLen); + va_list args; + va_start(args, format); + uptr len = VSNPrintf(buffer, buffer.Size(), format, args); + va_end(args); + internal_write(CTX() ? flags()->log_fileno : 2, + buffer, len < buffer.Size() ? len : buffer.Size() - 1); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_report.cc b/lib/tsan/rtl/tsan_report.cc new file mode 100644 index 0000000..c841a98 --- /dev/null +++ b/lib/tsan/rtl/tsan_report.cc @@ -0,0 +1,167 @@ +//===-- tsan_report.cc ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_report.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" + +namespace __tsan { + +ReportDesc::ReportDesc() + : stacks(MBlockReportStack) + , mops(MBlockReportMop) + , locs(MBlockReportLoc) + , mutexes(MBlockReportMutex) + , threads(MBlockReportThread) { +} + +ReportDesc::~ReportDesc() { +} + +#ifndef TSAN_GO + +static void PrintHeader(ReportType typ) { + TsanPrintf("WARNING: ThreadSanitizer: "); + + if (typ == ReportTypeRace) + TsanPrintf("data race"); + else if (typ == ReportTypeUseAfterFree) + TsanPrintf("heap-use-after-free"); + else if (typ == ReportTypeThreadLeak) + TsanPrintf("thread leak"); + else if (typ == ReportTypeMutexDestroyLocked) + TsanPrintf("destroy of a locked mutex"); + else if (typ == ReportTypeSignalUnsafe) + TsanPrintf("signal-unsafe call inside of a signal"); + else if (typ == ReportTypeErrnoInSignal) + TsanPrintf("signal handler spoils errno"); + + TsanPrintf(" (pid=%d)\n", GetPid()); +} + +static void PrintStack(const ReportStack *ent) { + for (int i = 0; ent; ent = ent->next, i++) { + TsanPrintf(" #%d %s %s:%d", i, ent->func, ent->file, ent->line); + if (ent->col) + TsanPrintf(":%d", ent->col); + if (ent->module && ent->offset) + TsanPrintf(" (%s+%p)\n", ent->module, (void*)ent->offset); + else + TsanPrintf(" (%p)\n", (void*)ent->pc); + } +} + +static void PrintMop(const ReportMop *mop, bool first) { + TsanPrintf(" %s of size %d at %p", + (first ? (mop->write ? "Write" : "Read") + : (mop->write ? "Previous write" : "Previous read")), + mop->size, (void*)mop->addr); + if (mop->tid == 0) + TsanPrintf(" by main thread:\n"); + else + TsanPrintf(" by thread %d:\n", mop->tid); + PrintStack(mop->stack); +} + +static void PrintLocation(const ReportLocation *loc) { + if (loc->type == ReportLocationGlobal) { + TsanPrintf(" Location is global '%s' of size %zu at %zx %s:%d\n", + loc->name, loc->size, loc->addr, loc->file, loc->line); + } else if (loc->type == ReportLocationHeap) { + TsanPrintf(" Location is heap of size %zu at %zx allocated " + "by thread %d:\n", loc->size, loc->addr, loc->tid); + PrintStack(loc->stack); + } else if (loc->type == ReportLocationStack) { + TsanPrintf(" Location is stack of thread %d:\n", loc->tid); + } +} + +static void PrintMutex(const ReportMutex *rm) { + if (rm->stack == 0) + return; + TsanPrintf(" Mutex %d created at:\n", rm->id); + PrintStack(rm->stack); +} + +static void PrintThread(const ReportThread *rt) { + if (rt->id == 0) // Little sense in describing the main thread. + return; + TsanPrintf(" Thread %d", rt->id); + if (rt->name) + TsanPrintf(" '%s'", rt->name); + TsanPrintf(" (%s)", rt->running ? "running" : "finished"); + if (rt->stack) + TsanPrintf(" created at:"); + TsanPrintf("\n"); + PrintStack(rt->stack); +} + +void PrintReport(const ReportDesc *rep) { + TsanPrintf("==================\n"); + PrintHeader(rep->typ); + + for (uptr i = 0; i < rep->stacks.Size(); i++) + PrintStack(rep->stacks[i]); + + for (uptr i = 0; i < rep->mops.Size(); i++) + PrintMop(rep->mops[i], i == 0); + + for (uptr i = 0; i < rep->locs.Size(); i++) + PrintLocation(rep->locs[i]); + + for (uptr i = 0; i < rep->mutexes.Size(); i++) + PrintMutex(rep->mutexes[i]); + + for (uptr i = 0; i < rep->threads.Size(); i++) + PrintThread(rep->threads[i]); + + TsanPrintf("==================\n"); +} + +#else + +static void PrintStack(const ReportStack *ent) { + for (int i = 0; ent; ent = ent->next, i++) { + TsanPrintf(" %s()\n %s:%d +0x%zx\n", + ent->func, ent->file, ent->line, (void*)ent->offset); + } +} + +static void PrintMop(const ReportMop *mop, bool first) { + TsanPrintf("%s by goroutine %d:\n", + (first ? (mop->write ? "Write" : "Read") + : (mop->write ? "Previous write" : "Previous read")), + mop->tid); + PrintStack(mop->stack); +} + +static void PrintThread(const ReportThread *rt) { + if (rt->id == 0) // Little sense in describing the main thread. + return; + TsanPrintf("Goroutine %d (%s) created at:\n", + rt->id, rt->running ? "running" : "finished"); + PrintStack(rt->stack); +} + +void PrintReport(const ReportDesc *rep) { + TsanPrintf("==================\n"); + TsanPrintf("WARNING: DATA RACE at %p\n", (void*)rep->mops[0]->addr); + for (uptr i = 0; i < rep->mops.Size(); i++) + PrintMop(rep->mops[i], i == 0); + for (uptr i = 0; i < rep->threads.Size(); i++) + PrintThread(rep->threads[i]); + TsanPrintf("==================\n"); +} + +#endif + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_report.h b/lib/tsan/rtl/tsan_report.h new file mode 100644 index 0000000..d139296 --- /dev/null +++ b/lib/tsan/rtl/tsan_report.h @@ -0,0 +1,102 @@ +//===-- tsan_report.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_REPORT_H +#define TSAN_REPORT_H + +#include "tsan_defs.h" +#include "tsan_vector.h" + +namespace __tsan { + +enum ReportType { + ReportTypeRace, + ReportTypeUseAfterFree, + ReportTypeThreadLeak, + ReportTypeMutexDestroyLocked, + ReportTypeSignalUnsafe, + ReportTypeErrnoInSignal, +}; + +struct ReportStack { + ReportStack *next; + char *module; + uptr offset; + uptr pc; + char *func; + char *file; + int line; + int col; +}; + +struct ReportMop { + int tid; + uptr addr; + int size; + bool write; + int nmutex; + int *mutex; + ReportStack *stack; +}; + +enum ReportLocationType { + ReportLocationGlobal, + ReportLocationHeap, + ReportLocationStack, +}; + +struct ReportLocation { + ReportLocationType type; + uptr addr; + uptr size; + int tid; + char *name; + char *file; + int line; + ReportStack *stack; +}; + +struct ReportThread { + int id; + bool running; + char *name; + ReportStack *stack; +}; + +struct ReportMutex { + int id; + ReportStack *stack; +}; + +class ReportDesc { + public: + ReportType typ; + Vector stacks; + Vector mops; + Vector locs; + Vector mutexes; + Vector threads; + + ReportDesc(); + ~ReportDesc(); + + private: + ReportDesc(const ReportDesc&); + void operator = (const ReportDesc&); +}; + +// Format and output the report to the console/log. No additional logic. +void PrintReport(const ReportDesc *rep); + +} // namespace __tsan + +#endif // TSAN_REPORT_H diff --git a/lib/tsan/rtl/tsan_rtl.cc b/lib/tsan/rtl/tsan_rtl.cc new file mode 100644 index 0000000..0ceb26c --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl.cc @@ -0,0 +1,534 @@ +//===-- tsan_rtl.cc -------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Main file (entry points) for the TSan run-time. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_defs.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "tsan_suppressions.h" + +volatile int __tsan_resumed = 0; + +extern "C" void __tsan_resume() { + __tsan_resumed = 1; +} + +namespace __tsan { + +#ifndef TSAN_GO +THREADLOCAL char cur_thread_placeholder[sizeof(ThreadState)] ALIGNED(64); +#endif +static char ctx_placeholder[sizeof(Context)] ALIGNED(64); + +static Context *ctx; +Context *CTX() { + return ctx; +} + +Context::Context() + : initialized() + , report_mtx(MutexTypeReport, StatMtxReport) + , nreported() + , nmissed_expected() + , thread_mtx(MutexTypeThreads, StatMtxThreads) + , racy_stacks(MBlockRacyStacks) + , racy_addresses(MBlockRacyAddresses) { +} + +// The objects are allocated in TLS, so one may rely on zero-initialization. +ThreadState::ThreadState(Context *ctx, int tid, u64 epoch, + uptr stk_addr, uptr stk_size, + uptr tls_addr, uptr tls_size) + : fast_state(tid, epoch) + // Do not touch these, rely on zero initialization, + // they may be accessed before the ctor. + // , fast_ignore_reads() + // , fast_ignore_writes() + // , in_rtl() + , shadow_stack_pos(&shadow_stack[0]) + , tid(tid) + , stk_addr(stk_addr) + , stk_size(stk_size) + , tls_addr(tls_addr) + , tls_size(tls_size) { +} + +ThreadContext::ThreadContext(int tid) + : tid(tid) + , unique_id() + , user_id() + , thr() + , status(ThreadStatusInvalid) + , detached() + , reuse_count() + , epoch0() + , epoch1() + , dead_info() + , dead_next() { +} + +static void WriteMemoryProfile(char *buf, uptr buf_size, int num) { + uptr shadow = GetShadowMemoryConsumption(); + + int nthread = 0; + int nlivethread = 0; + uptr threadmem = 0; + { + Lock l(&ctx->thread_mtx); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + nthread += 1; + threadmem += sizeof(ThreadContext); + if (tctx->status != ThreadStatusRunning) + continue; + nlivethread += 1; + threadmem += sizeof(ThreadState); + } + } + + uptr nsync = 0; + uptr syncmem = CTX()->synctab.GetMemoryConsumption(&nsync); + + internal_snprintf(buf, buf_size, "%d: shadow=%zuMB" + " thread=%zuMB(total=%d/live=%d)" + " sync=%zuMB(cnt=%zu)\n", + num, + shadow >> 20, + threadmem >> 20, nthread, nlivethread, + syncmem >> 20, nsync); +} + +static void MemoryProfileThread(void *arg) { + ScopedInRtl in_rtl; + fd_t fd = (fd_t)(uptr)arg; + for (int i = 0; ; i++) { + InternalScopedBuf buf(4096); + WriteMemoryProfile(buf.Ptr(), buf.Size(), i); + internal_write(fd, buf.Ptr(), internal_strlen(buf.Ptr())); + SleepForSeconds(1); + } +} + +static void InitializeMemoryProfile() { + if (flags()->profile_memory == 0 || flags()->profile_memory[0] == 0) + return; + InternalScopedBuf filename(4096); + internal_snprintf(filename.Ptr(), filename.Size(), "%s.%d", + flags()->profile_memory, GetPid()); + fd_t fd = internal_open(filename.Ptr(), true); + if (fd == kInvalidFd) { + TsanPrintf("Failed to open memory profile file '%s'\n", &filename[0]); + Die(); + } + internal_start_thread(&MemoryProfileThread, (void*)(uptr)fd); +} + +static void MemoryFlushThread(void *arg) { + ScopedInRtl in_rtl; + for (int i = 0; ; i++) { + SleepForMillis(flags()->flush_memory_ms); + FlushShadowMemory(); + } +} + +static void InitializeMemoryFlush() { + if (flags()->flush_memory_ms == 0) + return; + if (flags()->flush_memory_ms < 100) + flags()->flush_memory_ms = 100; + internal_start_thread(&MemoryFlushThread, 0); +} + +void Initialize(ThreadState *thr) { + // Thread safe because done before all threads exist. + static bool is_initialized = false; + if (is_initialized) + return; + is_initialized = true; + ScopedInRtl in_rtl; + InitializeInterceptors(); + const char *env = InitializePlatform(); + InitializeMutex(); + InitializeDynamicAnnotations(); + ctx = new(ctx_placeholder) Context; + InitializeShadowMemory(); + ctx->dead_list_size = 0; + ctx->dead_list_head = 0; + ctx->dead_list_tail = 0; + InitializeFlags(&ctx->flags, env); + InitializeSuppressions(); + InitializeMemoryProfile(); + InitializeMemoryFlush(); + + if (ctx->flags.verbosity) + TsanPrintf("***** Running under ThreadSanitizer v2 (pid %d) *****\n", + GetPid()); + + // Initialize thread 0. + ctx->thread_seq = 0; + int tid = ThreadCreate(thr, 0, 0, true); + CHECK_EQ(tid, 0); + ThreadStart(thr, tid); + CHECK_EQ(thr->in_rtl, 1); + ctx->initialized = true; + + if (flags()->stop_on_start) { + TsanPrintf("ThreadSanitizer is suspended at startup (pid %d)." + " Call __tsan_resume().\n", + GetPid()); + while (__tsan_resumed == 0); + } +} + +int Finalize(ThreadState *thr) { + ScopedInRtl in_rtl; + Context *ctx = __tsan::ctx; + bool failed = false; + + ThreadFinalize(thr); + + if (ctx->nreported) { + failed = true; + TsanPrintf("ThreadSanitizer: reported %d warnings\n", ctx->nreported); + } + + if (ctx->nmissed_expected) { + failed = true; + TsanPrintf("ThreadSanitizer: missed %d expected races\n", + ctx->nmissed_expected); + } + + StatOutput(ctx->stat); + return failed ? flags()->exitcode : 0; +} + +void TraceSwitch(ThreadState *thr) { + thr->nomalloc++; + ScopedInRtl in_rtl; + Lock l(&thr->trace.mtx); + unsigned trace = (thr->fast_state.epoch() / kTracePartSize) % kTraceParts; + TraceHeader *hdr = &thr->trace.headers[trace]; + hdr->epoch0 = thr->fast_state.epoch(); + hdr->stack0.ObtainCurrent(thr, 0); + thr->nomalloc--; +} + +#ifndef TSAN_GO +extern "C" void __tsan_trace_switch() { + TraceSwitch(cur_thread()); +} + +extern "C" void __tsan_report_race() { + ReportRace(cur_thread()); +} +#endif + +ALWAYS_INLINE +static Shadow LoadShadow(u64 *p) { + u64 raw = atomic_load((atomic_uint64_t*)p, memory_order_relaxed); + return Shadow(raw); +} + +ALWAYS_INLINE +static void StoreShadow(u64 *sp, u64 s) { + atomic_store((atomic_uint64_t*)sp, s, memory_order_relaxed); +} + +ALWAYS_INLINE +static void StoreIfNotYetStored(u64 *sp, u64 *s) { + StoreShadow(sp, *s); + *s = 0; +} + +static inline void HandleRace(ThreadState *thr, u64 *shadow_mem, + Shadow cur, Shadow old) { + thr->racy_state[0] = cur.raw(); + thr->racy_state[1] = old.raw(); + thr->racy_shadow_addr = shadow_mem; +#ifndef TSAN_GO + HACKY_CALL(__tsan_report_race); +#else + ReportRace(thr); +#endif +} + +static inline bool BothReads(Shadow s, int kAccessIsWrite) { + return !kAccessIsWrite && !s.is_write(); +} + +static inline bool OldIsRWStronger(Shadow old, int kAccessIsWrite) { + return old.is_write() || !kAccessIsWrite; +} + +static inline bool OldIsRWWeaker(Shadow old, int kAccessIsWrite) { + return !old.is_write() || kAccessIsWrite; +} + +static inline bool OldIsInSameSynchEpoch(Shadow old, ThreadState *thr) { + return old.epoch() >= thr->fast_synch_epoch; +} + +static inline bool HappensBefore(Shadow old, ThreadState *thr) { + return thr->clock.get(old.tid()) >= old.epoch(); +} + +ALWAYS_INLINE +void MemoryAccessImpl(ThreadState *thr, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + u64 *shadow_mem, Shadow cur) { + StatInc(thr, StatMop); + StatInc(thr, kAccessIsWrite ? StatMopWrite : StatMopRead); + StatInc(thr, (StatType)(StatMop1 + kAccessSizeLog)); + + // This potentially can live in an MMX/SSE scratch register. + // The required intrinsics are: + // __m128i _mm_move_epi64(__m128i*); + // _mm_storel_epi64(u64*, __m128i); + u64 store_word = cur.raw(); + + // scan all the shadow values and dispatch to 4 categories: + // same, replace, candidate and race (see comments below). + // we consider only 3 cases regarding access sizes: + // equal, intersect and not intersect. initially I considered + // larger and smaller as well, it allowed to replace some + // 'candidates' with 'same' or 'replace', but I think + // it's just not worth it (performance- and complexity-wise). + + Shadow old(0); + if (kShadowCnt == 1) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 2) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 4) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + idx = 2; +#include "tsan_update_shadow_word_inl.h" + idx = 3; +#include "tsan_update_shadow_word_inl.h" + } else if (kShadowCnt == 8) { + int idx = 0; +#include "tsan_update_shadow_word_inl.h" + idx = 1; +#include "tsan_update_shadow_word_inl.h" + idx = 2; +#include "tsan_update_shadow_word_inl.h" + idx = 3; +#include "tsan_update_shadow_word_inl.h" + idx = 4; +#include "tsan_update_shadow_word_inl.h" + idx = 5; +#include "tsan_update_shadow_word_inl.h" + idx = 6; +#include "tsan_update_shadow_word_inl.h" + idx = 7; +#include "tsan_update_shadow_word_inl.h" + } else { + CHECK(false); + } + + // we did not find any races and had already stored + // the current access info, so we are done + if (LIKELY(store_word == 0)) + return; + // choose a random candidate slot and replace it + StoreShadow(shadow_mem + (cur.epoch() % kShadowCnt), store_word); + StatInc(thr, StatShadowReplace); + return; + RACE: + HandleRace(thr, shadow_mem, cur, old); + return; +} + +ALWAYS_INLINE +void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite) { + u64 *shadow_mem = (u64*)MemToShadow(addr); + DPrintf2("#%d: tsan::OnMemoryAccess: @%p %p size=%d" + " is_write=%d shadow_mem=%p {%zx, %zx, %zx, %zx}\n", + (int)thr->fast_state.tid(), (void*)pc, (void*)addr, + (int)(1 << kAccessSizeLog), kAccessIsWrite, shadow_mem, + (uptr)shadow_mem[0], (uptr)shadow_mem[1], + (uptr)shadow_mem[2], (uptr)shadow_mem[3]); +#if TSAN_DEBUG + if (!IsAppMem(addr)) { + TsanPrintf("Access to non app mem %zx\n", addr); + DCHECK(IsAppMem(addr)); + } + if (!IsShadowMem((uptr)shadow_mem)) { + TsanPrintf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); + DCHECK(IsShadowMem((uptr)shadow_mem)); + } +#endif + + FastState fast_state = thr->fast_state; + if (fast_state.GetIgnoreBit()) + return; + fast_state.IncrementEpoch(); + thr->fast_state = fast_state; + Shadow cur(fast_state); + cur.SetAddr0AndSizeLog(addr & 7, kAccessSizeLog); + cur.SetWrite(kAccessIsWrite); + + // We must not store to the trace if we do not store to the shadow. + // That is, this call must be moved somewhere below. + TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + + MemoryAccessImpl(thr, addr, kAccessSizeLog, kAccessIsWrite, fast_state, + shadow_mem, cur); +} + +static void MemoryRangeSet(ThreadState *thr, uptr pc, uptr addr, uptr size, + u64 val) { + if (size == 0) + return; + // FIXME: fix me. + uptr offset = addr % kShadowCell; + if (offset) { + offset = kShadowCell - offset; + if (size <= offset) + return; + addr += offset; + size -= offset; + } + CHECK_EQ(addr % 8, 0); + CHECK(IsAppMem(addr)); + CHECK(IsAppMem(addr + size - 1)); + (void)thr; + (void)pc; + // Some programs mmap like hundreds of GBs but actually used a small part. + // So, it's better to report a false positive on the memory + // then to hang here senselessly. + const uptr kMaxResetSize = 1024*1024*1024; + if (size > kMaxResetSize) + size = kMaxResetSize; + size = (size + 7) & ~7; + u64 *p = (u64*)MemToShadow(addr); + CHECK(IsShadowMem((uptr)p)); + CHECK(IsShadowMem((uptr)(p + size * kShadowCnt / kShadowCell - 1))); + // FIXME: may overwrite a part outside the region + for (uptr i = 0; i < size * kShadowCnt / kShadowCell; i++) + p[i] = val; +} + +void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size) { + MemoryRangeSet(thr, pc, addr, size, 0); +} + +void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size) { + MemoryAccessRange(thr, pc, addr, size, true); + Shadow s(thr->fast_state); + s.MarkAsFreed(); + s.SetWrite(true); + s.SetAddr0AndSizeLog(0, 3); + MemoryRangeSet(thr, pc, addr, size, s.raw()); +} + +void FuncEntry(ThreadState *thr, uptr pc) { + DCHECK_EQ(thr->in_rtl, 0); + StatInc(thr, StatFuncEnter); + DPrintf2("#%d: FuncEntry %p\n", (int)thr->fast_state.tid(), (void*)pc); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncEnter, pc); + + // Shadow stack maintenance can be replaced with + // stack unwinding during trace switch (which presumably must be faster). + DCHECK_GE(thr->shadow_stack_pos, &thr->shadow_stack[0]); +#ifndef TSAN_GO + DCHECK_LT(thr->shadow_stack_pos, &thr->shadow_stack[kShadowStackSize]); +#else + if (thr->shadow_stack_pos == thr->shadow_stack_end) { + const int sz = thr->shadow_stack_end - thr->shadow_stack; + const int newsz = 2 * sz; + uptr *newstack = (uptr*)internal_alloc(MBlockShadowStack, + newsz * sizeof(uptr)); + internal_memcpy(newstack, thr->shadow_stack, sz * sizeof(uptr)); + internal_free(thr->shadow_stack); + thr->shadow_stack = newstack; + thr->shadow_stack_pos = newstack + sz; + thr->shadow_stack_end = newstack + newsz; + } +#endif + thr->shadow_stack_pos[0] = pc; + thr->shadow_stack_pos++; +} + +void FuncExit(ThreadState *thr) { + DCHECK_EQ(thr->in_rtl, 0); + StatInc(thr, StatFuncExit); + DPrintf2("#%d: FuncExit\n", (int)thr->fast_state.tid()); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeFuncExit, 0); + + DCHECK_GT(thr->shadow_stack_pos, &thr->shadow_stack[0]); +#ifndef TSAN_GO + DCHECK_LT(thr->shadow_stack_pos, &thr->shadow_stack[kShadowStackSize]); +#endif + thr->shadow_stack_pos--; +} + +void IgnoreCtl(ThreadState *thr, bool write, bool begin) { + DPrintf("#%d: IgnoreCtl(%d, %d)\n", thr->tid, write, begin); + thr->ignore_reads_and_writes += begin ? 1 : -1; + CHECK_GE(thr->ignore_reads_and_writes, 0); + if (thr->ignore_reads_and_writes) + thr->fast_state.SetIgnoreBit(); + else + thr->fast_state.ClearIgnoreBit(); +} + +bool MD5Hash::operator==(const MD5Hash &other) const { + return hash[0] == other.hash[0] && hash[1] == other.hash[1]; +} + +#if TSAN_DEBUG +void build_consistency_debug() {} +#else +void build_consistency_release() {} +#endif + +#if TSAN_COLLECT_STATS +void build_consistency_stats() {} +#else +void build_consistency_nostats() {} +#endif + +#if TSAN_SHADOW_COUNT == 1 +void build_consistency_shadow1() {} +#elif TSAN_SHADOW_COUNT == 2 +void build_consistency_shadow2() {} +#elif TSAN_SHADOW_COUNT == 4 +void build_consistency_shadow4() {} +#else +void build_consistency_shadow8() {} +#endif + +} // namespace __tsan + +#ifndef TSAN_GO +// Must be included in this file to make sure everything is inlined. +#include "tsan_interface_inl.h" +#endif diff --git a/lib/tsan/rtl/tsan_rtl.h b/lib/tsan/rtl/tsan_rtl.h new file mode 100644 index 0000000..c559cb2 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl.h @@ -0,0 +1,491 @@ +//===-- tsan_rtl.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Main internal TSan header file. +// +// Ground rules: +// - C++ run-time should not be used (static CTORs, RTTI, exceptions, static +// function-scope locals) +// - All functions/classes/etc reside in namespace __tsan, except for those +// declared in tsan_interface.h. +// - Platform-specific files should be used instead of ifdefs (*). +// - No system headers included in header files (*). +// - Platform specific headres included only into platform-specific files (*). +// +// (*) Except when inlining is critical for performance. +//===----------------------------------------------------------------------===// + +#ifndef TSAN_RTL_H +#define TSAN_RTL_H + +#include "sanitizer_common/sanitizer_common.h" +#include "tsan_clock.h" +#include "tsan_defs.h" +#include "tsan_flags.h" +#include "tsan_sync.h" +#include "tsan_trace.h" +#include "tsan_vector.h" +#include "tsan_report.h" + +namespace __tsan { + +void TsanPrintf(const char *format, ...); + +// FastState (from most significant bit): +// unused : 1 +// tid : kTidBits +// epoch : kClkBits +// unused : - +// ignore_bit : 1 +class FastState { + public: + FastState(u64 tid, u64 epoch) { + x_ = tid << kTidShift; + x_ |= epoch << kClkShift; + DCHECK(tid == this->tid()); + DCHECK(epoch == this->epoch()); + } + + explicit FastState(u64 x) + : x_(x) { + } + + u64 tid() const { + u64 res = x_ >> kTidShift; + return res; + } + + u64 epoch() const { + u64 res = (x_ << (kTidBits + 1)) >> (64 - kClkBits); + return res; + } + + void IncrementEpoch() { + u64 old_epoch = epoch(); + x_ += 1 << kClkShift; + DCHECK_EQ(old_epoch + 1, epoch()); + (void)old_epoch; + } + + void SetIgnoreBit() { x_ |= kIgnoreBit; } + void ClearIgnoreBit() { x_ &= ~kIgnoreBit; } + bool GetIgnoreBit() const { return x_ & kIgnoreBit; } + + private: + friend class Shadow; + static const int kTidShift = 64 - kTidBits - 1; + static const int kClkShift = kTidShift - kClkBits; + static const u64 kIgnoreBit = 1ull; + static const u64 kFreedBit = 1ull << 63; + u64 x_; +}; + +// Shadow (from most significant bit): +// freed : 1 +// tid : kTidBits +// epoch : kClkBits +// is_write : 1 +// size_log : 2 +// addr0 : 3 +class Shadow : public FastState { + public: + explicit Shadow(u64 x) : FastState(x) { } + + explicit Shadow(const FastState &s) : FastState(s.x_) { } + + void SetAddr0AndSizeLog(u64 addr0, unsigned kAccessSizeLog) { + DCHECK_EQ(x_ & 31, 0); + DCHECK_LE(addr0, 7); + DCHECK_LE(kAccessSizeLog, 3); + x_ |= (kAccessSizeLog << 3) | addr0; + DCHECK_EQ(kAccessSizeLog, size_log()); + DCHECK_EQ(addr0, this->addr0()); + } + + void SetWrite(unsigned kAccessIsWrite) { + DCHECK_EQ(x_ & 32, 0); + if (kAccessIsWrite) + x_ |= 32; + DCHECK_EQ(kAccessIsWrite, is_write()); + } + + bool IsZero() const { return x_ == 0; } + u64 raw() const { return x_; } + + static inline bool TidsAreEqual(const Shadow s1, const Shadow s2) { + u64 shifted_xor = (s1.x_ ^ s2.x_) >> kTidShift; + DCHECK_EQ(shifted_xor == 0, s1.tid() == s2.tid()); + return shifted_xor == 0; + } + + static inline bool Addr0AndSizeAreEqual(const Shadow s1, const Shadow s2) { + u64 masked_xor = (s1.x_ ^ s2.x_) & 31; + return masked_xor == 0; + } + + static inline bool TwoRangesIntersect(Shadow s1, Shadow s2, + unsigned kS2AccessSize) { + bool res = false; + u64 diff = s1.addr0() - s2.addr0(); + if ((s64)diff < 0) { // s1.addr0 < s2.addr0 // NOLINT + // if (s1.addr0() + size1) > s2.addr0()) return true; + if (s1.size() > -diff) res = true; + } else { + // if (s2.addr0() + kS2AccessSize > s1.addr0()) return true; + if (kS2AccessSize > diff) res = true; + } + DCHECK_EQ(res, TwoRangesIntersectSLOW(s1, s2)); + DCHECK_EQ(res, TwoRangesIntersectSLOW(s2, s1)); + return res; + } + + // The idea behind the offset is as follows. + // Consider that we have 8 bool's contained within a single 8-byte block + // (mapped to a single shadow "cell"). Now consider that we write to the bools + // from a single thread (which we consider the common case). + // W/o offsetting each access will have to scan 4 shadow values at average + // to find the corresponding shadow value for the bool. + // With offsetting we start scanning shadow with the offset so that + // each access hits necessary shadow straight off (at least in an expected + // optimistic case). + // This logic works seamlessly for any layout of user data. For example, + // if user data is {int, short, char, char}, then accesses to the int are + // offsetted to 0, short - 4, 1st char - 6, 2nd char - 7. Hopefully, accesses + // from a single thread won't need to scan all 8 shadow values. + unsigned ComputeSearchOffset() { + return x_ & 7; + } + u64 addr0() const { return x_ & 7; } + u64 size() const { return 1ull << size_log(); } + bool is_write() const { return x_ & 32; } + + // The idea behind the freed bit is as follows. + // When the memory is freed (or otherwise unaccessible) we write to the shadow + // values with tid/epoch related to the free and the freed bit set. + // During memory accesses processing the freed bit is considered + // as msb of tid. So any access races with shadow with freed bit set + // (it is as if write from a thread with which we never synchronized before). + // This allows us to detect accesses to freed memory w/o additional + // overheads in memory access processing and at the same time restore + // tid/epoch of free. + void MarkAsFreed() { + x_ |= kFreedBit; + } + + bool GetFreedAndReset() { + bool res = x_ & kFreedBit; + x_ &= ~kFreedBit; + return res; + } + + private: + u64 size_log() const { return (x_ >> 3) & 3; } + + static bool TwoRangesIntersectSLOW(const Shadow s1, const Shadow s2) { + if (s1.addr0() == s2.addr0()) return true; + if (s1.addr0() < s2.addr0() && s1.addr0() + s1.size() > s2.addr0()) + return true; + if (s2.addr0() < s1.addr0() && s2.addr0() + s2.size() > s1.addr0()) + return true; + return false; + } +}; + +// Freed memory. +// As if 8-byte write by thread 0xff..f at epoch 0xff..f, races with everything. +const u64 kShadowFreed = 0xfffffffffffffff8ull; + +struct SignalContext; + +// This struct is stored in TLS. +struct ThreadState { + FastState fast_state; + // Synch epoch represents the threads's epoch before the last synchronization + // action. It allows to reduce number of shadow state updates. + // For example, fast_synch_epoch=100, last write to addr X was at epoch=150, + // if we are processing write to X from the same thread at epoch=200, + // we do nothing, because both writes happen in the same 'synch epoch'. + // That is, if another memory access does not race with the former write, + // it does not race with the latter as well. + // QUESTION: can we can squeeze this into ThreadState::Fast? + // E.g. ThreadState::Fast is a 44-bit, 32 are taken by synch_epoch and 12 are + // taken by epoch between synchs. + // This way we can save one load from tls. + u64 fast_synch_epoch; + // This is a slow path flag. On fast path, fast_state.GetIgnoreBit() is read. + // We do not distinguish beteween ignoring reads and writes + // for better performance. + int ignore_reads_and_writes; + uptr *shadow_stack_pos; + u64 *racy_shadow_addr; + u64 racy_state[2]; + Trace trace; +#ifndef TSAN_GO + // C/C++ uses embed shadow stack of fixed size. + uptr shadow_stack[kShadowStackSize]; +#else + // Go uses satellite shadow stack with dynamic size. + uptr *shadow_stack; + uptr *shadow_stack_end; +#endif + ThreadClock clock; + u64 stat[StatCnt]; + const int tid; + int in_rtl; + bool is_alive; + const uptr stk_addr; + const uptr stk_size; + const uptr tls_addr; + const uptr tls_size; + + DeadlockDetector deadlock_detector; + + bool in_signal_handler; + SignalContext *signal_ctx; + + // Set in regions of runtime that must be signal-safe and fork-safe. + // If set, malloc must not be called. + int nomalloc; + + explicit ThreadState(Context *ctx, int tid, u64 epoch, + uptr stk_addr, uptr stk_size, + uptr tls_addr, uptr tls_size); +}; + +Context *CTX(); + +#ifndef TSAN_GO +extern THREADLOCAL char cur_thread_placeholder[]; +INLINE ThreadState *cur_thread() { + return reinterpret_cast(&cur_thread_placeholder); +} +#endif + +enum ThreadStatus { + ThreadStatusInvalid, // Non-existent thread, data is invalid. + ThreadStatusCreated, // Created but not yet running. + ThreadStatusRunning, // The thread is currently running. + ThreadStatusFinished, // Joinable thread is finished but not yet joined. + ThreadStatusDead, // Joined, but some info (trace) is still alive. +}; + +// An info about a thread that is hold for some time after its termination. +struct ThreadDeadInfo { + Trace trace; +}; + +struct ThreadContext { + const int tid; + int unique_id; // Non-rolling thread id. + uptr user_id; // Some opaque user thread id (e.g. pthread_t). + ThreadState *thr; + ThreadStatus status; + bool detached; + int reuse_count; + SyncClock sync; + // Epoch at which the thread had started. + // If we see an event from the thread stamped by an older epoch, + // the event is from a dead thread that shared tid with this thread. + u64 epoch0; + u64 epoch1; + StackTrace creation_stack; + ThreadDeadInfo *dead_info; + ThreadContext *dead_next; // In dead thread list. + + explicit ThreadContext(int tid); +}; + +struct RacyStacks { + MD5Hash hash[2]; + bool operator==(const RacyStacks &other) const { + if (hash[0] == other.hash[0] && hash[1] == other.hash[1]) + return true; + if (hash[0] == other.hash[1] && hash[1] == other.hash[0]) + return true; + return false; + } +}; + +struct RacyAddress { + uptr addr_min; + uptr addr_max; +}; + +struct Context { + Context(); + + bool initialized; + + SyncTab synctab; + + Mutex report_mtx; + int nreported; + int nmissed_expected; + + Mutex thread_mtx; + unsigned thread_seq; + unsigned unique_thread_seq; + int alive_threads; + int max_alive_threads; + ThreadContext *threads[kMaxTid]; + int dead_list_size; + ThreadContext* dead_list_head; + ThreadContext* dead_list_tail; + + Vector racy_stacks; + Vector racy_addresses; + + Flags flags; + + u64 stat[StatCnt]; + u64 int_alloc_cnt[MBlockTypeCount]; + u64 int_alloc_siz[MBlockTypeCount]; +}; + +class ScopedInRtl { + public: + ScopedInRtl(); + ~ScopedInRtl(); + private: + ThreadState*thr_; + int in_rtl_; + int errno_; +}; + +class ScopedReport { + public: + explicit ScopedReport(ReportType typ); + ~ScopedReport(); + + void AddStack(const StackTrace *stack); + void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack); + void AddThread(const ThreadContext *tctx); + void AddMutex(const SyncVar *s); + void AddLocation(uptr addr, uptr size); + + const ReportDesc *GetReport() const; + + private: + Context *ctx_; + ReportDesc *rep_; + + ScopedReport(const ScopedReport&); + void operator = (const ScopedReport&); +}; + +void StatAggregate(u64 *dst, u64 *src); +void StatOutput(u64 *stat); +void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) { + if (kCollectStats) + thr->stat[typ] += n; +} + +void InitializeShadowMemory(); +void InitializeInterceptors(); +void InitializeDynamicAnnotations(); + +void ReportRace(ThreadState *thr); +bool OutputReport(const ScopedReport &srep, + const ReportStack *suppress_stack = 0); +bool IsExpectedReport(uptr addr, uptr size); + +#if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 1 +# define DPrintf TsanPrintf +#else +# define DPrintf(...) +#endif + +#if defined(TSAN_DEBUG_OUTPUT) && TSAN_DEBUG_OUTPUT >= 2 +# define DPrintf2 TsanPrintf +#else +# define DPrintf2(...) +#endif + +void Initialize(ThreadState *thr); +int Finalize(ThreadState *thr); + +void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite); +void MemoryAccessImpl(ThreadState *thr, uptr addr, + int kAccessSizeLog, bool kAccessIsWrite, FastState fast_state, + u64 *shadow_mem, Shadow cur); +void MemoryRead1Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryWrite1Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryRead8Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryWrite8Byte(ThreadState *thr, uptr pc, uptr addr); +void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, + uptr size, bool is_write); +void MemoryResetRange(ThreadState *thr, uptr pc, uptr addr, uptr size); +void MemoryRangeFreed(ThreadState *thr, uptr pc, uptr addr, uptr size); +void IgnoreCtl(ThreadState *thr, bool write, bool begin); + +void FuncEntry(ThreadState *thr, uptr pc); +void FuncExit(ThreadState *thr); + +int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached); +void ThreadStart(ThreadState *thr, int tid); +void ThreadFinish(ThreadState *thr); +int ThreadTid(ThreadState *thr, uptr pc, uptr uid); +void ThreadJoin(ThreadState *thr, uptr pc, int tid); +void ThreadDetach(ThreadState *thr, uptr pc, int tid); +void ThreadFinalize(ThreadState *thr); +void ThreadFinalizerGoroutine(ThreadState *thr); + +void MutexCreate(ThreadState *thr, uptr pc, uptr addr, bool rw, bool recursive); +void MutexDestroy(ThreadState *thr, uptr pc, uptr addr); +void MutexLock(ThreadState *thr, uptr pc, uptr addr); +void MutexUnlock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadLock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr); +void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr); + +void Acquire(ThreadState *thr, uptr pc, uptr addr); +void Release(ThreadState *thr, uptr pc, uptr addr); +void ReleaseStore(ThreadState *thr, uptr pc, uptr addr); + +// The hacky call uses custom calling convention and an assembly thunk. +// It is considerably faster that a normal call for the caller +// if it is not executed (it is intended for slow paths from hot functions). +// The trick is that the call preserves all registers and the compiler +// does not treat it as a call. +// If it does not work for you, use normal call. +#if TSAN_DEBUG == 0 +// The caller may not create the stack frame for itself at all, +// so we create a reserve stack frame for it (1024b must be enough). +#define HACKY_CALL(f) \ + __asm__ __volatile__("sub $0x400, %%rsp;" \ + "call " #f "_thunk;" \ + "add $0x400, %%rsp;" ::: "memory"); +#else +#define HACKY_CALL(f) f() +#endif + +void TraceSwitch(ThreadState *thr); + +extern "C" void __tsan_trace_switch(); +void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, u64 epoch, + EventType typ, uptr addr) { + StatInc(thr, StatEvents); + if (UNLIKELY((epoch % kTracePartSize) == 0)) { +#ifndef TSAN_GO + HACKY_CALL(__tsan_trace_switch); +#else + TraceSwitch(thr); +#endif + } + Event *evp = &thr->trace.events[epoch % kTraceSize]; + Event ev = (u64)addr | ((u64)typ << 61); + *evp = ev; +} + +} // namespace __tsan + +#endif // TSAN_RTL_H diff --git a/lib/tsan/rtl/tsan_rtl_amd64.S b/lib/tsan/rtl/tsan_rtl_amd64.S new file mode 100644 index 0000000..2028ec5 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_amd64.S @@ -0,0 +1,71 @@ +.section .text + +.globl __tsan_trace_switch_thunk +__tsan_trace_switch_thunk: + # Save scratch registers. + push %rax + push %rcx + push %rdx + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + # Align stack frame. + push %rbx # non-scratch + mov %rsp, %rbx # save current rsp + shr $4, %rsp # clear 4 lsb, align to 16 + shl $4, %rsp + + call __tsan_trace_switch + + # Unalign stack frame back. + mov %rbx, %rsp # restore the original rsp + pop %rbx + # Restore scratch registers. + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rdx + pop %rcx + pop %rax + ret + +.globl __tsan_report_race_thunk +__tsan_report_race_thunk: + # Save scratch registers. + push %rax + push %rcx + push %rdx + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + # Align stack frame. + push %rbx # non-scratch + mov %rsp, %rbx # save current rsp + shr $4, %rsp # clear 4 lsb, align to 16 + shl $4, %rsp + + call __tsan_report_race + + # Unalign stack frame back. + mov %rbx, %rsp # restore the original rsp + pop %rbx + # Restore scratch registers. + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rdx + pop %rcx + pop %rax + ret diff --git a/lib/tsan/rtl/tsan_rtl_mutex.cc b/lib/tsan/rtl/tsan_rtl_mutex.cc new file mode 100644 index 0000000..882def8 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_mutex.cc @@ -0,0 +1,220 @@ +//===-- tsan_rtl_mutex.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_rtl.h" +#include "tsan_sync.h" +#include "tsan_report.h" +#include "tsan_symbolize.h" + +namespace __tsan { + +void MutexCreate(ThreadState *thr, uptr pc, uptr addr, + bool rw, bool recursive) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexCreate %zx\n", thr->tid, addr); + StatInc(thr, StatMutexCreate); + MemoryWrite1Byte(thr, pc, addr); + SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true); + s->is_rw = rw; + s->is_recursive = recursive; + s->mtx.Unlock(); +} + +void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexDestroy %zx\n", thr->tid, addr); + StatInc(thr, StatMutexDestroy); + MemoryWrite1Byte(thr, pc, addr); + SyncVar *s = ctx->synctab.GetAndRemove(thr, pc, addr); + if (s == 0) + return; + if (s->owner_tid != SyncVar::kInvalidTid && !s->is_broken) { + s->is_broken = true; + ScopedReport rep(ReportTypeMutexDestroyLocked); + rep.AddMutex(s); + rep.AddLocation(s->addr, 1); + OutputReport(rep); + } + DestroyAndFree(s); +} + +void MutexLock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexLock %zx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeLock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid == SyncVar::kInvalidTid) { + CHECK_EQ(s->recursion, 0); + s->owner_tid = thr->tid; + } else if (s->owner_tid == thr->tid) { + CHECK_GT(s->recursion, 0); + } else { + TsanPrintf("ThreadSanitizer WARNING: double lock\n"); + } + if (s->recursion == 0) { + StatInc(thr, StatMutexLock); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + thr->clock.acquire(&s->read_clock); + StatInc(thr, StatSyncAcquire); + } else if (!s->is_recursive) { + StatInc(thr, StatMutexRecLock); + } + s->recursion++; + s->mtx.Unlock(); +} + +void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->recursion == 0) { + if (!s->is_broken) { + s->is_broken = true; + TsanPrintf("ThreadSanitizer WARNING: unlock of unlocked mutex\n"); + } + } else if (s->owner_tid != thr->tid) { + if (!s->is_broken) { + s->is_broken = true; + TsanPrintf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + } + } else { + s->recursion--; + if (s->recursion == 0) { + StatInc(thr, StatMutexUnlock); + s->owner_tid = SyncVar::kInvalidTid; + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + } else { + StatInc(thr, StatMutexRecUnlock); + } + } + s->mtx.Unlock(); +} + +void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadLock %zx\n", thr->tid, addr); + StatInc(thr, StatMutexReadLock); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRLock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + if (s->owner_tid != SyncVar::kInvalidTid) + TsanPrintf("ThreadSanitizer WARNING: read lock of a write locked mutex\n"); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + s->mtx.ReadUnlock(); +} + +void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadUnlock %zx\n", thr->tid, addr); + StatInc(thr, StatMutexReadUnlock); + MemoryRead1Byte(thr, pc, addr); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid != SyncVar::kInvalidTid) + TsanPrintf("ThreadSanitizer WARNING: read unlock of a write " + "locked mutex\n"); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->read_clock); + StatInc(thr, StatSyncRelease); + s->mtx.Unlock(); +} + +void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr); + MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + if (s->owner_tid == SyncVar::kInvalidTid) { + // Seems to be read unlock. + StatInc(thr, StatMutexReadUnlock); + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeRUnlock, addr); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->read_clock); + StatInc(thr, StatSyncRelease); + } else if (s->owner_tid == thr->tid) { + // Seems to be write unlock. + CHECK_GT(s->recursion, 0); + s->recursion--; + if (s->recursion == 0) { + StatInc(thr, StatMutexUnlock); + s->owner_tid = SyncVar::kInvalidTid; + // FIXME: Refactor me, plz. + // The sequence of events is quite tricky and doubled in several places. + // First, it's a bug to increment the epoch w/o writing to the trace. + // Then, the acquire/release logic can be factored out as well. + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeUnlock, addr); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + } else { + StatInc(thr, StatMutexRecUnlock); + } + } else if (!s->is_broken) { + s->is_broken = true; + TsanPrintf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); + } + s->mtx.Unlock(); +} + +void Acquire(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: Acquire %zx\n", thr->tid, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.acquire(&s->clock); + StatInc(thr, StatSyncAcquire); + s->mtx.ReadUnlock(); +} + +void Release(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: Release %zx\n", thr->tid, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.release(&s->clock); + StatInc(thr, StatSyncRelease); + s->mtx.Unlock(); +} + +void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) { + CHECK_GT(thr->in_rtl, 0); + DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr); + SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->clock.ReleaseStore(&s->clock); + StatInc(thr, StatSyncRelease); + s->mtx.Unlock(); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_report.cc b/lib/tsan/rtl/tsan_rtl_report.cc new file mode 100644 index 0000000..f66e17e --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_report.cc @@ -0,0 +1,372 @@ +//===-- tsan_rtl_report.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_libc.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_platform.h" +#include "tsan_rtl.h" +#include "tsan_suppressions.h" +#include "tsan_symbolize.h" +#include "tsan_report.h" +#include "tsan_sync.h" +#include "tsan_mman.h" +#include "tsan_flags.h" + +namespace __sanitizer { +using namespace __tsan; + +void CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2) { + ScopedInRtl in_rtl; + TsanPrintf("FATAL: ThreadSanitizer CHECK failed: " + "%s:%d \"%s\" (0x%zx, 0x%zx)\n", + file, line, cond, (uptr)v1, (uptr)v2); + Die(); +} + +} // namespace __sanitizer + +namespace __tsan { + +// Can be overriden by an application/test to intercept reports. +#ifdef TSAN_EXTERNAL_HOOKS +bool OnReport(const ReportDesc *rep, bool suppressed); +#else +bool WEAK OnReport(const ReportDesc *rep, bool suppressed) { + (void)rep; + return suppressed; +} +#endif + +static void StackStripMain(ReportStack *stack) { + ReportStack *last_frame = 0; + ReportStack *last_frame2 = 0; + const char *prefix = "__interceptor_"; + uptr prefix_len = internal_strlen(prefix); + const char *path_prefix = flags()->strip_path_prefix; + uptr path_prefix_len = internal_strlen(path_prefix); + char *pos; + for (ReportStack *ent = stack; ent; ent = ent->next) { + if (ent->func && 0 == internal_strncmp(ent->func, prefix, prefix_len)) + ent->func += prefix_len; + if (ent->file && (pos = internal_strstr(ent->file, path_prefix))) + ent->file = pos + path_prefix_len; + if (ent->file && ent->file[0] == '.' && ent->file[1] == '/') + ent->file += 2; + last_frame2 = last_frame; + last_frame = ent; + } + + if (last_frame2 == 0) + return; + const char *last = last_frame->func; +#ifndef TSAN_GO + const char *last2 = last_frame2->func; + // Strip frame above 'main' + if (last2 && 0 == internal_strcmp(last2, "main")) { + last_frame2->next = 0; + // Strip our internal thread start routine. + } else if (last && 0 == internal_strcmp(last, "__tsan_thread_start_func")) { + last_frame2->next = 0; + // Strip global ctors init. + } else if (last && 0 == internal_strcmp(last, "__do_global_ctors_aux")) { + last_frame2->next = 0; + // If both are 0, then we probably just failed to symbolize. + } else if (last || last2) { + // Ensure that we recovered stack completely. Trimmed stack + // can actually happen if we do not instrument some code, + // so it's only a DCHECK. However we must try hard to not miss it + // due to our fault. + TsanPrintf("Bottom stack frame of stack %zx is missed\n", stack->pc); + } +#else + if (last && 0 == internal_strcmp(last, "schedunlock")) + last_frame2->next = 0; +#endif +} + +static ReportStack *SymbolizeStack(const StackTrace& trace) { + if (trace.IsEmpty()) + return 0; + ReportStack *stack = 0; + for (uptr si = 0; si < trace.Size(); si++) { + // We obtain the return address, that is, address of the next instruction, + // so offset it by 1 byte. + bool is_last = (si == trace.Size() - 1); + ReportStack *ent = SymbolizeCode(trace.Get(si) - !is_last); + CHECK_NE(ent, 0); + ReportStack *last = ent; + while (last->next) { + last->pc += !is_last; + last = last->next; + } + last->pc += !is_last; + last->next = stack; + stack = ent; + } + StackStripMain(stack); + return stack; +} + +ScopedReport::ScopedReport(ReportType typ) { + ctx_ = CTX(); + void *mem = internal_alloc(MBlockReport, sizeof(ReportDesc)); + rep_ = new(mem) ReportDesc; + rep_->typ = typ; + ctx_->report_mtx.Lock(); +} + +ScopedReport::~ScopedReport() { + ctx_->report_mtx.Unlock(); + rep_->~ReportDesc(); + internal_free(rep_); +} + +void ScopedReport::AddStack(const StackTrace *stack) { + ReportStack **rs = rep_->stacks.PushBack(); + *rs = SymbolizeStack(*stack); +} + +void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, + const StackTrace *stack) { + void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); + ReportMop *mop = new(mem) ReportMop; + rep_->mops.PushBack(mop); + mop->tid = s.tid(); + mop->addr = addr + s.addr0(); + mop->size = s.size(); + mop->write = s.is_write(); + mop->nmutex = 0; + mop->stack = SymbolizeStack(*stack); +} + +void ScopedReport::AddThread(const ThreadContext *tctx) { + void *mem = internal_alloc(MBlockReportThread, sizeof(ReportThread)); + ReportThread *rt = new(mem) ReportThread(); + rep_->threads.PushBack(rt); + rt->id = tctx->tid; + rt->running = (tctx->status == ThreadStatusRunning); + rt->stack = SymbolizeStack(tctx->creation_stack); +} + +void ScopedReport::AddMutex(const SyncVar *s) { + void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); + ReportMutex *rm = new(mem) ReportMutex(); + rep_->mutexes.PushBack(rm); + rm->id = 42; + rm->stack = SymbolizeStack(s->creation_stack); +} + +void ScopedReport::AddLocation(uptr addr, uptr size) { + ReportStack *symb = SymbolizeData(addr); + if (symb) { + void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); + ReportLocation *loc = new(mem) ReportLocation(); + rep_->locs.PushBack(loc); + loc->type = ReportLocationGlobal; + loc->addr = addr; + loc->size = size; + loc->tid = 0; + loc->name = symb->func; + loc->file = symb->file; + loc->line = symb->line; + loc->stack = 0; + internal_free(symb); + } +} + +const ReportDesc *ScopedReport::GetReport() const { + return rep_; +} + +static void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { + ThreadContext *tctx = CTX()->threads[tid]; + if (tctx == 0) + return; + Trace* trace = 0; + if (tctx->status == ThreadStatusRunning) { + CHECK(tctx->thr); + trace = &tctx->thr->trace; + } else if (tctx->status == ThreadStatusFinished + || tctx->status == ThreadStatusDead) { + if (tctx->dead_info == 0) + return; + trace = &tctx->dead_info->trace; + } else { + return; + } + Lock l(&trace->mtx); + const int partidx = (epoch / (kTraceSize / kTraceParts)) % kTraceParts; + TraceHeader* hdr = &trace->headers[partidx]; + if (epoch < hdr->epoch0) + return; + const u64 eend = epoch % kTraceSize; + const u64 ebegin = eend / kTracePartSize * kTracePartSize; + DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n", + tid, (uptr)epoch, (uptr)ebegin, (uptr)eend, partidx); + InternalScopedBuf stack(1024); // FIXME: de-hardcode 1024 + for (uptr i = 0; i < hdr->stack0.Size(); i++) { + stack[i] = hdr->stack0.Get(i); + DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]); + } + uptr pos = hdr->stack0.Size(); + for (uptr i = ebegin; i <= eend; i++) { + Event ev = trace->events[i]; + EventType typ = (EventType)(ev >> 61); + uptr pc = (uptr)(ev & 0xffffffffffffull); + DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc); + if (typ == EventTypeMop) { + stack[pos] = pc; + } else if (typ == EventTypeFuncEnter) { + stack[pos++] = pc; + } else if (typ == EventTypeFuncExit) { + // Since we have full stacks, this should never happen. + DCHECK_GT(pos, 0); + if (pos > 0) + pos--; + } + for (uptr j = 0; j <= pos; j++) + DPrintf2(" #%zu: %zx\n", j, stack[j]); + } + if (pos == 0 && stack[0] == 0) + return; + pos++; + stk->Init(stack, pos); +} + +static bool HandleRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], + uptr addr_min, uptr addr_max) { + Context *ctx = CTX(); + bool equal_stack = false; + RacyStacks hash = {}; + if (flags()->suppress_equal_stacks) { + hash.hash[0] = md5_hash(traces[0].Begin(), traces[0].Size() * sizeof(uptr)); + hash.hash[1] = md5_hash(traces[1].Begin(), traces[1].Size() * sizeof(uptr)); + for (uptr i = 0; i < ctx->racy_stacks.Size(); i++) { + if (hash == ctx->racy_stacks[i]) { + DPrintf("ThreadSanitizer: suppressing report as doubled (stack)\n"); + equal_stack = true; + break; + } + } + } + bool equal_address = false; + RacyAddress ra0 = {addr_min, addr_max}; + if (flags()->suppress_equal_addresses) { + for (uptr i = 0; i < ctx->racy_addresses.Size(); i++) { + RacyAddress ra2 = ctx->racy_addresses[i]; + uptr maxbeg = max(ra0.addr_min, ra2.addr_min); + uptr minend = min(ra0.addr_max, ra2.addr_max); + if (maxbeg < minend) { + DPrintf("ThreadSanitizer: suppressing report as doubled (addr)\n"); + equal_address = true; + break; + } + } + } + if (equal_stack || equal_address) { + if (!equal_stack) + ctx->racy_stacks.PushBack(hash); + if (!equal_address) + ctx->racy_addresses.PushBack(ra0); + return true; + } + return false; +} + +static void AddRacyStacks(ThreadState *thr, const StackTrace (&traces)[2], + uptr addr_min, uptr addr_max) { + Context *ctx = CTX(); + if (flags()->suppress_equal_stacks) { + RacyStacks hash; + hash.hash[0] = md5_hash(traces[0].Begin(), traces[0].Size() * sizeof(uptr)); + hash.hash[1] = md5_hash(traces[1].Begin(), traces[1].Size() * sizeof(uptr)); + ctx->racy_stacks.PushBack(hash); + } + if (flags()->suppress_equal_addresses) { + RacyAddress ra0 = {addr_min, addr_max}; + ctx->racy_addresses.PushBack(ra0); + } +} + +bool OutputReport(const ScopedReport &srep, const ReportStack *suppress_stack) { + const ReportDesc *rep = srep.GetReport(); + bool suppressed = IsSuppressed(rep->typ, suppress_stack); + suppressed = OnReport(rep, suppressed); + if (suppressed) + return false; + PrintReport(rep); + CTX()->nreported++; + return true; +} + +void ReportRace(ThreadState *thr) { + ScopedInRtl in_rtl; + + bool freed = false; + { + Shadow s(thr->racy_state[1]); + freed = s.GetFreedAndReset(); + thr->racy_state[1] = s.raw(); + } + + uptr addr = ShadowToMem((uptr)thr->racy_shadow_addr); + uptr addr_min = 0; + uptr addr_max = 0; + { + uptr a0 = addr + Shadow(thr->racy_state[0]).addr0(); + uptr a1 = addr + Shadow(thr->racy_state[1]).addr0(); + uptr e0 = a0 + Shadow(thr->racy_state[0]).size(); + uptr e1 = a1 + Shadow(thr->racy_state[1]).size(); + addr_min = min(a0, a1); + addr_max = max(e0, e1); + if (IsExpectedReport(addr_min, addr_max - addr_min)) + return; + } + + Context *ctx = CTX(); + Lock l0(&ctx->thread_mtx); + + ScopedReport rep(freed ? ReportTypeUseAfterFree : ReportTypeRace); + const uptr kMop = 2; + StackTrace traces[kMop]; + for (uptr i = 0; i < kMop; i++) { + Shadow s(thr->racy_state[i]); + RestoreStack(s.tid(), s.epoch(), &traces[i]); + } + + if (HandleRacyStacks(thr, traces, addr_min, addr_max)) + return; + + for (uptr i = 0; i < kMop; i++) { + Shadow s(thr->racy_state[i]); + rep.AddMemoryAccess(addr, s, &traces[i]); + } + + // Ensure that we have at least something for the current thread. + CHECK_EQ(traces[0].IsEmpty(), false); + + for (uptr i = 0; i < kMop; i++) { + FastState s(thr->racy_state[i]); + ThreadContext *tctx = ctx->threads[s.tid()]; + if (s.epoch() < tctx->epoch0 || s.epoch() > tctx->epoch1) + continue; + rep.AddThread(tctx); + } + + if (!OutputReport(rep, rep.GetReport()->mops[0]->stack)) + return; + + AddRacyStacks(thr, traces, addr_min, addr_max); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_rtl_thread.cc b/lib/tsan/rtl/tsan_rtl_thread.cc new file mode 100644 index 0000000..f7d5f13 --- /dev/null +++ b/lib/tsan/rtl/tsan_rtl_thread.cc @@ -0,0 +1,394 @@ +//===-- tsan_rtl_thread.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "tsan_platform.h" +#include "tsan_report.h" +#include "tsan_sync.h" + +namespace __tsan { + +#ifndef TSAN_GO +const int kThreadQuarantineSize = 16; +#else +const int kThreadQuarantineSize = 64; +#endif + +static void MaybeReportThreadLeak(ThreadContext *tctx) { + if (tctx->detached) + return; + if (tctx->status != ThreadStatusCreated + && tctx->status != ThreadStatusRunning + && tctx->status != ThreadStatusFinished) + return; + ScopedReport rep(ReportTypeThreadLeak); + rep.AddThread(tctx); + OutputReport(rep); +} + +void ThreadFinalize(ThreadState *thr) { + CHECK_GT(thr->in_rtl, 0); + if (!flags()->report_thread_leaks) + return; + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + for (unsigned i = 0; i < kMaxTid; i++) { + ThreadContext *tctx = ctx->threads[i]; + if (tctx == 0) + continue; + MaybeReportThreadLeak(tctx); + } +} + +static void ThreadDead(ThreadState *thr, ThreadContext *tctx) { + Context *ctx = CTX(); + CHECK_GT(thr->in_rtl, 0); + CHECK(tctx->status == ThreadStatusRunning + || tctx->status == ThreadStatusFinished); + DPrintf("#%d: ThreadDead uid=%zu\n", thr->tid, tctx->user_id); + tctx->status = ThreadStatusDead; + tctx->user_id = 0; + tctx->sync.Reset(); + + // Put to dead list. + tctx->dead_next = 0; + if (ctx->dead_list_size == 0) + ctx->dead_list_head = tctx; + else + ctx->dead_list_tail->dead_next = tctx; + ctx->dead_list_tail = tctx; + ctx->dead_list_size++; +} + +int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { + CHECK_GT(thr->in_rtl, 0); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + StatInc(thr, StatThreadCreate); + int tid = -1; + ThreadContext *tctx = 0; + if (ctx->dead_list_size > kThreadQuarantineSize + || ctx->thread_seq >= kMaxTid) { + if (ctx->dead_list_size == 0) { + TsanPrintf("ThreadSanitizer: %d thread limit exceeded. Dying.\n", + kMaxTid); + Die(); + } + StatInc(thr, StatThreadReuse); + tctx = ctx->dead_list_head; + ctx->dead_list_head = tctx->dead_next; + ctx->dead_list_size--; + if (ctx->dead_list_size == 0) { + CHECK_EQ(tctx->dead_next, 0); + ctx->dead_list_head = 0; + } + CHECK_EQ(tctx->status, ThreadStatusDead); + tctx->status = ThreadStatusInvalid; + tctx->reuse_count++; + tctx->sync.Reset(); + tid = tctx->tid; + DestroyAndFree(tctx->dead_info); + } else { + StatInc(thr, StatThreadMaxTid); + tid = ctx->thread_seq++; + void *mem = internal_alloc(MBlockThreadContex, sizeof(ThreadContext)); + tctx = new(mem) ThreadContext(tid); + ctx->threads[tid] = tctx; + } + CHECK_NE(tctx, 0); + CHECK_GE(tid, 0); + CHECK_LT(tid, kMaxTid); + DPrintf("#%d: ThreadCreate tid=%d uid=%zu\n", thr->tid, tid, uid); + CHECK_EQ(tctx->status, ThreadStatusInvalid); + ctx->alive_threads++; + if (ctx->max_alive_threads < ctx->alive_threads) { + ctx->max_alive_threads++; + CHECK_EQ(ctx->max_alive_threads, ctx->alive_threads); + StatInc(thr, StatThreadMaxAlive); + } + tctx->status = ThreadStatusCreated; + tctx->thr = 0; + tctx->user_id = uid; + tctx->unique_id = ctx->unique_thread_seq++; + tctx->detached = detached; + if (tid) { + thr->fast_state.IncrementEpoch(); + // Can't increment epoch w/o writing to the trace as well. + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&tctx->sync); + StatInc(thr, StatSyncRelease); + + tctx->creation_stack.ObtainCurrent(thr, pc); + } + return tid; +} + +void ThreadStart(ThreadState *thr, int tid) { + CHECK_GT(thr->in_rtl, 0); + uptr stk_addr = 0; + uptr stk_size = 0; + uptr tls_addr = 0; + uptr tls_size = 0; + GetThreadStackAndTls(tid == 0, &stk_addr, &stk_size, &tls_addr, &tls_size); + + if (tid) { + if (stk_addr && stk_size) { + MemoryResetRange(thr, /*pc=*/ 1, stk_addr, stk_size); + } + + if (tls_addr && tls_size) { + // Check that the thr object is in tls; + const uptr thr_beg = (uptr)thr; + const uptr thr_end = (uptr)thr + sizeof(*thr); + CHECK_GE(thr_beg, tls_addr); + CHECK_LE(thr_beg, tls_addr + tls_size); + CHECK_GE(thr_end, tls_addr); + CHECK_LE(thr_end, tls_addr + tls_size); + // Since the thr object is huge, skip it. + MemoryResetRange(thr, /*pc=*/ 2, tls_addr, thr_beg - tls_addr); + MemoryResetRange(thr, /*pc=*/ 2, thr_end, tls_addr + tls_size - thr_end); + } + } + + Lock l(&CTX()->thread_mtx); + ThreadContext *tctx = CTX()->threads[tid]; + CHECK_NE(tctx, 0); + CHECK_EQ(tctx->status, ThreadStatusCreated); + tctx->status = ThreadStatusRunning; + tctx->epoch0 = tctx->epoch1 + 1; + tctx->epoch1 = (u64)-1; + new(thr) ThreadState(CTX(), tid, tctx->epoch0, stk_addr, stk_size, + tls_addr, tls_size); +#ifdef TSAN_GO + // Setup dynamic shadow stack. + const int kInitStackSize = 8; + thr->shadow_stack = (uptr*)internal_alloc(MBlockShadowStack, + kInitStackSize * sizeof(uptr)); + thr->shadow_stack_pos = thr->shadow_stack; + thr->shadow_stack_end = thr->shadow_stack + kInitStackSize; +#endif + tctx->thr = thr; + thr->fast_synch_epoch = tctx->epoch0; + thr->clock.set(tid, tctx->epoch0); + thr->clock.acquire(&tctx->sync); + StatInc(thr, StatSyncAcquire); + DPrintf("#%d: ThreadStart epoch=%zu stk_addr=%zx stk_size=%zx " + "tls_addr=%zx tls_size=%zx\n", + tid, (uptr)tctx->epoch0, stk_addr, stk_size, tls_addr, tls_size); + thr->is_alive = true; +} + +void ThreadFinish(ThreadState *thr) { + CHECK_GT(thr->in_rtl, 0); + StatInc(thr, StatThreadFinish); + // FIXME: Treat it as write. + if (thr->stk_addr && thr->stk_size) + MemoryResetRange(thr, /*pc=*/ 3, thr->stk_addr, thr->stk_size); + if (thr->tls_addr && thr->tls_size) { + const uptr thr_beg = (uptr)thr; + const uptr thr_end = (uptr)thr + sizeof(*thr); + // Since the thr object is huge, skip it. + MemoryResetRange(thr, /*pc=*/ 4, thr->tls_addr, thr_beg - thr->tls_addr); + MemoryResetRange(thr, /*pc=*/ 5, + thr_end, thr->tls_addr + thr->tls_size - thr_end); + } + thr->is_alive = false; + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[thr->tid]; + CHECK_NE(tctx, 0); + CHECK_EQ(tctx->status, ThreadStatusRunning); + CHECK_GT(ctx->alive_threads, 0); + ctx->alive_threads--; + if (tctx->detached) { + ThreadDead(thr, tctx); + } else { + thr->fast_state.IncrementEpoch(); + // Can't increment epoch w/o writing to the trace as well. + TraceAddEvent(thr, thr->fast_state.epoch(), EventTypeMop, 0); + thr->clock.set(thr->tid, thr->fast_state.epoch()); + thr->fast_synch_epoch = thr->fast_state.epoch(); + thr->clock.release(&tctx->sync); + StatInc(thr, StatSyncRelease); + tctx->status = ThreadStatusFinished; + } + + // Save from info about the thread. + tctx->dead_info = new(internal_alloc(MBlockDeadInfo, sizeof(ThreadDeadInfo))) + ThreadDeadInfo(); + internal_memcpy(&tctx->dead_info->trace.events[0], + &thr->trace.events[0], sizeof(thr->trace.events)); + for (int i = 0; i < kTraceParts; i++) { + tctx->dead_info->trace.headers[i].stack0.CopyFrom( + thr->trace.headers[i].stack0); + } + tctx->epoch1 = thr->fast_state.epoch(); + + thr->~ThreadState(); + StatAggregate(ctx->stat, thr->stat); + tctx->thr = 0; +} + +int ThreadTid(ThreadState *thr, uptr pc, uptr uid) { + CHECK_GT(thr->in_rtl, 0); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + int res = -1; + for (unsigned tid = 0; tid < kMaxTid; tid++) { + ThreadContext *tctx = ctx->threads[tid]; + if (tctx != 0 && tctx->user_id == uid + && tctx->status != ThreadStatusInvalid) { + tctx->user_id = 0; + res = tid; + break; + } + } + DPrintf("#%d: ThreadTid uid=%zu tid=%d\n", thr->tid, uid, res); + return res; +} + +void ThreadJoin(ThreadState *thr, uptr pc, int tid) { + CHECK_GT(thr->in_rtl, 0); + CHECK_GT(tid, 0); + CHECK_LT(tid, kMaxTid); + DPrintf("#%d: ThreadJoin tid=%d\n", thr->tid, tid); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[tid]; + if (tctx->status == ThreadStatusInvalid) { + TsanPrintf("ThreadSanitizer: join of non-existent thread\n"); + return; + } + CHECK_EQ(tctx->detached, false); + CHECK_EQ(tctx->status, ThreadStatusFinished); + thr->clock.acquire(&tctx->sync); + StatInc(thr, StatSyncAcquire); + ThreadDead(thr, tctx); +} + +void ThreadDetach(ThreadState *thr, uptr pc, int tid) { + CHECK_GT(thr->in_rtl, 0); + CHECK_GT(tid, 0); + CHECK_LT(tid, kMaxTid); + Context *ctx = CTX(); + Lock l(&ctx->thread_mtx); + ThreadContext *tctx = ctx->threads[tid]; + if (tctx->status == ThreadStatusInvalid) { + TsanPrintf("ThreadSanitizer: detach of non-existent thread\n"); + return; + } + if (tctx->status == ThreadStatusFinished) { + ThreadDead(thr, tctx); + } else { + tctx->detached = true; + } +} + +void ThreadFinalizerGoroutine(ThreadState *thr) { + thr->clock.Disable(thr->tid); +} + +void MemoryAccessRange(ThreadState *thr, uptr pc, uptr addr, + uptr size, bool is_write) { + if (size == 0) + return; + + u64 *shadow_mem = (u64*)MemToShadow(addr); + DPrintf2("#%d: MemoryAccessRange: @%p %p size=%d is_write=%d\n", + thr->tid, (void*)pc, (void*)addr, + (int)size, is_write); + +#if TSAN_DEBUG + if (!IsAppMem(addr)) { + TsanPrintf("Access to non app mem %zx\n", addr); + DCHECK(IsAppMem(addr)); + } + if (!IsAppMem(addr + size - 1)) { + TsanPrintf("Access to non app mem %zx\n", addr + size - 1); + DCHECK(IsAppMem(addr + size - 1)); + } + if (!IsShadowMem((uptr)shadow_mem)) { + TsanPrintf("Bad shadow addr %p (%zx)\n", shadow_mem, addr); + DCHECK(IsShadowMem((uptr)shadow_mem)); + } + if (!IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))) { + TsanPrintf("Bad shadow addr %p (%zx)\n", + shadow_mem + size * kShadowCnt / 8 - 1, addr + size - 1); + DCHECK(IsShadowMem((uptr)(shadow_mem + size * kShadowCnt / 8 - 1))); + } +#endif + + StatInc(thr, StatMopRange); + + FastState fast_state = thr->fast_state; + if (fast_state.GetIgnoreBit()) + return; + + fast_state.IncrementEpoch(); + thr->fast_state = fast_state; + TraceAddEvent(thr, fast_state.epoch(), EventTypeMop, pc); + + bool unaligned = (addr % kShadowCell) != 0; + + // Handle unaligned beginning, if any. + for (; addr % kShadowCell && size; addr++, size--) { + int const kAccessSizeLog = 0; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + } + if (unaligned) + shadow_mem += kShadowCnt; + // Handle middle part, if any. + for (; size >= kShadowCell; addr += kShadowCell, size -= kShadowCell) { + int const kAccessSizeLog = 3; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(0, kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + shadow_mem += kShadowCnt; + } + // Handle ending, if any. + for (; size; addr++, size--) { + int const kAccessSizeLog = 0; + Shadow cur(fast_state); + cur.SetWrite(is_write); + cur.SetAddr0AndSizeLog(addr & (kShadowCell - 1), kAccessSizeLog); + MemoryAccessImpl(thr, addr, kAccessSizeLog, is_write, fast_state, + shadow_mem, cur); + } +} + +void MemoryRead1Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 0, 0); +} + +void MemoryWrite1Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 0, 1); +} + +void MemoryRead8Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 3, 0); +} + +void MemoryWrite8Byte(ThreadState *thr, uptr pc, uptr addr) { + MemoryAccess(thr, pc, addr, 3, 1); +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_stat.cc b/lib/tsan/rtl/tsan_stat.cc new file mode 100644 index 0000000..a7c33a5 --- /dev/null +++ b/lib/tsan/rtl/tsan_stat.cc @@ -0,0 +1,249 @@ +//===-- tsan_stat.cc ------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_stat.h" +#include "tsan_rtl.h" + +namespace __tsan { + +void StatAggregate(u64 *dst, u64 *src) { + if (!kCollectStats) + return; + for (int i = 0; i < StatCnt; i++) + dst[i] += src[i]; +} + +void StatOutput(u64 *stat) { + if (!kCollectStats) + return; + + stat[StatShadowNonZero] = stat[StatShadowProcessed] - stat[StatShadowZero]; + + static const char *name[StatCnt] = {}; + name[StatMop] = "Memory accesses "; + name[StatMopRead] = " Including reads "; + name[StatMopWrite] = " writes "; + name[StatMop1] = " Including size 1 "; + name[StatMop2] = " size 2 "; + name[StatMop4] = " size 4 "; + name[StatMop8] = " size 8 "; + name[StatMopSame] = " Including same "; + name[StatMopRange] = " Including range "; + name[StatShadowProcessed] = "Shadow processed "; + name[StatShadowZero] = " Including empty "; + name[StatShadowNonZero] = " Including non empty "; + name[StatShadowSameSize] = " Including same size "; + name[StatShadowIntersect] = " intersect "; + name[StatShadowNotIntersect] = " not intersect "; + name[StatShadowSameThread] = " Including same thread "; + name[StatShadowAnotherThread] = " another thread "; + name[StatShadowReplace] = " Including evicted "; + + name[StatFuncEnter] = "Function entries "; + name[StatFuncExit] = "Function exits "; + name[StatEvents] = "Events collected "; + + name[StatThreadCreate] = "Total threads created "; + name[StatThreadFinish] = " threads finished "; + name[StatThreadReuse] = " threads reused "; + name[StatThreadMaxTid] = " max tid "; + name[StatThreadMaxAlive] = " max alive threads "; + + name[StatMutexCreate] = "Mutexes created "; + name[StatMutexDestroy] = " destroyed "; + name[StatMutexLock] = " lock "; + name[StatMutexUnlock] = " unlock "; + name[StatMutexRecLock] = " recursive lock "; + name[StatMutexRecUnlock] = " recursive unlock "; + name[StatMutexReadLock] = " read lock "; + name[StatMutexReadUnlock] = " read unlock "; + + name[StatSyncCreated] = "Sync objects created "; + name[StatSyncDestroyed] = " destroyed "; + name[StatSyncAcquire] = " acquired "; + name[StatSyncRelease] = " released "; + + name[StatAtomic] = "Atomic operations "; + name[StatAtomicLoad] = " Including load "; + name[StatAtomicStore] = " store "; + name[StatAtomicExchange] = " exchange "; + name[StatAtomicFetchAdd] = " fetch_add "; + name[StatAtomicCAS] = " compare_exchange "; + name[StatAtomicFence] = " fence "; + name[StatAtomicRelaxed] = " Including relaxed "; + name[StatAtomicConsume] = " consume "; + name[StatAtomicAcquire] = " acquire "; + name[StatAtomicRelease] = " release "; + name[StatAtomicAcq_Rel] = " acq_rel "; + name[StatAtomicSeq_Cst] = " seq_cst "; + name[StatAtomic1] = " Including size 1 "; + name[StatAtomic2] = " size 2 "; + name[StatAtomic4] = " size 4 "; + name[StatAtomic8] = " size 8 "; + + name[StatInterceptor] = "Interceptors "; + name[StatInt_longjmp] = " longjmp "; + name[StatInt_siglongjmp] = " siglongjmp "; + name[StatInt_malloc] = " malloc "; + name[StatInt_calloc] = " calloc "; + name[StatInt_realloc] = " realloc "; + name[StatInt_free] = " free "; + name[StatInt_cfree] = " cfree "; + name[StatInt_mmap] = " mmap "; + name[StatInt_mmap64] = " mmap64 "; + name[StatInt_munmap] = " munmap "; + name[StatInt_memalign] = " memalign "; + name[StatInt_valloc] = " valloc "; + name[StatInt_pvalloc] = " pvalloc "; + name[StatInt_posix_memalign] = " posix_memalign "; + name[StatInt__Znwm] = " _Znwm "; + name[StatInt__ZnwmRKSt9nothrow_t] = " _ZnwmRKSt9nothrow_t "; + name[StatInt__Znam] = " _Znam "; + name[StatInt__ZnamRKSt9nothrow_t] = " _ZnamRKSt9nothrow_t "; + name[StatInt__ZdlPv] = " _ZdlPv "; + name[StatInt__ZdlPvRKSt9nothrow_t] = " _ZdlPvRKSt9nothrow_t "; + name[StatInt__ZdaPv] = " _ZdaPv "; + name[StatInt__ZdaPvRKSt9nothrow_t] = " _ZdaPvRKSt9nothrow_t "; + name[StatInt_strlen] = " strlen "; + name[StatInt_memset] = " memset "; + name[StatInt_memcpy] = " memcpy "; + name[StatInt_strcmp] = " strcmp "; + name[StatInt_memchr] = " memchr "; + name[StatInt_memrchr] = " memrchr "; + name[StatInt_memmove] = " memmove "; + name[StatInt_memcmp] = " memcmp "; + name[StatInt_strchr] = " strchr "; + name[StatInt_strchrnul] = " strchrnul "; + name[StatInt_strrchr] = " strrchr "; + name[StatInt_strncmp] = " strncmp "; + name[StatInt_strcpy] = " strcpy "; + name[StatInt_strncpy] = " strncpy "; + name[StatInt_strstr] = " strstr "; + name[StatInt_atexit] = " atexit "; + name[StatInt___cxa_guard_acquire] = " __cxa_guard_acquire "; + name[StatInt___cxa_guard_release] = " __cxa_guard_release "; + name[StatInt_pthread_create] = " pthread_create "; + name[StatInt_pthread_join] = " pthread_join "; + name[StatInt_pthread_detach] = " pthread_detach "; + name[StatInt_pthread_mutex_init] = " pthread_mutex_init "; + name[StatInt_pthread_mutex_destroy] = " pthread_mutex_destroy "; + name[StatInt_pthread_mutex_lock] = " pthread_mutex_lock "; + name[StatInt_pthread_mutex_trylock] = " pthread_mutex_trylock "; + name[StatInt_pthread_mutex_timedlock] = " pthread_mutex_timedlock "; + name[StatInt_pthread_mutex_unlock] = " pthread_mutex_unlock "; + name[StatInt_pthread_spin_init] = " pthread_spin_init "; + name[StatInt_pthread_spin_destroy] = " pthread_spin_destroy "; + name[StatInt_pthread_spin_lock] = " pthread_spin_lock "; + name[StatInt_pthread_spin_trylock] = " pthread_spin_trylock "; + name[StatInt_pthread_spin_unlock] = " pthread_spin_unlock "; + name[StatInt_pthread_rwlock_init] = " pthread_rwlock_init "; + name[StatInt_pthread_rwlock_destroy] = " pthread_rwlock_destroy "; + name[StatInt_pthread_rwlock_rdlock] = " pthread_rwlock_rdlock "; + name[StatInt_pthread_rwlock_tryrdlock] = " pthread_rwlock_tryrdlock "; + name[StatInt_pthread_rwlock_timedrdlock] + = " pthread_rwlock_timedrdlock "; + name[StatInt_pthread_rwlock_wrlock] = " pthread_rwlock_wrlock "; + name[StatInt_pthread_rwlock_trywrlock] = " pthread_rwlock_trywrlock "; + name[StatInt_pthread_rwlock_timedwrlock] + = " pthread_rwlock_timedwrlock "; + name[StatInt_pthread_rwlock_unlock] = " pthread_rwlock_unlock "; + name[StatInt_pthread_cond_init] = " pthread_cond_init "; + name[StatInt_pthread_cond_destroy] = " pthread_cond_destroy "; + name[StatInt_pthread_cond_signal] = " pthread_cond_signal "; + name[StatInt_pthread_cond_broadcast] = " pthread_cond_broadcast "; + name[StatInt_pthread_cond_wait] = " pthread_cond_wait "; + name[StatInt_pthread_cond_timedwait] = " pthread_cond_timedwait "; + name[StatInt_pthread_barrier_init] = " pthread_barrier_init "; + name[StatInt_pthread_barrier_destroy] = " pthread_barrier_destroy "; + name[StatInt_pthread_barrier_wait] = " pthread_barrier_wait "; + name[StatInt_pthread_once] = " pthread_once "; + name[StatInt_sem_init] = " sem_init "; + name[StatInt_sem_destroy] = " sem_destroy "; + name[StatInt_sem_wait] = " sem_wait "; + name[StatInt_sem_trywait] = " sem_trywait "; + name[StatInt_sem_timedwait] = " sem_timedwait "; + name[StatInt_sem_post] = " sem_post "; + name[StatInt_sem_getvalue] = " sem_getvalue "; + name[StatInt_read] = " read "; + name[StatInt_pread] = " pread "; + name[StatInt_pread64] = " pread64 "; + name[StatInt_readv] = " readv "; + name[StatInt_preadv64] = " preadv64 "; + name[StatInt_write] = " write "; + name[StatInt_pwrite] = " pwrite "; + name[StatInt_pwrite64] = " pwrite64 "; + name[StatInt_writev] = " writev "; + name[StatInt_pwritev64] = " pwritev64 "; + name[StatInt_send] = " send "; + name[StatInt_sendmsg] = " sendmsg "; + name[StatInt_recv] = " recv "; + name[StatInt_recvmsg] = " recvmsg "; + name[StatInt_unlink] = " unlink "; + name[StatInt_fopen] = " fopen "; + name[StatInt_fread] = " fread "; + name[StatInt_fwrite] = " fwrite "; + name[StatInt_puts] = " puts "; + name[StatInt_rmdir] = " rmdir "; + name[StatInt_opendir] = " opendir "; + name[StatInt_epoll_ctl] = " epoll_ctl "; + name[StatInt_epoll_wait] = " epoll_wait "; + name[StatInt_sigaction] = " sigaction "; + + name[StatAnnotation] = "Dynamic annotations "; + name[StatAnnotateHappensBefore] = " HappensBefore "; + name[StatAnnotateHappensAfter] = " HappensAfter "; + name[StatAnnotateCondVarSignal] = " CondVarSignal "; + name[StatAnnotateCondVarSignalAll] = " CondVarSignalAll "; + name[StatAnnotateMutexIsNotPHB] = " MutexIsNotPHB "; + name[StatAnnotateCondVarWait] = " CondVarWait "; + name[StatAnnotateRWLockCreate] = " RWLockCreate "; + name[StatAnnotateRWLockDestroy] = " RWLockDestroy "; + name[StatAnnotateRWLockAcquired] = " RWLockAcquired "; + name[StatAnnotateRWLockReleased] = " RWLockReleased "; + name[StatAnnotateTraceMemory] = " TraceMemory "; + name[StatAnnotateFlushState] = " FlushState "; + name[StatAnnotateNewMemory] = " NewMemory "; + name[StatAnnotateNoOp] = " NoOp "; + name[StatAnnotateFlushExpectedRaces] = " FlushExpectedRaces "; + name[StatAnnotateEnableRaceDetection] = " EnableRaceDetection "; + name[StatAnnotateMutexIsUsedAsCondVar] = " MutexIsUsedAsCondVar "; + name[StatAnnotatePCQGet] = " PCQGet "; + name[StatAnnotatePCQPut] = " PCQPut "; + name[StatAnnotatePCQDestroy] = " PCQDestroy "; + name[StatAnnotatePCQCreate] = " PCQCreate "; + name[StatAnnotateExpectRace] = " ExpectRace "; + name[StatAnnotateBenignRaceSized] = " BenignRaceSized "; + name[StatAnnotateBenignRace] = " BenignRace "; + name[StatAnnotateIgnoreReadsBegin] = " IgnoreReadsBegin "; + name[StatAnnotateIgnoreReadsEnd] = " IgnoreReadsEnd "; + name[StatAnnotateIgnoreWritesBegin] = " IgnoreWritesBegin "; + name[StatAnnotateIgnoreWritesEnd] = " IgnoreWritesEnd "; + name[StatAnnotatePublishMemoryRange] = " PublishMemoryRange "; + name[StatAnnotateUnpublishMemoryRange] = " UnpublishMemoryRange "; + name[StatAnnotateThreadName] = " ThreadName "; + + name[StatMtxTotal] = "Contentionz "; + name[StatMtxTrace] = " Trace "; + name[StatMtxThreads] = " Threads "; + name[StatMtxReport] = " Report "; + name[StatMtxSyncVar] = " SyncVar "; + name[StatMtxSyncTab] = " SyncTab "; + name[StatMtxSlab] = " Slab "; + name[StatMtxAtExit] = " Atexit "; + name[StatMtxAnnotations] = " Annotations "; + + TsanPrintf("Statistics:\n"); + for (int i = 0; i < StatCnt; i++) + TsanPrintf("%s: %zu\n", name[i], (uptr)stat[i]); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_stat.h b/lib/tsan/rtl/tsan_stat.h new file mode 100644 index 0000000..71b1b13 --- /dev/null +++ b/lib/tsan/rtl/tsan_stat.h @@ -0,0 +1,254 @@ +//===-- tsan_stat.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#ifndef TSAN_STAT_H +#define TSAN_STAT_H + +namespace __tsan { + +enum StatType { + // Memory access processing related stuff. + StatMop, + StatMopRead, + StatMopWrite, + StatMop1, // These must be consequtive. + StatMop2, + StatMop4, + StatMop8, + StatMopSame, + StatMopRange, + StatShadowProcessed, + StatShadowZero, + StatShadowNonZero, // Derived. + StatShadowSameSize, + StatShadowIntersect, + StatShadowNotIntersect, + StatShadowSameThread, + StatShadowAnotherThread, + StatShadowReplace, + + // Func processing. + StatFuncEnter, + StatFuncExit, + + // Trace processing. + StatEvents, + + // Threads. + StatThreadCreate, + StatThreadFinish, + StatThreadReuse, + StatThreadMaxTid, + StatThreadMaxAlive, + + // Mutexes. + StatMutexCreate, + StatMutexDestroy, + StatMutexLock, + StatMutexUnlock, + StatMutexRecLock, + StatMutexRecUnlock, + StatMutexReadLock, + StatMutexReadUnlock, + + // Synchronization. + StatSyncCreated, + StatSyncDestroyed, + StatSyncAcquire, + StatSyncRelease, + + // Atomics. + StatAtomic, + StatAtomicLoad, + StatAtomicStore, + StatAtomicExchange, + StatAtomicFetchAdd, + StatAtomicFetchAnd, + StatAtomicFetchOr, + StatAtomicFetchXor, + StatAtomicCAS, + StatAtomicFence, + StatAtomicRelaxed, + StatAtomicConsume, + StatAtomicAcquire, + StatAtomicRelease, + StatAtomicAcq_Rel, + StatAtomicSeq_Cst, + StatAtomic1, + StatAtomic2, + StatAtomic4, + StatAtomic8, + + // Interceptors. + StatInterceptor, + StatInt_longjmp, + StatInt_siglongjmp, + StatInt_malloc, + StatInt_calloc, + StatInt_realloc, + StatInt_free, + StatInt_cfree, + StatInt_mmap, + StatInt_mmap64, + StatInt_munmap, + StatInt_memalign, + StatInt_valloc, + StatInt_pvalloc, + StatInt_posix_memalign, + StatInt__Znwm, + StatInt__ZnwmRKSt9nothrow_t, + StatInt__Znam, + StatInt__ZnamRKSt9nothrow_t, + StatInt__ZdlPv, + StatInt__ZdlPvRKSt9nothrow_t, + StatInt__ZdaPv, + StatInt__ZdaPvRKSt9nothrow_t, + StatInt_strlen, + StatInt_memset, + StatInt_memcpy, + StatInt_strcmp, + StatInt_memchr, + StatInt_memrchr, + StatInt_memmove, + StatInt_memcmp, + StatInt_strchr, + StatInt_strchrnul, + StatInt_strrchr, + StatInt_strncmp, + StatInt_strcpy, + StatInt_strncpy, + StatInt_strstr, + StatInt_atexit, + StatInt___cxa_guard_acquire, + StatInt___cxa_guard_release, + StatInt_pthread_create, + StatInt_pthread_join, + StatInt_pthread_detach, + StatInt_pthread_mutex_init, + StatInt_pthread_mutex_destroy, + StatInt_pthread_mutex_lock, + StatInt_pthread_mutex_trylock, + StatInt_pthread_mutex_timedlock, + StatInt_pthread_mutex_unlock, + StatInt_pthread_spin_init, + StatInt_pthread_spin_destroy, + StatInt_pthread_spin_lock, + StatInt_pthread_spin_trylock, + StatInt_pthread_spin_unlock, + StatInt_pthread_rwlock_init, + StatInt_pthread_rwlock_destroy, + StatInt_pthread_rwlock_rdlock, + StatInt_pthread_rwlock_tryrdlock, + StatInt_pthread_rwlock_timedrdlock, + StatInt_pthread_rwlock_wrlock, + StatInt_pthread_rwlock_trywrlock, + StatInt_pthread_rwlock_timedwrlock, + StatInt_pthread_rwlock_unlock, + StatInt_pthread_cond_init, + StatInt_pthread_cond_destroy, + StatInt_pthread_cond_signal, + StatInt_pthread_cond_broadcast, + StatInt_pthread_cond_wait, + StatInt_pthread_cond_timedwait, + StatInt_pthread_barrier_init, + StatInt_pthread_barrier_destroy, + StatInt_pthread_barrier_wait, + StatInt_pthread_once, + StatInt_sem_init, + StatInt_sem_destroy, + StatInt_sem_wait, + StatInt_sem_trywait, + StatInt_sem_timedwait, + StatInt_sem_post, + StatInt_sem_getvalue, + StatInt_read, + StatInt_pread, + StatInt_pread64, + StatInt_readv, + StatInt_preadv64, + StatInt_write, + StatInt_pwrite, + StatInt_pwrite64, + StatInt_writev, + StatInt_pwritev64, + StatInt_send, + StatInt_sendmsg, + StatInt_recv, + StatInt_recvmsg, + StatInt_unlink, + StatInt_fopen, + StatInt_fread, + StatInt_fwrite, + StatInt_puts, + StatInt_rmdir, + StatInt_opendir, + StatInt_epoll_ctl, + StatInt_epoll_wait, + StatInt_sigaction, + StatInt_signal, + StatInt_raise, + StatInt_kill, + StatInt_pthread_kill, + + // Dynamic annotations. + StatAnnotation, + StatAnnotateHappensBefore, + StatAnnotateHappensAfter, + StatAnnotateCondVarSignal, + StatAnnotateCondVarSignalAll, + StatAnnotateMutexIsNotPHB, + StatAnnotateCondVarWait, + StatAnnotateRWLockCreate, + StatAnnotateRWLockDestroy, + StatAnnotateRWLockAcquired, + StatAnnotateRWLockReleased, + StatAnnotateTraceMemory, + StatAnnotateFlushState, + StatAnnotateNewMemory, + StatAnnotateNoOp, + StatAnnotateFlushExpectedRaces, + StatAnnotateEnableRaceDetection, + StatAnnotateMutexIsUsedAsCondVar, + StatAnnotatePCQGet, + StatAnnotatePCQPut, + StatAnnotatePCQDestroy, + StatAnnotatePCQCreate, + StatAnnotateExpectRace, + StatAnnotateBenignRaceSized, + StatAnnotateBenignRace, + StatAnnotateIgnoreReadsBegin, + StatAnnotateIgnoreReadsEnd, + StatAnnotateIgnoreWritesBegin, + StatAnnotateIgnoreWritesEnd, + StatAnnotatePublishMemoryRange, + StatAnnotateUnpublishMemoryRange, + StatAnnotateThreadName, + + // Internal mutex contentionz. + StatMtxTotal, + StatMtxTrace, + StatMtxThreads, + StatMtxReport, + StatMtxSyncVar, + StatMtxSyncTab, + StatMtxSlab, + StatMtxAnnotations, + StatMtxAtExit, + + // This must be the last. + StatCnt, +}; + +} // namespace __tsan + +#endif // TSAN_STAT_H diff --git a/lib/tsan/rtl/tsan_suppressions.cc b/lib/tsan/rtl/tsan_suppressions.cc new file mode 100644 index 0000000..7549a4f --- /dev/null +++ b/lib/tsan/rtl/tsan_suppressions.cc @@ -0,0 +1,163 @@ +//===-- tsan_suppressions.cc ----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_suppressions.h" +#include "tsan_rtl.h" +#include "tsan_flags.h" +#include "tsan_mman.h" +#include "tsan_platform.h" + +namespace __tsan { + +static Suppression *g_suppressions; + +static char *ReadFile(const char *filename) { + if (filename == 0 || filename[0] == 0) + return 0; + InternalScopedBuf tmp(4*1024); + if (filename[0] == '/') + internal_snprintf(tmp, tmp.Size(), "%s", filename); + else + internal_snprintf(tmp, tmp.Size(), "%s/%s", GetPwd(), filename); + fd_t fd = internal_open(tmp, false); + if (fd == kInvalidFd) { + TsanPrintf("ThreadSanitizer: failed to open suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + const uptr fsize = internal_filesize(fd); + if (fsize == (uptr)-1) { + TsanPrintf("ThreadSanitizer: failed to stat suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + char *buf = (char*)internal_alloc(MBlockSuppression, fsize + 1); + if (fsize != internal_read(fd, buf, fsize)) { + TsanPrintf("ThreadSanitizer: failed to read suppressions file '%s'\n", + tmp.Ptr()); + Die(); + } + internal_close(fd); + buf[fsize] = 0; + return buf; +} + +bool SuppressionMatch(char *templ, const char *str) { + if (str == 0 || str[0] == 0) + return false; + char *tpos; + const char *spos; + while (templ && templ[0]) { + if (templ[0] == '*') { + templ++; + continue; + } + if (str[0] == 0) + return false; + tpos = (char*)internal_strchr(templ, '*'); + if (tpos != 0) + tpos[0] = 0; + spos = internal_strstr(str, templ); + str = spos + internal_strlen(templ); + templ = tpos; + if (tpos) + tpos[0] = '*'; + if (spos == 0) + return false; + } + return true; +} + +Suppression *SuppressionParse(const char* supp) { + Suppression *head = 0; + const char *line = supp; + while (line) { + while (line[0] == ' ' || line[0] == '\t') + line++; + const char *end = internal_strchr(line, '\n'); + if (end == 0) + end = line + internal_strlen(line); + if (line != end && line[0] != '#') { + const char *end2 = end; + while (line != end2 && (end2[-1] == ' ' || end2[-1] == '\t')) + end2--; + SuppressionType stype; + if (0 == internal_strncmp(line, "race:", sizeof("race:") - 1)) { + stype = SuppressionRace; + line += sizeof("race:") - 1; + } else if (0 == internal_strncmp(line, "thread:", + sizeof("thread:") - 1)) { + stype = SuppressionThread; + line += sizeof("thread:") - 1; + } else if (0 == internal_strncmp(line, "mutex:", + sizeof("mutex:") - 1)) { + stype = SuppressionMutex; + line += sizeof("mutex:") - 1; + } else if (0 == internal_strncmp(line, "signal:", + sizeof("signal:") - 1)) { + stype = SuppressionSignal; + line += sizeof("signal:") - 1; + } else { + TsanPrintf("ThreadSanitizer: failed to parse suppressions file\n"); + Die(); + } + Suppression *s = (Suppression*)internal_alloc(MBlockSuppression, + sizeof(Suppression)); + s->next = head; + head = s; + s->type = stype; + s->templ = (char*)internal_alloc(MBlockSuppression, end2 - line + 1); + internal_memcpy(s->templ, line, end2 - line); + s->templ[end2 - line] = 0; + } + if (end[0] == 0) + break; + line = end + 1; + } + return head; +} + +void InitializeSuppressions() { + char *supp = ReadFile(flags()->suppressions); + g_suppressions = SuppressionParse(supp); +} + +bool IsSuppressed(ReportType typ, const ReportStack *stack) { + if (g_suppressions == 0 || stack == 0) + return false; + SuppressionType stype; + if (typ == ReportTypeRace) + stype = SuppressionRace; + else if (typ == ReportTypeThreadLeak) + stype = SuppressionThread; + else if (typ == ReportTypeMutexDestroyLocked) + stype = SuppressionMutex; + else if (typ == ReportTypeSignalUnsafe) + stype = SuppressionSignal; + else + return false; + for (const ReportStack *frame = stack; frame; frame = frame->next) { + for (Suppression *supp = g_suppressions; supp; supp = supp->next) { + if (stype == supp->type && + (SuppressionMatch(supp->templ, frame->func) || + SuppressionMatch(supp->templ, frame->file))) { + DPrintf("ThreadSanitizer: matched suppression '%s'\n", supp->templ); + return true; + } + } + } + return false; +} +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_suppressions.h b/lib/tsan/rtl/tsan_suppressions.h new file mode 100644 index 0000000..29311c1 --- /dev/null +++ b/lib/tsan/rtl/tsan_suppressions.h @@ -0,0 +1,43 @@ +//===-- tsan_suppressions.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SUPPRESSIONS_H +#define TSAN_SUPPRESSIONS_H + +#include "tsan_report.h" + +namespace __tsan { + +void InitializeSuppressions(); +void FinalizeSuppressions(); +bool IsSuppressed(ReportType typ, const ReportStack *stack); + +// Exposed for testing. +enum SuppressionType { + SuppressionRace, + SuppressionMutex, + SuppressionThread, + SuppressionSignal, +}; + +struct Suppression { + Suppression *next; + SuppressionType type; + char *templ; +}; + +Suppression *SuppressionParse(const char* supp); +bool SuppressionMatch(char *templ, const char *str); + +} // namespace __tsan + +#endif // TSAN_SUPPRESSIONS_H diff --git a/lib/tsan/rtl/tsan_symbolize.cc b/lib/tsan/rtl/tsan_symbolize.cc new file mode 100644 index 0000000..f757d07 --- /dev/null +++ b/lib/tsan/rtl/tsan_symbolize.cc @@ -0,0 +1,78 @@ +//===-- tsan_symbolize.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_symbolize.h" + +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_placement_new.h" +#include "sanitizer_common/sanitizer_symbolizer.h" +#include "tsan_flags.h" +#include "tsan_report.h" + +namespace __tsan { + +ReportStack *NewReportStackEntry(uptr addr) { + ReportStack *ent = (ReportStack*)internal_alloc(MBlockReportStack, + sizeof(ReportStack)); + internal_memset(ent, 0, sizeof(*ent)); + ent->pc = addr; + return ent; +} + +static ReportStack *NewReportStackEntry(const AddressInfo &info) { + ReportStack *ent = NewReportStackEntry(info.address); + if (info.module) + ent->module = internal_strdup(info.module); + ent->offset = info.module_offset; + if (info.function) { + ent->func = internal_strdup(info.function); + } + if (info.file) + ent->file = internal_strdup(info.file); + ent->line = info.line; + ent->col = info.column; + return ent; +} + +ReportStack *SymbolizeCode(uptr addr) { + if (flags()->use_internal_symbolizer) { + static const uptr kMaxAddrFrames = 16; + InternalScopedBuf addr_frames(kMaxAddrFrames); + for (uptr i = 0; i < kMaxAddrFrames; i++) + new(&addr_frames[i]) AddressInfo(); + uptr addr_frames_num = __sanitizer::SymbolizeCode(addr, addr_frames, + kMaxAddrFrames); + if (addr_frames_num == 0) + return NewReportStackEntry(addr); + ReportStack *top = 0; + ReportStack *bottom = 0; + for (uptr i = 0; i < addr_frames_num; i++) { + ReportStack *cur_entry = NewReportStackEntry(addr_frames[i]); + CHECK(cur_entry); + addr_frames[i].Clear(); + if (i == 0) + top = cur_entry; + else + bottom->next = cur_entry; + bottom = cur_entry; + } + return top; + } + return SymbolizeCodeAddr2Line(addr); +} + +ReportStack *SymbolizeData(uptr addr) { + return SymbolizeDataAddr2Line(addr); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_symbolize.h b/lib/tsan/rtl/tsan_symbolize.h new file mode 100644 index 0000000..115339b --- /dev/null +++ b/lib/tsan/rtl/tsan_symbolize.h @@ -0,0 +1,31 @@ +//===-- tsan_symbolize.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SYMBOLIZE_H +#define TSAN_SYMBOLIZE_H + +#include "tsan_defs.h" +#include "tsan_report.h" + +namespace __tsan { + +ReportStack *SymbolizeCode(uptr addr); +ReportStack *SymbolizeData(uptr addr); + +ReportStack *SymbolizeCodeAddr2Line(uptr addr); +ReportStack *SymbolizeDataAddr2Line(uptr addr); + +ReportStack *NewReportStackEntry(uptr addr); + +} // namespace __tsan + +#endif // TSAN_SYMBOLIZE_H diff --git a/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc new file mode 100644 index 0000000..5eed977 --- /dev/null +++ b/lib/tsan/rtl/tsan_symbolize_addr2line_linux.cc @@ -0,0 +1,193 @@ +//===-- tsan_symbolize_addr2line.cc ---------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_symbolize.h" +#include "tsan_mman.h" +#include "tsan_rtl.h" +#include "tsan_platform.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace __tsan { + +struct ModuleDesc { + const char *fullname; + const char *name; + uptr base; + int inp_fd; + int out_fd; +}; + +struct SectionDesc { + SectionDesc *next; + ModuleDesc *module; + uptr base; + uptr end; +}; + +struct DlIteratePhdrCtx { + SectionDesc *sections; + bool is_first; +}; + +static void NOINLINE InitModule(ModuleDesc *m) { + int outfd[2] = {}; + if (pipe(&outfd[0])) { + TsanPrintf("ThreadSanitizer: outfd pipe() failed (%d)\n", errno); + Die(); + } + int infd[2] = {}; + if (pipe(&infd[0])) { + TsanPrintf("ThreadSanitizer: infd pipe() failed (%d)\n", errno); + Die(); + } + int pid = fork(); + if (pid == 0) { + flags()->log_fileno = STDERR_FILENO; + internal_close(STDOUT_FILENO); + internal_close(STDIN_FILENO); + internal_dup2(outfd[0], STDIN_FILENO); + internal_dup2(infd[1], STDOUT_FILENO); + internal_close(outfd[0]); + internal_close(outfd[1]); + internal_close(infd[0]); + internal_close(infd[1]); + for (int fd = getdtablesize(); fd > 2; fd--) + internal_close(fd); + execl("/usr/bin/addr2line", "/usr/bin/addr2line", "-Cfe", m->fullname, 0); + _exit(0); + } else if (pid < 0) { + TsanPrintf("ThreadSanitizer: failed to fork symbolizer\n"); + Die(); + } + internal_close(outfd[0]); + internal_close(infd[1]); + m->inp_fd = infd[0]; + m->out_fd = outfd[1]; +} + +static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { + DlIteratePhdrCtx *ctx = (DlIteratePhdrCtx*)arg; + InternalScopedBuf tmp(128); + if (ctx->is_first) { + internal_snprintf(tmp.Ptr(), tmp.Size(), "/proc/%d/exe", GetPid()); + info->dlpi_name = tmp.Ptr(); + } + ctx->is_first = false; + if (info->dlpi_name == 0 || info->dlpi_name[0] == 0) + return 0; + ModuleDesc *m = (ModuleDesc*)internal_alloc(MBlockReportStack, + sizeof(ModuleDesc)); + m->fullname = internal_strdup(info->dlpi_name); + m->name = internal_strrchr(m->fullname, '/'); + if (m->name) + m->name += 1; + else + m->name = m->fullname; + m->base = (uptr)info->dlpi_addr; + m->inp_fd = -1; + m->out_fd = -1; + DPrintf("Module %s %zx\n", m->name, m->base); + for (int i = 0; i < info->dlpi_phnum; i++) { + const Elf64_Phdr *s = &info->dlpi_phdr[i]; + DPrintf(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx" + " p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n", + (uptr)s->p_type, (uptr)s->p_offset, (uptr)s->p_vaddr, + (uptr)s->p_paddr, (uptr)s->p_filesz, (uptr)s->p_memsz, + (uptr)s->p_flags, (uptr)s->p_align); + if (s->p_type != PT_LOAD) + continue; + SectionDesc *sec = (SectionDesc*)internal_alloc(MBlockReportStack, + sizeof(SectionDesc)); + sec->module = m; + sec->base = info->dlpi_addr + s->p_vaddr; + sec->end = sec->base + s->p_memsz; + sec->next = ctx->sections; + ctx->sections = sec; + DPrintf(" Section %zx-%zx\n", sec->base, sec->end); + } + return 0; +} + +static SectionDesc *InitSections() { + DlIteratePhdrCtx ctx = {0, true}; + dl_iterate_phdr(dl_iterate_phdr_cb, &ctx); + return ctx.sections; +} + +static SectionDesc *GetSectionDesc(uptr addr) { + static SectionDesc *sections = 0; + if (sections == 0) + sections = InitSections(); + for (SectionDesc *s = sections; s; s = s->next) { + if (addr >= s->base && addr < s->end) { + if (s->module->inp_fd == -1) + InitModule(s->module); + return s; + } + } + return 0; +} + +ReportStack *SymbolizeCodeAddr2Line(uptr addr) { + SectionDesc *s = GetSectionDesc(addr); + if (s == 0) + return NewReportStackEntry(addr); + ModuleDesc *m = s->module; + uptr offset = addr - m->base; + char addrstr[32]; + internal_snprintf(addrstr, sizeof(addrstr), "%p\n", (void*)offset); + if (0 >= internal_write(m->out_fd, addrstr, internal_strlen(addrstr))) { + TsanPrintf("ThreadSanitizer: can't write from symbolizer (%d, %d)\n", + m->out_fd, errno); + Die(); + } + InternalScopedBuf func(1024); + ssize_t len = internal_read(m->inp_fd, func, func.Size() - 1); + if (len <= 0) { + TsanPrintf("ThreadSanitizer: can't read from symbolizer (%d, %d)\n", + m->inp_fd, errno); + Die(); + } + func.Ptr()[len] = 0; + ReportStack *res = NewReportStackEntry(addr); + res->module = internal_strdup(m->name); + res->offset = offset; + char *pos = (char*)internal_strchr(func, '\n'); + if (pos && func[0] != '?') { + res->func = (char*)internal_alloc(MBlockReportStack, pos - func + 1); + internal_memcpy(res->func, func, pos - func); + res->func[pos - func] = 0; + char *pos2 = (char*)internal_strchr(pos, ':'); + if (pos2) { + res->file = (char*)internal_alloc(MBlockReportStack, pos2 - pos - 1 + 1); + internal_memcpy(res->file, pos + 1, pos2 - pos - 1); + res->file[pos2 - pos - 1] = 0; + res->line = atoi(pos2 + 1); + } + } + return res; +} + +ReportStack *SymbolizeDataAddr2Line(uptr addr) { + return 0; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_sync.cc b/lib/tsan/rtl/tsan_sync.cc new file mode 100644 index 0000000..abb5a2a --- /dev/null +++ b/lib/tsan/rtl/tsan_sync.cc @@ -0,0 +1,219 @@ +//===-- tsan_sync.cc ------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_placement_new.h" +#include "tsan_sync.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" + +namespace __tsan { + +SyncVar::SyncVar(uptr addr) + : mtx(MutexTypeSyncVar, StatMtxSyncVar) + , addr(addr) + , owner_tid(kInvalidTid) + , recursion() + , is_rw() + , is_recursive() + , is_broken() { +} + +SyncTab::Part::Part() + : mtx(MutexTypeSyncTab, StatMtxSyncTab) + , val() { +} + +SyncTab::SyncTab() { +} + +SyncTab::~SyncTab() { + for (int i = 0; i < kPartCount; i++) { + while (tab_[i].val) { + SyncVar *tmp = tab_[i].val; + tab_[i].val = tmp->next; + DestroyAndFree(tmp); + } + } +} + +SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock) { + Part *p = &tab_[PartIdx(addr)]; + { + ReadLock l(&p->mtx); + for (SyncVar *res = p->val; res; res = res->next) { + if (res->addr == addr) { + if (write_lock) + res->mtx.Lock(); + else + res->mtx.ReadLock(); + return res; + } + } + } + { + Lock l(&p->mtx); + SyncVar *res = p->val; + for (; res; res = res->next) { + if (res->addr == addr) + break; + } + if (res == 0) { + StatInc(thr, StatSyncCreated); + void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); + res = new(mem) SyncVar(addr); +#ifndef TSAN_GO + res->creation_stack.ObtainCurrent(thr, pc); +#endif + res->next = p->val; + p->val = res; + } + if (write_lock) + res->mtx.Lock(); + else + res->mtx.ReadLock(); + return res; + } +} + +SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) { + Part *p = &tab_[PartIdx(addr)]; + SyncVar *res = 0; + { + Lock l(&p->mtx); + SyncVar **prev = &p->val; + res = *prev; + while (res) { + if (res->addr == addr) { + *prev = res->next; + break; + } + prev = &res->next; + res = *prev; + } + } + if (res) { + StatInc(thr, StatSyncDestroyed); + res->mtx.Lock(); + res->mtx.Unlock(); + } + return res; +} + +uptr SyncVar::GetMemoryConsumption() { + return sizeof(*this) + + clock.size() * sizeof(u64) + + read_clock.size() * sizeof(u64) + + creation_stack.Size() * sizeof(uptr); +} + +uptr SyncTab::GetMemoryConsumption(uptr *nsync) { + uptr mem = 0; + for (int i = 0; i < kPartCount; i++) { + Part *p = &tab_[i]; + Lock l(&p->mtx); + for (SyncVar *s = p->val; s; s = s->next) { + *nsync += 1; + mem += s->GetMemoryConsumption(); + } + } + return mem; +} + +int SyncTab::PartIdx(uptr addr) { + return (addr >> 3) % kPartCount; +} + +StackTrace::StackTrace() + : n_() + , s_() + , c_() { +} + +StackTrace::StackTrace(uptr *buf, uptr cnt) + : n_() + , s_(buf) + , c_(cnt) { + CHECK_NE(buf, 0); + CHECK_NE(cnt, 0); +} + +StackTrace::~StackTrace() { + Reset(); +} + +void StackTrace::Reset() { + if (s_ && !c_) { + CHECK_NE(n_, 0); + internal_free(s_); + s_ = 0; + } + n_ = 0; +} + +void StackTrace::Init(const uptr *pcs, uptr cnt) { + Reset(); + if (cnt == 0) + return; + if (c_) { + CHECK_NE(s_, 0); + CHECK_LE(cnt, c_); + } else { + s_ = (uptr*)internal_alloc(MBlockStackTrace, cnt * sizeof(s_[0])); + } + n_ = cnt; + internal_memcpy(s_, pcs, cnt * sizeof(s_[0])); +} + +void StackTrace::ObtainCurrent(ThreadState *thr, uptr toppc) { + Reset(); + n_ = thr->shadow_stack_pos - thr->shadow_stack; + if (n_ + !!toppc == 0) + return; + if (c_) { + CHECK_NE(s_, 0); + CHECK_LE(n_ + !!toppc, c_); + } else { + s_ = (uptr*)internal_alloc(MBlockStackTrace, + (n_ + !!toppc) * sizeof(s_[0])); + } + for (uptr i = 0; i < n_; i++) + s_[i] = thr->shadow_stack[i]; + if (toppc) { + s_[n_] = toppc; + n_++; + } +} + +void StackTrace::CopyFrom(const StackTrace& other) { + Reset(); + Init(other.Begin(), other.Size()); +} + +bool StackTrace::IsEmpty() const { + return n_ == 0; +} + +uptr StackTrace::Size() const { + return n_; +} + +uptr StackTrace::Get(uptr i) const { + CHECK_LT(i, n_); + return s_[i]; +} + +const uptr *StackTrace::Begin() const { + return s_; +} + +} // namespace __tsan diff --git a/lib/tsan/rtl/tsan_sync.h b/lib/tsan/rtl/tsan_sync.h new file mode 100644 index 0000000..34d3e0b --- /dev/null +++ b/lib/tsan/rtl/tsan_sync.h @@ -0,0 +1,106 @@ +//===-- tsan_sync.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_SYNC_H +#define TSAN_SYNC_H + +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" +#include "tsan_clock.h" +#include "tsan_defs.h" +#include "tsan_mutex.h" + +namespace __tsan { + +class SlabCache; + +class StackTrace { + public: + StackTrace(); + // Initialized the object in "static mode", + // in this mode it never calls malloc/free but uses the provided buffer. + StackTrace(uptr *buf, uptr cnt); + ~StackTrace(); + void Reset(); + + void Init(const uptr *pcs, uptr cnt); + void ObtainCurrent(ThreadState *thr, uptr toppc); + bool IsEmpty() const; + uptr Size() const; + uptr Get(uptr i) const; + const uptr *Begin() const; + void CopyFrom(const StackTrace& other); + + private: + uptr n_; + uptr *s_; + const uptr c_; + + StackTrace(const StackTrace&); + void operator = (const StackTrace&); +}; + +struct SyncVar { + explicit SyncVar(uptr addr); + + static const int kInvalidTid = -1; + + Mutex mtx; + const uptr addr; + SyncClock clock; + SyncClock read_clock; // Used for rw mutexes only. + StackTrace creation_stack; + int owner_tid; // Set only by exclusive owners. + int recursion; + bool is_rw; + bool is_recursive; + bool is_broken; + SyncVar *next; // In SyncTab hashtable. + + uptr GetMemoryConsumption(); +}; + +class SyncTab { + public: + SyncTab(); + ~SyncTab(); + + // If the SyncVar does not exist yet, it is created. + SyncVar* GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock); + + // If the SyncVar does not exist, returns 0. + SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr); + + uptr GetMemoryConsumption(uptr *nsync); + + private: + struct Part { + Mutex mtx; + SyncVar *val; + char pad[kCacheLineSize - sizeof(Mutex) - sizeof(SyncVar*)]; // NOLINT + Part(); + }; + + // FIXME: Implement something more sane. + static const int kPartCount = 1009; + Part tab_[kPartCount]; + + int PartIdx(uptr addr); + + SyncTab(const SyncTab&); // Not implemented. + void operator = (const SyncTab&); // Not implemented. +}; + +} // namespace __tsan + +#endif // TSAN_SYNC_H diff --git a/lib/tsan/rtl/tsan_trace.h b/lib/tsan/rtl/tsan_trace.h new file mode 100644 index 0000000..bf15bf5 --- /dev/null +++ b/lib/tsan/rtl/tsan_trace.h @@ -0,0 +1,71 @@ +//===-- tsan_trace.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#ifndef TSAN_TRACE_H +#define TSAN_TRACE_H + +#include "tsan_defs.h" +#include "tsan_mutex.h" +#include "tsan_sync.h" + +namespace __tsan { + +const int kTraceParts = 8; +const int kTraceSize = 128*1024; +const int kTracePartSize = kTraceSize / kTraceParts; + +// Must fit into 3 bits. +enum EventType { + EventTypeMop, + EventTypeFuncEnter, + EventTypeFuncExit, + EventTypeLock, + EventTypeUnlock, + EventTypeRLock, + EventTypeRUnlock, +}; + +// Represents a thread event (from most significant bit): +// u64 typ : 3; // EventType. +// u64 addr : 61; // Associated pc. +typedef u64 Event; + +struct TraceHeader { + StackTrace stack0; // Start stack for the trace. + u64 epoch0; // Start epoch for the trace. +#ifndef TSAN_GO + uptr stack0buf[kShadowStackSize]; +#endif + + TraceHeader() +#ifndef TSAN_GO + : stack0(stack0buf, kShadowStackSize) +#else + : stack0() +#endif + , epoch0() { + } +}; + +struct Trace { + Event events[kTraceSize]; + TraceHeader headers[kTraceParts]; + Mutex mtx; + + Trace() + : mtx(MutexTypeTrace, StatMtxTrace) { + } +}; + +} // namespace __tsan + +#endif // TSAN_TRACE_H diff --git a/lib/tsan/rtl/tsan_update_shadow_word_inl.h b/lib/tsan/rtl/tsan_update_shadow_word_inl.h new file mode 100644 index 0000000..c7864ce --- /dev/null +++ b/lib/tsan/rtl/tsan_update_shadow_word_inl.h @@ -0,0 +1,79 @@ +//===-- tsan_update_shadow_word_inl.h ---------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Body of the hottest inner loop. +// If we wrap this body into a function, compilers (both gcc and clang) +// produce sligtly less efficient code. +//===----------------------------------------------------------------------===// +do { + StatInc(thr, StatShadowProcessed); + const unsigned kAccessSize = 1 << kAccessSizeLog; + unsigned off = cur.ComputeSearchOffset(); + u64 *sp = &shadow_mem[(idx + off) % kShadowCnt]; + old = LoadShadow(sp); + if (old.IsZero()) { + StatInc(thr, StatShadowZero); + if (store_word) + StoreIfNotYetStored(sp, &store_word); + // The above StoreIfNotYetStored could be done unconditionally + // and it even shows 4% gain on synthetic benchmarks (r4307). + break; + } + // is the memory access equal to the previous? + if (Shadow::Addr0AndSizeAreEqual(cur, old)) { + StatInc(thr, StatShadowSameSize); + // same thread? + if (Shadow::TidsAreEqual(old, cur)) { + StatInc(thr, StatShadowSameThread); + if (OldIsInSameSynchEpoch(old, thr)) { + if (OldIsRWStronger(old, kAccessIsWrite)) { + // found a slot that holds effectively the same info + // (that is, same tid, same sync epoch and same size) + StatInc(thr, StatMopSame); + return; + } + StoreIfNotYetStored(sp, &store_word); + break; + } + if (OldIsRWWeaker(old, kAccessIsWrite)) + StoreIfNotYetStored(sp, &store_word); + break; + } + StatInc(thr, StatShadowAnotherThread); + if (HappensBefore(old, thr)) { + StoreIfNotYetStored(sp, &store_word); + break; + } + if (BothReads(old, kAccessIsWrite)) + break; + goto RACE; + } + + // Do the memory access intersect? + if (Shadow::TwoRangesIntersect(old, cur, kAccessSize)) { + StatInc(thr, StatShadowIntersect); + if (Shadow::TidsAreEqual(old, cur)) { + StatInc(thr, StatShadowSameThread); + break; + } + StatInc(thr, StatShadowAnotherThread); + if (HappensBefore(old, thr)) + break; + + if (BothReads(old, kAccessIsWrite)) + break; + + goto RACE; + } + // The accesses do not intersect. + StatInc(thr, StatShadowNotIntersect); + break; +} while (0); diff --git a/lib/tsan/rtl/tsan_vector.h b/lib/tsan/rtl/tsan_vector.h new file mode 100644 index 0000000..d41063d --- /dev/null +++ b/lib/tsan/rtl/tsan_vector.h @@ -0,0 +1,110 @@ +//===-- tsan_vector.h -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +// Low-fat STL-like vector container. + +#ifndef TSAN_VECTOR_H +#define TSAN_VECTOR_H + +#include "tsan_defs.h" +#include "tsan_mman.h" + +namespace __tsan { + +template +class Vector { + public: + explicit Vector(MBlockType typ) + : typ_(typ) + , begin_() + , end_() + , last_() { + } + + ~Vector() { + if (begin_) + internal_free(begin_); + } + + void Reset() { + if (begin_) + internal_free(begin_); + begin_ = 0; + end_ = 0; + last_ = 0; + } + + uptr Size() const { + return end_ - begin_; + } + + T &operator[](uptr i) { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + const T &operator[](uptr i) const { + DCHECK_LT(i, end_ - begin_); + return begin_[i]; + } + + T *PushBack(T v = T()) { + EnsureSize(Size() + 1); + end_[-1] = v; + return &end_[-1]; + } + + void Resize(uptr size) { + uptr old_size = Size(); + EnsureSize(size); + if (old_size < size) { + for (uptr i = old_size; i < size; i++) + begin_[i] = T(); + } + } + + private: + const MBlockType typ_; + T *begin_; + T *end_; + T *last_; + + void EnsureSize(uptr size) { + if (size <= Size()) + return; + if (size <= (uptr)(last_ - begin_)) { + end_ = begin_ + size; + return; + } + uptr cap0 = last_ - begin_; + uptr cap = 2 * cap0; + if (cap == 0) + cap = 16; + if (cap < size) + cap = size; + T *p = (T*)internal_alloc(typ_, cap * sizeof(T)); + if (cap0) { + internal_memcpy(p, begin_, cap0 * sizeof(T)); + internal_free(begin_); + } + begin_ = p; + end_ = begin_ + size; + last_ = begin_ + cap; + } + + Vector(const Vector&); + void operator=(const Vector&); +}; +} + +#endif // #ifndef TSAN_VECTOR_H diff --git a/lib/tsan/rtl_tests/tsan_bench.cc b/lib/tsan/rtl_tests/tsan_bench.cc new file mode 100644 index 0000000..a3cf22f --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_bench.cc @@ -0,0 +1,105 @@ +//===-- tsan_bench.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_test_util.h" +#include "tsan_interface.h" +#include "tsan_defs.h" +#include "gtest/gtest.h" +#include + +const int kSize = 128; +const int kRepeat = 2*1024*1024; + +void noinstr(void *p) {} + +template +static void Benchmark() { + volatile T data[kSize]; + for (int i = 0; i < kRepeat; i++) { + for (int j = 0; j < kSize; j++) { + __tsan_mop((void*)&data[j]); + data[j]++; + } + } +} + +TEST(DISABLED_BENCH, Mop1) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop1Read) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop1Write) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop2) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop2Read) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop2Write) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop4) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop4Read) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop4Write) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop8) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop8Read) { + Benchmark(); +} + +TEST(DISABLED_BENCH, Mop8Write) { + Benchmark(); +} + +TEST(DISABLED_BENCH, FuncCall) { + for (int i = 0; i < kRepeat; i++) { + for (int j = 0; j < kSize; j++) + __tsan_func_entry((void*)(uintptr_t)j); + for (int j = 0; j < kSize; j++) + __tsan_func_exit(); + } +} + +TEST(DISABLED_BENCH, MutexLocal) { + Mutex m; + ScopedThread().Create(m); + for (int i = 0; i < 50; i++) { + ScopedThread t; + t.Lock(m); + t.Unlock(m); + } + for (int i = 0; i < 16*1024*1024; i++) { + m.Lock(); + m.Unlock(); + } + ScopedThread().Destroy(m); +} diff --git a/lib/tsan/rtl_tests/tsan_mop.cc b/lib/tsan/rtl_tests/tsan_mop.cc new file mode 100644 index 0000000..f217428 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_mop.cc @@ -0,0 +1,233 @@ +//===-- tsan_mop.cc -------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_interface.h" +#include "tsan_test_util.h" +#include "gtest/gtest.h" +#include +#include + +TEST(ThreadSanitizer, SimpleWrite) { + ScopedThread t; + MemLoc l; + t.Write1(l); +} + +TEST(ThreadSanitizer, SimpleWriteWrite) { + ScopedThread t1, t2; + MemLoc l1, l2; + t1.Write1(l1); + t2.Write1(l2); +} + +TEST(ThreadSanitizer, WriteWriteRace) { + ScopedThread t1, t2; + MemLoc l; + t1.Write1(l); + t2.Write1(l, true); +} + +TEST(ThreadSanitizer, ReadWriteRace) { + ScopedThread t1, t2; + MemLoc l; + t1.Read1(l); + t2.Write1(l, true); +} + +TEST(ThreadSanitizer, WriteReadRace) { + ScopedThread t1, t2; + MemLoc l; + t1.Write1(l); + t2.Read1(l, true); +} + +TEST(ThreadSanitizer, ReadReadNoRace) { + ScopedThread t1, t2; + MemLoc l; + t1.Read1(l); + t2.Read1(l); +} + +TEST(ThreadSanitizer, WriteThenRead) { + MemLoc l; + ScopedThread t1, t2; + t1.Write1(l); + t1.Read1(l); + t2.Read1(l, true); +} + +TEST(ThreadSanitizer, WriteThenLockedRead) { + Mutex m(Mutex::RW); + MainThread t0; + t0.Create(m); + MemLoc l; + { + ScopedThread t1, t2; + + t1.Write8(l); + + t1.Lock(m); + t1.Read8(l); + t1.Unlock(m); + + t2.Read8(l, true); + } + t0.Destroy(m); +} + +TEST(ThreadSanitizer, LockedWriteThenRead) { + Mutex m(Mutex::RW); + MainThread t0; + t0.Create(m); + MemLoc l; + { + ScopedThread t1, t2; + + t1.Lock(m); + t1.Write8(l); + t1.Unlock(m); + + t1.Read8(l); + + t2.Read8(l, true); + } + t0.Destroy(m); +} + + +TEST(ThreadSanitizer, RaceWithOffset) { + ScopedThread t1, t2; + { + MemLoc l; + t1.Access(l.loc(), true, 8, false); + t2.Access((char*)l.loc() + 4, true, 4, true); + } + { + MemLoc l; + t1.Access(l.loc(), true, 8, false); + t2.Access((char*)l.loc() + 7, true, 1, true); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 4, true, 4, false); + t2.Access((char*)l.loc() + 4, true, 2, true); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 4, true, 4, false); + t2.Access((char*)l.loc() + 6, true, 2, true); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 3, true, 2, false); + t2.Access((char*)l.loc() + 4, true, 1, true); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 1, true, 8, false); + t2.Access((char*)l.loc() + 3, true, 1, true); + } +} + +TEST(ThreadSanitizer, RaceWithOffset2) { + ScopedThread t1, t2; + { + MemLoc l; + t1.Access((char*)l.loc(), true, 4, false); + t2.Access((char*)l.loc() + 2, true, 1, true); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 2, true, 1, false); + t2.Access((char*)l.loc(), true, 4, true); + } +} + +TEST(ThreadSanitizer, NoRaceWithOffset) { + ScopedThread t1, t2; + { + MemLoc l; + t1.Access(l.loc(), true, 4, false); + t2.Access((char*)l.loc() + 4, true, 4, false); + } + { + MemLoc l; + t1.Access((char*)l.loc() + 3, true, 2, false); + t2.Access((char*)l.loc() + 1, true, 2, false); + t2.Access((char*)l.loc() + 5, true, 2, false); + } +} + +TEST(ThreadSanitizer, RaceWithDeadThread) { + MemLoc l; + ScopedThread t; + ScopedThread().Write1(l); + t.Write1(l, true); +} + +TEST(ThreadSanitizer, BenignRaceOnVptr) { + void *vptr_storage; + MemLoc vptr(&vptr_storage), val; + vptr_storage = val.loc(); + ScopedThread t1, t2; + t1.VptrUpdate(vptr, val); + t2.Read8(vptr); +} + +TEST(ThreadSanitizer, HarmfulRaceOnVptr) { + void *vptr_storage; + MemLoc vptr(&vptr_storage), val1, val2; + vptr_storage = val1.loc(); + ScopedThread t1, t2; + t1.VptrUpdate(vptr, val2); + t2.Read8(vptr, true); +} + +static void foo() { + volatile int x = 42; + int x2 = x; + (void)x2; +} + +static void bar() { + volatile int x = 43; + int x2 = x; + (void)x2; +} + +TEST(ThreadSanitizer, ReportDeadThread) { + MemLoc l; + ScopedThread t1; + { + ScopedThread t2; + t2.Call(&foo); + t2.Call(&bar); + t2.Write1(l); + } + t1.Write1(l, true); +} + +struct ClassWithStatic { + static int Data[4]; +}; + +int ClassWithStatic::Data[4]; + +static void foobarbaz() {} + +TEST(ThreadSanitizer, ReportRace) { + ScopedThread t1; + MainThread().Access(&ClassWithStatic::Data, true, 4, false); + t1.Call(&foobarbaz); + t1.Access(&ClassWithStatic::Data, true, 2, true); + t1.Return(); +} diff --git a/lib/tsan/rtl_tests/tsan_mutex.cc b/lib/tsan/rtl_tests/tsan_mutex.cc new file mode 100644 index 0000000..4d9c779 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_mutex.cc @@ -0,0 +1,221 @@ +//===-- tsan_mutex.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_atomic.h" +#include "tsan_interface.h" +#include "tsan_interface_ann.h" +#include "tsan_test_util.h" +#include "gtest/gtest.h" +#include + +namespace __tsan { + +TEST(ThreadSanitizer, BasicMutex) { + ScopedThread t; + Mutex m; + t.Create(m); + + t.Lock(m); + t.Unlock(m); + + CHECK(t.TryLock(m)); + t.Unlock(m); + + t.Lock(m); + CHECK(!t.TryLock(m)); + t.Unlock(m); + + t.Destroy(m); +} + +TEST(ThreadSanitizer, BasicSpinMutex) { + ScopedThread t; + Mutex m(Mutex::Spin); + t.Create(m); + + t.Lock(m); + t.Unlock(m); + + CHECK(t.TryLock(m)); + t.Unlock(m); + + t.Lock(m); + CHECK(!t.TryLock(m)); + t.Unlock(m); + + t.Destroy(m); +} + +TEST(ThreadSanitizer, BasicRwMutex) { + ScopedThread t; + Mutex m(Mutex::RW); + t.Create(m); + + t.Lock(m); + t.Unlock(m); + + CHECK(t.TryLock(m)); + t.Unlock(m); + + t.Lock(m); + CHECK(!t.TryLock(m)); + t.Unlock(m); + + t.ReadLock(m); + t.ReadUnlock(m); + + CHECK(t.TryReadLock(m)); + t.ReadUnlock(m); + + t.Lock(m); + CHECK(!t.TryReadLock(m)); + t.Unlock(m); + + t.ReadLock(m); + CHECK(!t.TryLock(m)); + t.ReadUnlock(m); + + t.ReadLock(m); + CHECK(t.TryReadLock(m)); + t.ReadUnlock(m); + t.ReadUnlock(m); + + t.Destroy(m); +} + +TEST(ThreadSanitizer, Mutex) { + Mutex m; + MainThread t0; + t0.Create(m); + + ScopedThread t1, t2; + MemLoc l; + t1.Lock(m); + t1.Write1(l); + t1.Unlock(m); + t2.Lock(m); + t2.Write1(l); + t2.Unlock(m); + t2.Destroy(m); +} + +TEST(ThreadSanitizer, SpinMutex) { + Mutex m(Mutex::Spin); + MainThread t0; + t0.Create(m); + + ScopedThread t1, t2; + MemLoc l; + t1.Lock(m); + t1.Write1(l); + t1.Unlock(m); + t2.Lock(m); + t2.Write1(l); + t2.Unlock(m); + t2.Destroy(m); +} + +TEST(ThreadSanitizer, RwMutex) { + Mutex m(Mutex::RW); + MainThread t0; + t0.Create(m); + + ScopedThread t1, t2, t3; + MemLoc l; + t1.Lock(m); + t1.Write1(l); + t1.Unlock(m); + t2.Lock(m); + t2.Write1(l); + t2.Unlock(m); + t1.ReadLock(m); + t3.ReadLock(m); + t1.Read1(l); + t3.Read1(l); + t1.ReadUnlock(m); + t3.ReadUnlock(m); + t2.Lock(m); + t2.Write1(l); + t2.Unlock(m); + t2.Destroy(m); +} + +TEST(ThreadSanitizer, StaticMutex) { + // Emulates statically initialized mutex. + Mutex m; + m.StaticInit(); + { + ScopedThread t1, t2; + t1.Lock(m); + t1.Unlock(m); + t2.Lock(m); + t2.Unlock(m); + } + MainThread().Destroy(m); +} + +static void *singleton_thread(void *param) { + atomic_uintptr_t *singleton = (atomic_uintptr_t *)param; + for (int i = 0; i < 4*1024*1024; i++) { + int *val = (int *)atomic_load(singleton, memory_order_acquire); + __tsan_acquire(singleton); + __tsan_read4(val); + CHECK_EQ(*val, 42); + } + return 0; +} + +TEST(DISABLED_BENCH_ThreadSanitizer, Singleton) { + const int kClockSize = 100; + const int kThreadCount = 8; + + // Puff off thread's clock. + for (int i = 0; i < kClockSize; i++) { + ScopedThread t1; + (void)t1; + } + // Create the singleton. + int val = 42; + __tsan_write4(&val); + atomic_uintptr_t singleton; + __tsan_release(&singleton); + atomic_store(&singleton, (uintptr_t)&val, memory_order_release); + // Create reader threads. + pthread_t threads[kThreadCount]; + for (int t = 0; t < kThreadCount; t++) + pthread_create(&threads[t], 0, singleton_thread, &singleton); + for (int t = 0; t < kThreadCount; t++) + pthread_join(threads[t], 0); +} + +TEST(DISABLED_BENCH_ThreadSanitizer, StopFlag) { + const int kClockSize = 100; + const int kIters = 16*1024*1024; + + // Puff off thread's clock. + for (int i = 0; i < kClockSize; i++) { + ScopedThread t1; + (void)t1; + } + // Create the stop flag. + atomic_uintptr_t flag; + __tsan_release(&flag); + atomic_store(&flag, 0, memory_order_release); + // Read it a lot. + for (int i = 0; i < kIters; i++) { + uptr v = atomic_load(&flag, memory_order_acquire); + __tsan_acquire(&flag); + CHECK_EQ(v, 0); + } +} + +} // namespace __tsan diff --git a/lib/tsan/rtl_tests/tsan_posix.cc b/lib/tsan/rtl_tests/tsan_posix.cc new file mode 100644 index 0000000..0caedd7 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_posix.cc @@ -0,0 +1,146 @@ +//===-- tsan_posix.cc -----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_interface.h" +#include "tsan_test_util.h" +#include "gtest/gtest.h" +#include + +struct thread_key { + pthread_key_t key; + pthread_mutex_t *mtx; + int val; + int *cnt; + thread_key(pthread_key_t key, pthread_mutex_t *mtx, int val, int *cnt) + : key(key) + , mtx(mtx) + , val(val) + , cnt(cnt) { + } +}; + +static void thread_secific_dtor(void *v) { + thread_key *k = (thread_key *)v; + EXPECT_EQ(pthread_mutex_lock(k->mtx), 0); + (*k->cnt)++; + __tsan_write4(&k->cnt); + EXPECT_EQ(pthread_mutex_unlock(k->mtx), 0); + if (k->val == 42) { + delete k; + } else if (k->val == 43 || k->val == 44) { + k->val--; + EXPECT_EQ(pthread_setspecific(k->key, k), 0); + } else { + ASSERT_TRUE(false); + } +} + +static void *dtors_thread(void *p) { + thread_key *k = (thread_key *)p; + EXPECT_EQ(pthread_setspecific(k->key, k), 0); + return 0; +} + +TEST(Posix, ThreadSpecificDtors) { + int cnt = 0; + pthread_key_t key; + EXPECT_EQ(pthread_key_create(&key, thread_secific_dtor), 0); + pthread_mutex_t mtx; + EXPECT_EQ(pthread_mutex_init(&mtx, 0), 0); + pthread_t th[3]; + thread_key *k[3]; + k[0] = new thread_key(key, &mtx, 42, &cnt); + k[1] = new thread_key(key, &mtx, 43, &cnt); + k[2] = new thread_key(key, &mtx, 44, &cnt); + EXPECT_EQ(pthread_create(&th[0], 0, dtors_thread, k[0]), 0); + EXPECT_EQ(pthread_create(&th[1], 0, dtors_thread, k[1]), 0); + EXPECT_EQ(pthread_join(th[0], 0), 0); + EXPECT_EQ(pthread_create(&th[2], 0, dtors_thread, k[2]), 0); + EXPECT_EQ(pthread_join(th[1], 0), 0); + EXPECT_EQ(pthread_join(th[2], 0), 0); + EXPECT_EQ(pthread_key_delete(key), 0); + EXPECT_EQ(6, cnt); +} + +static __thread int local_var; + +static void *local_thread(void *p) { + __tsan_write1(&local_var); + __tsan_write1(&p); + if (p == 0) + return 0; + const int kThreads = 4; + pthread_t th[kThreads]; + for (int i = 0; i < kThreads; i++) + EXPECT_EQ(pthread_create(&th[i], 0, local_thread, + (void*)((long)p - 1)), 0); // NOLINT + for (int i = 0; i < kThreads; i++) + EXPECT_EQ(pthread_join(th[i], 0), 0); + return 0; +} + +TEST(Posix, ThreadLocalAccesses) { + local_thread((void*)2); +} + +struct CondContext { + pthread_mutex_t m; + pthread_cond_t c; + int data; +}; + +static void *cond_thread(void *p) { + CondContext &ctx = *static_cast(p); + + EXPECT_EQ(pthread_mutex_lock(&ctx.m), 0); + EXPECT_EQ(ctx.data, 0); + ctx.data = 1; + EXPECT_EQ(pthread_cond_signal(&ctx.c), 0); + EXPECT_EQ(pthread_mutex_unlock(&ctx.m), 0); + + EXPECT_EQ(pthread_mutex_lock(&ctx.m), 0); + while (ctx.data != 2) + EXPECT_EQ(pthread_cond_wait(&ctx.c, &ctx.m), 0); + EXPECT_EQ(pthread_mutex_unlock(&ctx.m), 0); + + EXPECT_EQ(pthread_mutex_lock(&ctx.m), 0); + ctx.data = 3; + EXPECT_EQ(pthread_cond_broadcast(&ctx.c), 0); + EXPECT_EQ(pthread_mutex_unlock(&ctx.m), 0); + + return 0; +} + +TEST(Posix, CondBasic) { + CondContext ctx; + EXPECT_EQ(pthread_mutex_init(&ctx.m, 0), 0); + EXPECT_EQ(pthread_cond_init(&ctx.c, 0), 0); + ctx.data = 0; + pthread_t th; + EXPECT_EQ(pthread_create(&th, 0, cond_thread, &ctx), 0); + + EXPECT_EQ(pthread_mutex_lock(&ctx.m), 0); + while (ctx.data != 1) + EXPECT_EQ(pthread_cond_wait(&ctx.c, &ctx.m), 0); + ctx.data = 2; + EXPECT_EQ(pthread_mutex_unlock(&ctx.m), 0); + EXPECT_EQ(pthread_cond_broadcast(&ctx.c), 0); + + EXPECT_EQ(pthread_mutex_lock(&ctx.m), 0); + while (ctx.data != 3) + EXPECT_EQ(pthread_cond_wait(&ctx.c, &ctx.m), 0); + EXPECT_EQ(pthread_mutex_unlock(&ctx.m), 0); + + EXPECT_EQ(pthread_join(th, 0), 0); + EXPECT_EQ(pthread_cond_destroy(&ctx.c), 0); + EXPECT_EQ(pthread_mutex_destroy(&ctx.m), 0); +} diff --git a/lib/tsan/rtl_tests/tsan_string.cc b/lib/tsan/rtl_tests/tsan_string.cc new file mode 100644 index 0000000..75adc6c --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_string.cc @@ -0,0 +1,82 @@ +//===-- tsan_string.cc ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_test_util.h" +#include "gtest/gtest.h" +#include + +namespace __tsan { + +TEST(ThreadSanitizer, Memcpy) { + char data0[7] = {1, 2, 3, 4, 5, 6, 7}; + char data[7] = {42, 42, 42, 42, 42, 42, 42}; + MainThread().Memcpy(data+1, data0+1, 5); + EXPECT_EQ(data[0], 42); + EXPECT_EQ(data[1], 2); + EXPECT_EQ(data[2], 3); + EXPECT_EQ(data[3], 4); + EXPECT_EQ(data[4], 5); + EXPECT_EQ(data[5], 6); + EXPECT_EQ(data[6], 42); + MainThread().Memset(data+1, 13, 5); + EXPECT_EQ(data[0], 42); + EXPECT_EQ(data[1], 13); + EXPECT_EQ(data[2], 13); + EXPECT_EQ(data[3], 13); + EXPECT_EQ(data[4], 13); + EXPECT_EQ(data[5], 13); + EXPECT_EQ(data[6], 42); +} + +TEST(ThreadSanitizer, MemcpyRace1) { + char *data = new char[10]; + char *data1 = new char[10]; + char *data2 = new char[10]; + ScopedThread t1, t2; + t1.Memcpy(data, data1, 10); + t2.Memcpy(data, data2, 10, true); +} + +TEST(ThreadSanitizer, MemcpyRace2) { + char *data = new char[10]; + char *data1 = new char[10]; + char *data2 = new char[10]; + ScopedThread t1, t2; + t1.Memcpy(data+5, data1, 1); + t2.Memcpy(data+3, data2, 4, true); +} + +TEST(ThreadSanitizer, MemcpyRace3) { + char *data = new char[10]; + char *data1 = new char[10]; + char *data2 = new char[10]; + ScopedThread t1, t2; + t1.Memcpy(data, data1, 10); + t2.Memcpy(data1, data2, 10, true); +} + +TEST(ThreadSanitizer, MemcpyStack) { + char *data = new char[10]; + char *data1 = new char[10]; + ScopedThread t1, t2; + t1.Memcpy(data, data1, 10); + t2.Memcpy(data, data1, 10, true); +} + +TEST(ThreadSanitizer, MemsetRace1) { + char *data = new char[10]; + ScopedThread t1, t2; + t1.Memset(data, 1, 10); + t2.Memset(data, 2, 10, true); +} + +} // namespace __tsan diff --git a/lib/tsan/rtl_tests/tsan_test.cc b/lib/tsan/rtl_tests/tsan_test.cc new file mode 100644 index 0000000..7164140 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_test.cc @@ -0,0 +1,44 @@ +//===-- tsan_test.cc ------------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_interface.h" +#include "tsan_test_util.h" +#include "gtest/gtest.h" + +static void foo() {} +static void bar() {} + +TEST(ThreadSanitizer, FuncCall) { + ScopedThread t1, t2; + MemLoc l; + t1.Write1(l); + t2.Call(foo); + t2.Call(bar); + t2.Write1(l, true); + t2.Return(); + t2.Return(); +} + +int main(int argc, char **argv) { + TestMutexBeforeInit(); // Mutexes must be usable before __tsan_init(); + __tsan_init(); + __tsan_func_entry(__builtin_return_address(0)); + __tsan_func_entry((char*)&main + 1); + + testing::GTEST_FLAG(death_test_style) = "threadsafe"; + testing::InitGoogleTest(&argc, argv); + int res = RUN_ALL_TESTS(); + + __tsan_func_exit(); + __tsan_func_exit(); + return res; +} diff --git a/lib/tsan/rtl_tests/tsan_test_util.h b/lib/tsan/rtl_tests/tsan_test_util.h new file mode 100644 index 0000000..483a564 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_test_util.h @@ -0,0 +1,122 @@ +//===-- tsan_test_util.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Test utils. +//===----------------------------------------------------------------------===// +#ifndef TSAN_TEST_UTIL_H +#define TSAN_TEST_UTIL_H + +void TestMutexBeforeInit(); + +// A location of memory on which a race may be detected. +class MemLoc { + public: + explicit MemLoc(int offset_from_aligned = 0); + explicit MemLoc(void *const real_addr) : loc_(real_addr) { } + ~MemLoc(); + void *loc() const { return loc_; } + private: + void *const loc_; + MemLoc(const MemLoc&); + void operator = (const MemLoc&); +}; + +class Mutex { + public: + enum Type { Normal, Spin, RW }; + + explicit Mutex(Type type = Normal); + ~Mutex(); + + void Init(); + void StaticInit(); // Emulates static initalization (tsan invisible). + void Destroy(); + void Lock(); + bool TryLock(); + void Unlock(); + void ReadLock(); + bool TryReadLock(); + void ReadUnlock(); + + private: + // Placeholder for pthread_mutex_t, CRITICAL_SECTION or whatever. + void *mtx_[128]; + bool alive_; + const Type type_; + + Mutex(const Mutex&); + void operator = (const Mutex&); +}; + +// A thread is started in CTOR and joined in DTOR. +class ScopedThread { + public: + explicit ScopedThread(bool detached = false, bool main = false); + ~ScopedThread(); + void Detach(); + + void Access(void *addr, bool is_write, int size, bool expect_race); + void Read(const MemLoc &ml, int size, bool expect_race = false) { + Access(ml.loc(), false, size, expect_race); + } + void Write(const MemLoc &ml, int size, bool expect_race = false) { + Access(ml.loc(), true, size, expect_race); + } + void Read1(const MemLoc &ml, bool expect_race = false) { + Read(ml, 1, expect_race); } + void Read2(const MemLoc &ml, bool expect_race = false) { + Read(ml, 2, expect_race); } + void Read4(const MemLoc &ml, bool expect_race = false) { + Read(ml, 4, expect_race); } + void Read8(const MemLoc &ml, bool expect_race = false) { + Read(ml, 8, expect_race); } + void Write1(const MemLoc &ml, bool expect_race = false) { + Write(ml, 1, expect_race); } + void Write2(const MemLoc &ml, bool expect_race = false) { + Write(ml, 2, expect_race); } + void Write4(const MemLoc &ml, bool expect_race = false) { + Write(ml, 4, expect_race); } + void Write8(const MemLoc &ml, bool expect_race = false) { + Write(ml, 8, expect_race); } + + void VptrUpdate(const MemLoc &vptr, const MemLoc &new_val, + bool expect_race = false); + + void Call(void(*pc)()); + void Return(); + + void Create(const Mutex &m); + void Destroy(const Mutex &m); + void Lock(const Mutex &m); + bool TryLock(const Mutex &m); + void Unlock(const Mutex &m); + void ReadLock(const Mutex &m); + bool TryReadLock(const Mutex &m); + void ReadUnlock(const Mutex &m); + + void Memcpy(void *dst, const void *src, int size, bool expect_race = false); + void Memset(void *dst, int val, int size, bool expect_race = false); + + private: + struct Impl; + Impl *impl_; + ScopedThread(const ScopedThread&); // Not implemented. + void operator = (const ScopedThread&); // Not implemented. +}; + +class MainThread : public ScopedThread { + public: + MainThread() + : ScopedThread(false, true) { + } +}; + +#endif // #ifndef TSAN_TEST_UTIL_H diff --git a/lib/tsan/rtl_tests/tsan_test_util_linux.cc b/lib/tsan/rtl_tests/tsan_test_util_linux.cc new file mode 100644 index 0000000..5bc393b --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_test_util_linux.cc @@ -0,0 +1,465 @@ + +//===-- tsan_test_util_linux.cc -------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Test utils, linux implementation. +//===----------------------------------------------------------------------===// + +#include "sanitizer_common/sanitizer_atomic.h" +#include "tsan_interface.h" +#include "tsan_test_util.h" +#include "tsan_report.h" + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace __tsan; // NOLINT + +static __thread bool expect_report; +static __thread bool expect_report_reported; +static __thread ReportType expect_report_type; + +extern "C" void *__interceptor_memcpy(void*, const void*, uptr); +extern "C" void *__interceptor_memset(void*, int, uptr); + +static void *BeforeInitThread(void *param) { + (void)param; + return 0; +} + +static void AtExit() { +} + +void TestMutexBeforeInit() { + // Mutexes must be usable before __tsan_init(); + pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; + pthread_mutex_lock(&mtx); + pthread_mutex_unlock(&mtx); + pthread_mutex_destroy(&mtx); + pthread_t thr; + pthread_create(&thr, 0, BeforeInitThread, 0); + pthread_join(thr, 0); + atexit(AtExit); +} + +namespace __tsan { +bool OnReport(const ReportDesc *rep, bool suppressed) { + if (expect_report) { + if (rep->typ != expect_report_type) { + printf("Expected report of type %d, got type %d\n", + (int)expect_report_type, (int)rep->typ); + EXPECT_FALSE("Wrong report type"); + return false; + } + } else { + EXPECT_FALSE("Unexpected report"); + return false; + } + expect_report_reported = true; + return true; +} +} + +static void* allocate_addr(int size, int offset_from_aligned = 0) { + static uintptr_t foo; + static atomic_uintptr_t uniq = {(uintptr_t)&foo}; // Some real address. + const int kAlign = 16; + CHECK(offset_from_aligned < kAlign); + size = (size + 2 * kAlign) & ~(kAlign - 1); + uintptr_t addr = atomic_fetch_add(&uniq, size, memory_order_relaxed); + return (void*)(addr + offset_from_aligned); +} + +MemLoc::MemLoc(int offset_from_aligned) + : loc_(allocate_addr(16, offset_from_aligned)) { +} + +MemLoc::~MemLoc() { +} + +Mutex::Mutex(Type type) + : alive_() + , type_(type) { +} + +Mutex::~Mutex() { + CHECK(!alive_); +} + +void Mutex::Init() { + CHECK(!alive_); + alive_ = true; + if (type_ == Normal) + CHECK_EQ(pthread_mutex_init((pthread_mutex_t*)mtx_, 0), 0); + else if (type_ == Spin) + CHECK_EQ(pthread_spin_init((pthread_spinlock_t*)mtx_, 0), 0); + else if (type_ == RW) + CHECK_EQ(pthread_rwlock_init((pthread_rwlock_t*)mtx_, 0), 0); + else + CHECK(0); +} + +void Mutex::StaticInit() { + CHECK(!alive_); + CHECK(type_ == Normal); + alive_ = true; + pthread_mutex_t tmp = PTHREAD_MUTEX_INITIALIZER; + memcpy(mtx_, &tmp, sizeof(tmp)); +} + +void Mutex::Destroy() { + CHECK(alive_); + alive_ = false; + if (type_ == Normal) + CHECK_EQ(pthread_mutex_destroy((pthread_mutex_t*)mtx_), 0); + else if (type_ == Spin) + CHECK_EQ(pthread_spin_destroy((pthread_spinlock_t*)mtx_), 0); + else if (type_ == RW) + CHECK_EQ(pthread_rwlock_destroy((pthread_rwlock_t*)mtx_), 0); +} + +void Mutex::Lock() { + CHECK(alive_); + if (type_ == Normal) + CHECK_EQ(pthread_mutex_lock((pthread_mutex_t*)mtx_), 0); + else if (type_ == Spin) + CHECK_EQ(pthread_spin_lock((pthread_spinlock_t*)mtx_), 0); + else if (type_ == RW) + CHECK_EQ(pthread_rwlock_wrlock((pthread_rwlock_t*)mtx_), 0); +} + +bool Mutex::TryLock() { + CHECK(alive_); + if (type_ == Normal) + return pthread_mutex_trylock((pthread_mutex_t*)mtx_) == 0; + else if (type_ == Spin) + return pthread_spin_trylock((pthread_spinlock_t*)mtx_) == 0; + else if (type_ == RW) + return pthread_rwlock_trywrlock((pthread_rwlock_t*)mtx_) == 0; + return false; +} + +void Mutex::Unlock() { + CHECK(alive_); + if (type_ == Normal) + CHECK_EQ(pthread_mutex_unlock((pthread_mutex_t*)mtx_), 0); + else if (type_ == Spin) + CHECK_EQ(pthread_spin_unlock((pthread_spinlock_t*)mtx_), 0); + else if (type_ == RW) + CHECK_EQ(pthread_rwlock_unlock((pthread_rwlock_t*)mtx_), 0); +} + +void Mutex::ReadLock() { + CHECK(alive_); + CHECK(type_ == RW); + CHECK_EQ(pthread_rwlock_rdlock((pthread_rwlock_t*)mtx_), 0); +} + +bool Mutex::TryReadLock() { + CHECK(alive_); + CHECK(type_ == RW); + return pthread_rwlock_tryrdlock((pthread_rwlock_t*)mtx_) == 0; +} + +void Mutex::ReadUnlock() { + CHECK(alive_); + CHECK(type_ == RW); + CHECK_EQ(pthread_rwlock_unlock((pthread_rwlock_t*)mtx_), 0); +} + +struct Event { + enum Type { + SHUTDOWN, + READ, + WRITE, + VPTR_UPDATE, + CALL, + RETURN, + MUTEX_CREATE, + MUTEX_DESTROY, + MUTEX_LOCK, + MUTEX_TRYLOCK, + MUTEX_UNLOCK, + MUTEX_READLOCK, + MUTEX_TRYREADLOCK, + MUTEX_READUNLOCK, + MEMCPY, + MEMSET + }; + Type type; + void *ptr; + uptr arg; + uptr arg2; + bool res; + bool expect_report; + ReportType report_type; + + Event(Type type, const void *ptr = 0, uptr arg = 0, uptr arg2 = 0) + : type(type) + , ptr(const_cast(ptr)) + , arg(arg) + , arg2(arg2) + , res() + , expect_report() + , report_type() { + } + + void ExpectReport(ReportType type) { + expect_report = true; + report_type = type; + } +}; + +struct ScopedThread::Impl { + pthread_t thread; + bool main; + bool detached; + atomic_uintptr_t event; // Event* + + static void *ScopedThreadCallback(void *arg); + void send(Event *ev); + void HandleEvent(Event *ev); +}; + +void ScopedThread::Impl::HandleEvent(Event *ev) { + CHECK_EQ(expect_report, false); + expect_report = ev->expect_report; + expect_report_reported = false; + expect_report_type = ev->report_type; + switch (ev->type) { + case Event::READ: + case Event::WRITE: { + void (*tsan_mop)(void *addr) = 0; + if (ev->type == Event::READ) { + switch (ev->arg /*size*/) { + case 1: tsan_mop = __tsan_read1; break; + case 2: tsan_mop = __tsan_read2; break; + case 4: tsan_mop = __tsan_read4; break; + case 8: tsan_mop = __tsan_read8; break; + case 16: tsan_mop = __tsan_read16; break; + } + } else { + switch (ev->arg /*size*/) { + case 1: tsan_mop = __tsan_write1; break; + case 2: tsan_mop = __tsan_write2; break; + case 4: tsan_mop = __tsan_write4; break; + case 8: tsan_mop = __tsan_write8; break; + case 16: tsan_mop = __tsan_write16; break; + } + } + CHECK_NE(tsan_mop, 0); + errno = ECHRNG; + tsan_mop(ev->ptr); + CHECK_EQ(errno, ECHRNG); // In no case must errno be changed. + break; + } + case Event::VPTR_UPDATE: + __tsan_vptr_update((void**)ev->ptr, (void*)ev->arg); + break; + case Event::CALL: + __tsan_func_entry((void*)((uptr)ev->ptr)); + break; + case Event::RETURN: + __tsan_func_exit(); + break; + case Event::MUTEX_CREATE: + static_cast(ev->ptr)->Init(); + break; + case Event::MUTEX_DESTROY: + static_cast(ev->ptr)->Destroy(); + break; + case Event::MUTEX_LOCK: + static_cast(ev->ptr)->Lock(); + break; + case Event::MUTEX_TRYLOCK: + ev->res = static_cast(ev->ptr)->TryLock(); + break; + case Event::MUTEX_UNLOCK: + static_cast(ev->ptr)->Unlock(); + break; + case Event::MUTEX_READLOCK: + static_cast(ev->ptr)->ReadLock(); + break; + case Event::MUTEX_TRYREADLOCK: + ev->res = static_cast(ev->ptr)->TryReadLock(); + break; + case Event::MUTEX_READUNLOCK: + static_cast(ev->ptr)->ReadUnlock(); + break; + case Event::MEMCPY: + __interceptor_memcpy(ev->ptr, (void*)ev->arg, ev->arg2); + break; + case Event::MEMSET: + __interceptor_memset(ev->ptr, ev->arg, ev->arg2); + break; + default: CHECK(0); + } + if (expect_report && !expect_report_reported) { + printf("Missed expected report of type %d\n", (int)ev->report_type); + EXPECT_FALSE("Missed expected race"); + } + expect_report = false; +} + +void *ScopedThread::Impl::ScopedThreadCallback(void *arg) { + __tsan_func_entry(__builtin_return_address(0)); + Impl *impl = (Impl*)arg; + for (;;) { + Event* ev = (Event*)atomic_load(&impl->event, memory_order_acquire); + if (ev == 0) { + pthread_yield(); + continue; + } + if (ev->type == Event::SHUTDOWN) { + atomic_store(&impl->event, 0, memory_order_release); + break; + } + impl->HandleEvent(ev); + atomic_store(&impl->event, 0, memory_order_release); + } + __tsan_func_exit(); + return 0; +} + +void ScopedThread::Impl::send(Event *e) { + if (main) { + HandleEvent(e); + } else { + CHECK_EQ(atomic_load(&event, memory_order_relaxed), 0); + atomic_store(&event, (uintptr_t)e, memory_order_release); + while (atomic_load(&event, memory_order_acquire) != 0) + pthread_yield(); + } +} + +ScopedThread::ScopedThread(bool detached, bool main) { + impl_ = new Impl; + impl_->main = main; + impl_->detached = detached; + atomic_store(&impl_->event, 0, memory_order_relaxed); + if (!main) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, detached); + pthread_attr_setstacksize(&attr, 64*1024); + pthread_create(&impl_->thread, &attr, + ScopedThread::Impl::ScopedThreadCallback, impl_); + } +} + +ScopedThread::~ScopedThread() { + if (!impl_->main) { + Event event(Event::SHUTDOWN); + impl_->send(&event); + if (!impl_->detached) + pthread_join(impl_->thread, 0); + } + delete impl_; +} + +void ScopedThread::Detach() { + CHECK(!impl_->main); + CHECK(!impl_->detached); + impl_->detached = true; + pthread_detach(impl_->thread); +} + +void ScopedThread::Access(void *addr, bool is_write, + int size, bool expect_race) { + Event event(is_write ? Event::WRITE : Event::READ, addr, size); + if (expect_race) + event.ExpectReport(ReportTypeRace); + impl_->send(&event); +} + +void ScopedThread::VptrUpdate(const MemLoc &vptr, + const MemLoc &new_val, + bool expect_race) { + Event event(Event::VPTR_UPDATE, vptr.loc(), (uptr)new_val.loc()); + if (expect_race) + event.ExpectReport(ReportTypeRace); + impl_->send(&event); +} + +void ScopedThread::Call(void(*pc)()) { + Event event(Event::CALL, (void*)pc); + impl_->send(&event); +} + +void ScopedThread::Return() { + Event event(Event::RETURN); + impl_->send(&event); +} + +void ScopedThread::Create(const Mutex &m) { + Event event(Event::MUTEX_CREATE, &m); + impl_->send(&event); +} + +void ScopedThread::Destroy(const Mutex &m) { + Event event(Event::MUTEX_DESTROY, &m); + impl_->send(&event); +} + +void ScopedThread::Lock(const Mutex &m) { + Event event(Event::MUTEX_LOCK, &m); + impl_->send(&event); +} + +bool ScopedThread::TryLock(const Mutex &m) { + Event event(Event::MUTEX_TRYLOCK, &m); + impl_->send(&event); + return event.res; +} + +void ScopedThread::Unlock(const Mutex &m) { + Event event(Event::MUTEX_UNLOCK, &m); + impl_->send(&event); +} + +void ScopedThread::ReadLock(const Mutex &m) { + Event event(Event::MUTEX_READLOCK, &m); + impl_->send(&event); +} + +bool ScopedThread::TryReadLock(const Mutex &m) { + Event event(Event::MUTEX_TRYREADLOCK, &m); + impl_->send(&event); + return event.res; +} + +void ScopedThread::ReadUnlock(const Mutex &m) { + Event event(Event::MUTEX_READUNLOCK, &m); + impl_->send(&event); +} + +void ScopedThread::Memcpy(void *dst, const void *src, int size, + bool expect_race) { + Event event(Event::MEMCPY, dst, (uptr)src, size); + if (expect_race) + event.ExpectReport(ReportTypeRace); + impl_->send(&event); +} + +void ScopedThread::Memset(void *dst, int val, int size, + bool expect_race) { + Event event(Event::MEMSET, dst, val, size); + if (expect_race) + event.ExpectReport(ReportTypeRace); + impl_->send(&event); +} diff --git a/lib/tsan/rtl_tests/tsan_thread.cc b/lib/tsan/rtl_tests/tsan_thread.cc new file mode 100644 index 0000000..5646415 --- /dev/null +++ b/lib/tsan/rtl_tests/tsan_thread.cc @@ -0,0 +1,59 @@ +//===-- tsan_thread.cc ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_test_util.h" +#include "gtest/gtest.h" + +TEST(ThreadSanitizer, ThreadSync) { + MainThread t0; + MemLoc l; + t0.Write1(l); + { + ScopedThread t1; + t1.Write1(l); + } + t0.Write1(l); +} + +TEST(ThreadSanitizer, ThreadDetach1) { + ScopedThread t1(true); + MemLoc l; + t1.Write1(l); +} + +TEST(ThreadSanitizer, ThreadDetach2) { + ScopedThread t1; + MemLoc l; + t1.Write1(l); + t1.Detach(); +} + +static void *thread_alot_func(void *arg) { + (void)arg; + int usleep(unsigned); + usleep(50); + return 0; +} + +TEST(DISABLED_SLOW_ThreadSanitizer, ThreadALot) { + const int kThreads = 70000; + const int kAlive = 1000; + pthread_t threads[kAlive] = {}; + for (int i = 0; i < kThreads; i++) { + if (threads[i % kAlive]) + pthread_join(threads[i % kAlive], 0); + pthread_create(&threads[i % kAlive], 0, thread_alot_func, 0); + } + for (int i = 0; i < kAlive; i++) { + pthread_join(threads[i], 0); + } +} diff --git a/lib/tsan/unit_tests/tsan_clock_test.cc b/lib/tsan/unit_tests/tsan_clock_test.cc new file mode 100644 index 0000000..fe886e1 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_clock_test.cc @@ -0,0 +1,123 @@ +//===-- tsan_clock_test.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_clock.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +namespace __tsan { + +TEST(Clock, VectorBasic) { + ScopedInRtl in_rtl; + ThreadClock clk; + CHECK_EQ(clk.size(), 0); + clk.tick(0); + CHECK_EQ(clk.size(), 1); + CHECK_EQ(clk.get(0), 1); + clk.tick(3); + CHECK_EQ(clk.size(), 4); + CHECK_EQ(clk.get(0), 1); + CHECK_EQ(clk.get(1), 0); + CHECK_EQ(clk.get(2), 0); + CHECK_EQ(clk.get(3), 1); + clk.tick(3); + CHECK_EQ(clk.get(3), 2); +} + +TEST(Clock, ChunkedBasic) { + ScopedInRtl in_rtl; + ThreadClock vector; + SyncClock chunked; + CHECK_EQ(vector.size(), 0); + CHECK_EQ(chunked.size(), 0); + vector.acquire(&chunked); + CHECK_EQ(vector.size(), 0); + CHECK_EQ(chunked.size(), 0); + vector.release(&chunked); + CHECK_EQ(vector.size(), 0); + CHECK_EQ(chunked.size(), 0); + vector.acq_rel(&chunked); + CHECK_EQ(vector.size(), 0); + CHECK_EQ(chunked.size(), 0); +} + +TEST(Clock, AcquireRelease) { + ScopedInRtl in_rtl; + ThreadClock vector1; + vector1.tick(100); + SyncClock chunked; + vector1.release(&chunked); + CHECK_EQ(chunked.size(), 101); + ThreadClock vector2; + vector2.acquire(&chunked); + CHECK_EQ(vector2.size(), 101); + CHECK_EQ(vector2.get(0), 0); + CHECK_EQ(vector2.get(1), 0); + CHECK_EQ(vector2.get(99), 0); + CHECK_EQ(vector2.get(100), 1); +} + +TEST(Clock, ManyThreads) { + ScopedInRtl in_rtl; + SyncClock chunked; + for (int i = 0; i < 100; i++) { + ThreadClock vector; + vector.tick(i); + vector.release(&chunked); + CHECK_EQ(chunked.size(), i + 1); + vector.acquire(&chunked); + CHECK_EQ(vector.size(), i + 1); + } + ThreadClock vector; + vector.acquire(&chunked); + CHECK_EQ(vector.size(), 100); + for (int i = 0; i < 100; i++) + CHECK_EQ(vector.get(i), 1); +} + +TEST(Clock, DifferentSizes) { + ScopedInRtl in_rtl; + { + ThreadClock vector1; + vector1.tick(10); + ThreadClock vector2; + vector2.tick(20); + { + SyncClock chunked; + vector1.release(&chunked); + CHECK_EQ(chunked.size(), 11); + vector2.release(&chunked); + CHECK_EQ(chunked.size(), 21); + } + { + SyncClock chunked; + vector2.release(&chunked); + CHECK_EQ(chunked.size(), 21); + vector1.release(&chunked); + CHECK_EQ(chunked.size(), 21); + } + { + SyncClock chunked; + vector1.release(&chunked); + vector2.acquire(&chunked); + CHECK_EQ(vector2.size(), 21); + } + { + SyncClock chunked; + vector2.release(&chunked); + vector1.acquire(&chunked); + CHECK_EQ(vector1.size(), 21); + } + } +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_flags_test.cc b/lib/tsan/unit_tests/tsan_flags_test.cc new file mode 100644 index 0000000..d344cb5 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_flags_test.cc @@ -0,0 +1,38 @@ +//===-- tsan_flags_test.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_flags.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +namespace __tsan { + +TEST(Flags, Basic) { + ScopedInRtl in_rtl; + // At least should not crash. + Flags f = {}; + InitializeFlags(&f, 0); + InitializeFlags(&f, ""); +} + +TEST(Flags, DefaultValues) { + ScopedInRtl in_rtl; + Flags f = {}; + + f.enable_annotations = false; + f.exitcode = -11; + InitializeFlags(&f, ""); + EXPECT_EQ(66, f.exitcode); + EXPECT_EQ(true, f.enable_annotations); +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_mman_test.cc b/lib/tsan/unit_tests/tsan_mman_test.cc new file mode 100644 index 0000000..1a9a88f --- /dev/null +++ b/lib/tsan/unit_tests/tsan_mman_test.cc @@ -0,0 +1,109 @@ +//===-- tsan_mman_test.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mman.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +namespace __tsan { + +TEST(Mman, Internal) { + ScopedInRtl in_rtl; + char *p = (char*)internal_alloc(MBlockScopedBuf, 10); + EXPECT_NE(p, (char*)0); + char *p2 = (char*)internal_alloc(MBlockScopedBuf, 20); + EXPECT_NE(p2, (char*)0); + EXPECT_NE(p2, p); + for (int i = 0; i < 10; i++) { + p[i] = 42; + } + for (int i = 0; i < 20; i++) { + ((char*)p2)[i] = 42; + } + internal_free(p); + internal_free(p2); +} + +TEST(Mman, User) { + ScopedInRtl in_rtl; + ThreadState *thr = cur_thread(); + uptr pc = 0; + char *p = (char*)user_alloc(thr, pc, 10); + EXPECT_NE(p, (char*)0); + char *p2 = (char*)user_alloc(thr, pc, 20); + EXPECT_NE(p2, (char*)0); + EXPECT_NE(p2, p); + MBlock *b = user_mblock(thr, p); + EXPECT_NE(b, (MBlock*)0); + EXPECT_EQ(b->size, (uptr)10); + MBlock *b2 = user_mblock(thr, p2); + EXPECT_NE(b2, (MBlock*)0); + EXPECT_EQ(b2->size, (uptr)20); + for (int i = 0; i < 10; i++) { + p[i] = 42; + EXPECT_EQ(b, user_mblock(thr, p + i)); + } + for (int i = 0; i < 20; i++) { + ((char*)p2)[i] = 42; + EXPECT_EQ(b2, user_mblock(thr, p2 + i)); + } + user_free(thr, pc, p); + user_free(thr, pc, p2); +} + +TEST(Mman, UserRealloc) { + ScopedInRtl in_rtl; + ThreadState *thr = cur_thread(); + uptr pc = 0; + { + void *p = user_realloc(thr, pc, 0, 0); + // Strictly saying this is incorrect, realloc(NULL, N) is equivalent to + // malloc(N), thus must return non-NULL pointer. + EXPECT_EQ(p, (void*)0); + } + { + void *p = user_realloc(thr, pc, 0, 100); + EXPECT_NE(p, (void*)0); + memset(p, 0xde, 100); + user_free(thr, pc, p); + } + { + void *p = user_alloc(thr, pc, 100); + EXPECT_NE(p, (void*)0); + memset(p, 0xde, 100); + void *p2 = user_realloc(thr, pc, p, 0); + EXPECT_EQ(p2, (void*)0); + } + { + void *p = user_realloc(thr, pc, 0, 100); + EXPECT_NE(p, (void*)0); + memset(p, 0xde, 100); + void *p2 = user_realloc(thr, pc, p, 10000); + EXPECT_NE(p2, (void*)0); + for (int i = 0; i < 100; i++) + EXPECT_EQ(((char*)p2)[i], (char)0xde); + memset(p2, 0xde, 10000); + user_free(thr, pc, p2); + } + { + void *p = user_realloc(thr, pc, 0, 10000); + EXPECT_NE(p, (void*)0); + memset(p, 0xde, 10000); + void *p2 = user_realloc(thr, pc, p, 10); + EXPECT_NE(p2, (void*)0); + for (int i = 0; i < 10; i++) + EXPECT_EQ(((char*)p2)[i], (char)0xde); + user_free(thr, pc, p2); + } +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_mutex_test.cc b/lib/tsan/unit_tests/tsan_mutex_test.cc new file mode 100644 index 0000000..c39841d --- /dev/null +++ b/lib/tsan/unit_tests/tsan_mutex_test.cc @@ -0,0 +1,126 @@ +//===-- tsan_mutex_test.cc ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_atomic.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_mutex.h" +#include "tsan_mutex.h" +#include "gtest/gtest.h" + +namespace __tsan { + +template +class TestData { + public: + explicit TestData(MutexType *mtx) + : mtx_(mtx) { + for (int i = 0; i < kSize; i++) + data_[i] = 0; + } + + void Write() { + Lock l(mtx_); + T v0 = data_[0]; + for (int i = 0; i < kSize; i++) { + CHECK_EQ(data_[i], v0); + data_[i]++; + } + } + + void Read() { + ReadLock l(mtx_); + T v0 = data_[0]; + for (int i = 0; i < kSize; i++) { + CHECK_EQ(data_[i], v0); + } + } + + void Backoff() { + volatile T data[kSize] = {}; + for (int i = 0; i < kSize; i++) { + data[i]++; + CHECK_EQ(data[i], 1); + } + } + + private: + typedef GenericScopedLock Lock; + static const int kSize = 64; + typedef u64 T; + MutexType *mtx_; + char pad_[kCacheLineSize]; + T data_[kSize]; +}; + +const int kThreads = 8; +const int kWriteRate = 1024; +#if TSAN_DEBUG +const int kIters = 16*1024; +#else +const int kIters = 64*1024; +#endif + +template +static void *write_mutex_thread(void *param) { + TestData *data = (TestData*)param; + for (int i = 0; i < kIters; i++) { + data->Write(); + data->Backoff(); + } + return 0; +} + +template +static void *read_mutex_thread(void *param) { + TestData *data = (TestData*)param; + for (int i = 0; i < kIters; i++) { + if ((i % kWriteRate) == 0) + data->Write(); + else + data->Read(); + data->Backoff(); + } + return 0; +} + +TEST(Mutex, Write) { + Mutex mtx(MutexTypeAnnotations, StatMtxAnnotations); + TestData data(&mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, write_mutex_thread, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +TEST(Mutex, ReadWrite) { + Mutex mtx(MutexTypeAnnotations, StatMtxAnnotations); + TestData data(&mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, read_mutex_thread, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +TEST(Mutex, SpinWrite) { + SpinMutex mtx; + TestData data(&mtx); + pthread_t threads[kThreads]; + for (int i = 0; i < kThreads; i++) + pthread_create(&threads[i], 0, write_mutex_thread, &data); + for (int i = 0; i < kThreads; i++) + pthread_join(threads[i], 0); +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_platform_test.cc b/lib/tsan/unit_tests/tsan_platform_test.cc new file mode 100644 index 0000000..64c4499 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_platform_test.cc @@ -0,0 +1,88 @@ +//===-- tsan_platform_test.cc ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "sanitizer_common/sanitizer_libc.h" +#include "tsan_platform.h" +#include "gtest/gtest.h" + +namespace __tsan { + +static void TestThreadInfo(bool main) { + ScopedInRtl in_rtl; + uptr stk_addr = 0; + uptr stk_size = 0; + uptr tls_addr = 0; + uptr tls_size = 0; + GetThreadStackAndTls(main, &stk_addr, &stk_size, &tls_addr, &tls_size); + // Printf("stk=%zx-%zx(%zu)\n", stk_addr, stk_addr + stk_size, stk_size); + // Printf("tls=%zx-%zx(%zu)\n", tls_addr, tls_addr + tls_size, tls_size); + + int stack_var; + EXPECT_NE(stk_addr, (uptr)0); + EXPECT_NE(stk_size, (uptr)0); + EXPECT_GT((uptr)&stack_var, stk_addr); + EXPECT_LT((uptr)&stack_var, stk_addr + stk_size); + + static __thread int thread_var; + EXPECT_NE(tls_addr, (uptr)0); + EXPECT_NE(tls_size, (uptr)0); + EXPECT_GT((uptr)&thread_var, tls_addr); + EXPECT_LT((uptr)&thread_var, tls_addr + tls_size); + + // Ensure that tls and stack do not intersect. + uptr tls_end = tls_addr + tls_size; + EXPECT_TRUE(tls_addr < stk_addr || tls_addr >= stk_addr + stk_size); + EXPECT_TRUE(tls_end < stk_addr || tls_end >= stk_addr + stk_size); + EXPECT_TRUE((tls_addr < stk_addr) == (tls_end < stk_addr)); +} + +static void *WorkerThread(void *arg) { + TestThreadInfo(false); + return 0; +} + +TEST(Platform, ThreadInfoMain) { + TestThreadInfo(true); +} + +TEST(Platform, ThreadInfoWorker) { + pthread_t t; + pthread_create(&t, 0, WorkerThread, 0); + pthread_join(t, 0); +} + +TEST(Platform, FileOps) { + const char *str1 = "qwerty"; + uptr len1 = internal_strlen(str1); + const char *str2 = "zxcv"; + uptr len2 = internal_strlen(str2); + + fd_t fd = internal_open("./tsan_test.tmp", true); + EXPECT_NE(fd, kInvalidFd); + EXPECT_EQ(len1, internal_write(fd, str1, len1)); + EXPECT_EQ(len2, internal_write(fd, str2, len2)); + internal_close(fd); + + fd = internal_open("./tsan_test.tmp", false); + EXPECT_NE(fd, kInvalidFd); + EXPECT_EQ(len1 + len2, internal_filesize(fd)); + char buf[64] = {}; + EXPECT_EQ(len1, internal_read(fd, buf, len1)); + EXPECT_EQ(0, internal_memcmp(buf, str1, len1)); + EXPECT_EQ((char)0, buf[len1 + 1]); + internal_memset(buf, 0, len1); + EXPECT_EQ(len2, internal_read(fd, buf, len2)); + EXPECT_EQ(0, internal_memcmp(buf, str2, len2)); + internal_close(fd); +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_printf_test.cc b/lib/tsan/unit_tests/tsan_printf_test.cc new file mode 100644 index 0000000..0dfd1d2 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_printf_test.cc @@ -0,0 +1,106 @@ +//===-- tsan_printf_test.cc -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +#include +#include + +namespace __tsan { + +TEST(Printf, Basic) { + char buf[1024]; + uptr len = internal_snprintf(buf, sizeof(buf), + "a%db%zdc%ue%zuf%xh%zxq%pe%sr", + (int)-1, (long)-2, // NOLINT + (unsigned)-4, (unsigned long)5, // NOLINT + (unsigned)10, (unsigned long)11, // NOLINT + (void*)0x123, "_string_"); + EXPECT_EQ(len, strlen(buf)); + EXPECT_EQ(0, strcmp(buf, "a-1b-2c4294967292e5fahbq" + "0x000000000123e_string_r")); +} + +TEST(Printf, OverflowStr) { + char buf[] = "123456789"; + uptr len = internal_snprintf(buf, 4, "%s", "abcdef"); // NOLINT + EXPECT_EQ(len, (uptr)6); + EXPECT_EQ(0, strcmp(buf, "abc")); + EXPECT_EQ(buf[3], 0); + EXPECT_EQ(buf[4], '5'); + EXPECT_EQ(buf[5], '6'); + EXPECT_EQ(buf[6], '7'); + EXPECT_EQ(buf[7], '8'); + EXPECT_EQ(buf[8], '9'); + EXPECT_EQ(buf[9], 0); +} + +TEST(Printf, OverflowInt) { + char buf[] = "123456789"; + internal_snprintf(buf, 4, "%d", -123456789); // NOLINT + EXPECT_EQ(0, strcmp(buf, "-12")); + EXPECT_EQ(buf[3], 0); + EXPECT_EQ(buf[4], '5'); + EXPECT_EQ(buf[5], '6'); + EXPECT_EQ(buf[6], '7'); + EXPECT_EQ(buf[7], '8'); + EXPECT_EQ(buf[8], '9'); + EXPECT_EQ(buf[9], 0); +} + +TEST(Printf, OverflowUint) { + char buf[] = "123456789"; + internal_snprintf(buf, 4, "a%zx", (unsigned long)0x123456789); // NOLINT + EXPECT_EQ(0, strcmp(buf, "a12")); + EXPECT_EQ(buf[3], 0); + EXPECT_EQ(buf[4], '5'); + EXPECT_EQ(buf[5], '6'); + EXPECT_EQ(buf[6], '7'); + EXPECT_EQ(buf[7], '8'); + EXPECT_EQ(buf[8], '9'); + EXPECT_EQ(buf[9], 0); +} + +TEST(Printf, OverflowPtr) { + char buf[] = "123456789"; + internal_snprintf(buf, 4, "%p", (void*)0x123456789); // NOLINT + EXPECT_EQ(0, strcmp(buf, "0x0")); + EXPECT_EQ(buf[3], 0); + EXPECT_EQ(buf[4], '5'); + EXPECT_EQ(buf[5], '6'); + EXPECT_EQ(buf[6], '7'); + EXPECT_EQ(buf[7], '8'); + EXPECT_EQ(buf[8], '9'); + EXPECT_EQ(buf[9], 0); +} + +template +static void TestMinMax(const char *fmt, T min, T max) { + char buf[1024]; + uptr len = internal_snprintf(buf, sizeof(buf), fmt, min, max); + char buf2[1024]; + snprintf(buf2, sizeof(buf2), fmt, min, max); + EXPECT_EQ(len, strlen(buf)); + EXPECT_EQ(0, strcmp(buf, buf2)); +} + +TEST(Printf, MinMax) { + TestMinMax("%d-%d", INT_MIN, INT_MAX); // NOLINT + TestMinMax("%zd-%zd", LONG_MIN, LONG_MAX); // NOLINT + TestMinMax("%u-%u", 0, UINT_MAX); // NOLINT + TestMinMax("%zu-%zu", 0, ULONG_MAX); // NOLINT + TestMinMax("%x-%x", 0, UINT_MAX); // NOLINT + TestMinMax("%zx-%zx", 0, ULONG_MAX); // NOLINT +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_shadow_test.cc b/lib/tsan/unit_tests/tsan_shadow_test.cc new file mode 100644 index 0000000..41f9121 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_shadow_test.cc @@ -0,0 +1,47 @@ +//===-- tsan_shadow_test.cc -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_platform.h" +#include "gtest/gtest.h" + +namespace __tsan { + +TEST(Shadow, Mapping) { + static int global; + int stack; + void *heap = malloc(0); + free(heap); + + CHECK(IsAppMem((uptr)&global)); + CHECK(IsAppMem((uptr)&stack)); + CHECK(IsAppMem((uptr)heap)); + + CHECK(IsShadowMem(MemToShadow((uptr)&global))); + CHECK(IsShadowMem(MemToShadow((uptr)&stack))); + CHECK(IsShadowMem(MemToShadow((uptr)heap))); +} + +TEST(Shadow, Celling) { + u64 aligned_data[4]; + char *data = (char*)aligned_data; + CHECK_EQ((uptr)data % kShadowSize, 0); + uptr s0 = MemToShadow((uptr)&data[0]); + CHECK_EQ(s0 % kShadowSize, 0); + for (unsigned i = 1; i < kShadowCell; i++) + CHECK_EQ(s0, MemToShadow((uptr)&data[i])); + for (unsigned i = kShadowCell; i < 2*kShadowCell; i++) + CHECK_EQ(s0 + kShadowSize*kShadowCnt, MemToShadow((uptr)&data[i])); + for (unsigned i = 2*kShadowCell; i < 3*kShadowCell; i++) + CHECK_EQ(s0 + 2*kShadowSize*kShadowCnt, MemToShadow((uptr)&data[i])); +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_suppressions_test.cc b/lib/tsan/unit_tests/tsan_suppressions_test.cc new file mode 100644 index 0000000..e1e0c12 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_suppressions_test.cc @@ -0,0 +1,128 @@ +//===-- tsan_suppressions_test.cc -----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_suppressions.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +#include + +namespace __tsan { + +TEST(Suppressions, Parse) { + ScopedInRtl in_rtl; + Suppression *supp0 = SuppressionParse( + "race:foo\n" + " race:bar\n" // NOLINT + "race:baz \n" // NOLINT + "# a comment\n" + "race:quz\n" + ); // NOLINT + Suppression *supp = supp0; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "quz")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "baz")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "bar")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "foo")); + supp = supp->next; + EXPECT_EQ((Suppression*)0, supp); +} + +TEST(Suppressions, Parse2) { + ScopedInRtl in_rtl; + Suppression *supp0 = SuppressionParse( + " # first line comment\n" // NOLINT + " race:bar \n" // NOLINT + "race:baz* *baz\n" + "# a comment\n" + "# last line comment\n" + ); // NOLINT + Suppression *supp = supp0; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "baz* *baz")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "bar")); + supp = supp->next; + EXPECT_EQ((Suppression*)0, supp); +} + +TEST(Suppressions, Parse3) { + ScopedInRtl in_rtl; + Suppression *supp0 = SuppressionParse( + "# last suppression w/o line-feed\n" + "race:foo\n" + "race:bar" + ); // NOLINT + Suppression *supp = supp0; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "bar")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "foo")); + supp = supp->next; + EXPECT_EQ((Suppression*)0, supp); +} + +TEST(Suppressions, ParseType) { + ScopedInRtl in_rtl; + Suppression *supp0 = SuppressionParse( + "race:foo\n" + "thread:bar\n" + "mutex:baz\n" + "signal:quz\n" + ); // NOLINT + Suppression *supp = supp0; + EXPECT_EQ(supp->type, SuppressionSignal); + EXPECT_EQ(0, strcmp(supp->templ, "quz")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionMutex); + EXPECT_EQ(0, strcmp(supp->templ, "baz")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionThread); + EXPECT_EQ(0, strcmp(supp->templ, "bar")); + supp = supp->next; + EXPECT_EQ(supp->type, SuppressionRace); + EXPECT_EQ(0, strcmp(supp->templ, "foo")); + supp = supp->next; + EXPECT_EQ((Suppression*)0, supp); +} + +static bool MyMatch(const char *templ, const char *func) { + char tmp[1024]; + strcpy(tmp, templ); // NOLINT + return SuppressionMatch(tmp, func); +} + +TEST(Suppressions, Match) { + EXPECT_TRUE(MyMatch("foobar", "foobar")); + EXPECT_TRUE(MyMatch("foobar", "prefix_foobar_postfix")); + EXPECT_TRUE(MyMatch("*foobar*", "prefix_foobar_postfix")); + EXPECT_TRUE(MyMatch("foo*bar", "foo_middle_bar")); + EXPECT_TRUE(MyMatch("foo*bar", "foobar")); + EXPECT_TRUE(MyMatch("foo*bar*baz", "foo_middle_bar_another_baz")); + EXPECT_TRUE(MyMatch("foo*bar*baz", "foo_middle_barbaz")); + + EXPECT_FALSE(MyMatch("foo", "baz")); + EXPECT_FALSE(MyMatch("foobarbaz", "foobar")); + EXPECT_FALSE(MyMatch("foobarbaz", "barbaz")); + EXPECT_FALSE(MyMatch("foo*bar", "foobaz")); + EXPECT_FALSE(MyMatch("foo*bar", "foo_baz")); +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_sync_test.cc b/lib/tsan/unit_tests/tsan_sync_test.cc new file mode 100644 index 0000000..b7605a5 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_sync_test.cc @@ -0,0 +1,65 @@ +//===-- tsan_sync_test.cc -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_sync.h" +#include "tsan_rtl.h" +#include "tsan_mman.h" +#include "gtest/gtest.h" + +#include +#include +#include + +namespace __tsan { + +TEST(Sync, Table) { + const uintptr_t kIters = 512*1024; + const uintptr_t kRange = 10000; + + ScopedInRtl in_rtl; + ThreadState *thr = cur_thread(); + uptr pc = 0; + + SyncTab tab; + SyncVar *golden[kRange] = {}; + unsigned seed = 0; + for (uintptr_t i = 0; i < kIters; i++) { + uintptr_t addr = rand_r(&seed) % (kRange - 1) + 1; + if (rand_r(&seed) % 2) { + // Get or add. + SyncVar *v = tab.GetAndLock(thr, pc, addr, true); + EXPECT_TRUE(golden[addr] == 0 || golden[addr] == v); + EXPECT_EQ(v->addr, addr); + golden[addr] = v; + v->mtx.Unlock(); + } else { + // Remove. + SyncVar *v = tab.GetAndRemove(thr, pc, addr); + EXPECT_EQ(golden[addr], v); + if (v) { + EXPECT_EQ(v->addr, addr); + golden[addr] = 0; + DestroyAndFree(v); + } + } + } + for (uintptr_t addr = 0; addr < kRange; addr++) { + if (golden[addr] == 0) + continue; + SyncVar *v = tab.GetAndRemove(thr, pc, addr); + EXPECT_EQ(v, golden[addr]); + EXPECT_EQ(v->addr, addr); + DestroyAndFree(v); + } +} + +} // namespace __tsan diff --git a/lib/tsan/unit_tests/tsan_vector_test.cc b/lib/tsan/unit_tests/tsan_vector_test.cc new file mode 100644 index 0000000..cfef6e5 --- /dev/null +++ b/lib/tsan/unit_tests/tsan_vector_test.cc @@ -0,0 +1,45 @@ +//===-- tsan_vector_test.cc -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_vector.h" +#include "tsan_rtl.h" +#include "gtest/gtest.h" + +namespace __tsan { + +TEST(Vector, Basic) { + ScopedInRtl in_rtl; + Vector v(MBlockScopedBuf); + EXPECT_EQ(v.Size(), (uptr)0); + v.PushBack(42); + EXPECT_EQ(v.Size(), (uptr)1); + EXPECT_EQ(v[0], 42); + v.PushBack(43); + EXPECT_EQ(v.Size(), (uptr)2); + EXPECT_EQ(v[0], 42); + EXPECT_EQ(v[1], 43); +} + +TEST(Vector, Stride) { + ScopedInRtl in_rtl; + Vector v(MBlockScopedBuf); + for (int i = 0; i < 1000; i++) { + v.PushBack(i); + EXPECT_EQ(v.Size(), (uptr)(i + 1)); + EXPECT_EQ(v[i], i); + } + for (int i = 0; i < 1000; i++) { + EXPECT_EQ(v[i], i); + } +} + +} // namespace __tsan -- cgit v1.1