diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h @@ -711,7 +711,8 @@ internal_memset(uuid_, 0, kModuleUUIDSize); ranges_.clear(); } - void set(const char *module_name, uptr base_address); + void set(const char *module_name, uptr base_address, + bool instrumented = false); void set(const char *module_name, uptr base_address, ModuleArch arch, u8 uuid[kModuleUUIDSize], bool instrumented); void clear(); diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.cc b/compiler-rt/lib/sanitizer_common/sanitizer_common.cc --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.cc @@ -127,19 +127,20 @@ *z = '\0'; } -void LoadedModule::set(const char *module_name, uptr base_address) { +void LoadedModule::set(const char *module_name, uptr base_address, + bool instrumented) { clear(); full_name_ = internal_strdup(module_name); base_address_ = base_address; + instrumented_ = instrumented; } void LoadedModule::set(const char *module_name, uptr base_address, ModuleArch arch, u8 uuid[kModuleUUIDSize], bool instrumented) { - set(module_name, base_address); + set(module_name, base_address, instrumented); arch_ = arch; internal_memcpy(uuid_, uuid, sizeof(uuid_)); - instrumented_ = instrumented; } void LoadedModule::clear() { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cc b/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cc --- a/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cc @@ -541,6 +541,126 @@ bool first; }; +class DynamicSegment { + ElfW(Addr) base_addr; + size_t symbol_count; + ElfW(Sym *) symbol_table; + const char *string_table; + + public: + DynamicSegment(ElfW(Addr) base_addr, ElfW(Addr) segment_addr) + : base_addr(base_addr), + symbol_count(0), + symbol_table(nullptr), + string_table(nullptr) { + initialize(segment_addr); + CHECK(symbol_count > 0 && symbol_table && string_table); + } + + size_t symbolCount() const { return symbol_count; } + + const char *getSymbolName(size_t index) const { + auto str_index = symbol_table[index].st_name; + return &string_table[str_index]; + } + + private: + // Elf_Addr -> dereferenceable pointer + template + Type *toPtr(ElfW(Addr) addr_or_offset) const { + bool offset = addr_or_offset < base_addr; + ElfW(Addr) ptr = offset ? (base_addr + addr_or_offset) : addr_or_offset; + return reinterpret_cast(ptr); + } + + void initialize(ElfW(Addr) segment_addr) { + auto *entry = toPtr(segment_addr); + for (; entry->d_tag != DT_NULL; entry++) { + auto addr = entry->d_un.d_ptr; + switch (entry->d_tag) { + case DT_HASH: + symbol_count = getSymbolCountFromHash(addr); + break; + case DT_GNU_HASH: + // DT_HASH takes precedence over DT_GNU_HASH + if (symbol_count > 0) + break; + symbol_count = getSymbolCountFromGnuHash(addr); + break; + case DT_SYMTAB: + CHECK_EQ(symbol_table, nullptr); + symbol_table = toPtr(addr); + break; + case DT_STRTAB: + CHECK_EQ(string_table, nullptr); + string_table = toPtr(addr); + break; + } + } + } + + size_t getSymbolCountFromHash(ElfW(Addr) hashtable_addr) const { + struct ht_header { + uint32_t bucket_count; + uint32_t chain_count; + }; + return toPtr(hashtable_addr)->chain_count; + } + + size_t getSymbolCountFromGnuHash(ElfW(Addr) hashtable_addr) const { + struct ht_header { + uint32_t bucket_count; + uint32_t symoffset; + uint32_t bloom_size; + uint32_t bloom_shift; + }; + auto header = toPtr(hashtable_addr); + auto word_size = FIRST_32_SECOND_64(sizeof(uint32_t), sizeof(uint64_t)); + auto buckets_addr = + hashtable_addr + sizeof(ht_header) + (word_size * header->bloom_size); + auto buckets = toPtr(buckets_addr); + auto chains_addr = + buckets_addr + (header->bucket_count * sizeof(buckets[0])); + auto chains = toPtr(chains_addr); + + // Locate the chain that handles the largest index bucket. + uint32_t last_symbol = 0; + for (uint32_t i = 0; i < header->bucket_count; i++) { + last_symbol = Max(buckets[i], last_symbol); + } + + // Walk the bucket's chain to add the chain length to the total. + uint32_t chain_entry; + do { + chain_entry = chains[last_symbol - header->symoffset]; + last_symbol++; + } while ((chain_entry & 1) == 0); + + return last_symbol; + } +}; + +static bool IsModuleInstrumented(dl_phdr_info *info) { + // Iterate all headers of the library. + for (size_t header = 0; header < info->dlpi_phnum; header++) { + // We are only interested in dynamic segments. + if (info->dlpi_phdr[header].p_type != PT_DYNAMIC) + continue; + + auto base_addr = info->dlpi_addr; + auto segment_addr = info->dlpi_phdr[header].p_vaddr; + DynamicSegment segment(base_addr, segment_addr); + + // Iterate symbol table. + for (size_t i = 0; i < segment.symbolCount(); i++) { + auto *name = segment.getSymbolName(i); + if (internal_strcmp(name, "__tsan_init") == 0) + return true; + } + } + return false; +} + static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { DlIteratePhdrData *data = (DlIteratePhdrData*)arg; InternalScopedString module_name(kMaxPathLength); @@ -554,7 +674,8 @@ if (module_name[0] == '\0') return 0; LoadedModule cur_module; - cur_module.set(module_name.data(), info->dlpi_addr); + bool instrumented = IsModuleInstrumented(info); + cur_module.set(module_name.data(), info->dlpi_addr, instrumented); for (int i = 0; i < (int)info->dlpi_phnum; i++) { const Elf_Phdr *phdr = &info->dlpi_phdr[i]; if (phdr->p_type == PT_LOAD) { diff --git a/compiler-rt/test/tsan/Darwin/ignore-noninstrumented.mm b/compiler-rt/test/tsan/Darwin/ignore-noninstrumented.mm --- a/compiler-rt/test/tsan/Darwin/ignore-noninstrumented.mm +++ b/compiler-rt/test/tsan/Darwin/ignore-noninstrumented.mm @@ -6,13 +6,13 @@ // RUN: %clang_tsan %s -o %t -framework Foundation // Check that without the flag, there are false positives. -// RUN: %env_tsan_opts=ignore_noninstrumented_modules=0 %deflake %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RACE +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=0 %deflake %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RACE // With ignore_noninstrumented_modules=1, no races are reported. -// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %run %t 2>&1 | FileCheck %s +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %run %t 2>&1 | FileCheck %s --implicit-check-not='ThreadSanitizer' // With ignore_noninstrumented_modules=1, races in user's code are still reported. -// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %deflake %run %t race 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-RACE +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %deflake %run %t race 2>&1 | FileCheck %s --check-prefix=CHECK-RACE #import diff --git a/compiler-rt/test/tsan/ignore-noninstrumented.cc b/compiler-rt/test/tsan/ignore-noninstrumented.cc new file mode 100644 --- /dev/null +++ b/compiler-rt/test/tsan/ignore-noninstrumented.cc @@ -0,0 +1,81 @@ +// Check that ignore_noninstrumented_modules=1 suppresses reports originating +// from interceptors that are called from an un-instrumented library. + +// RUN: %clangxx_tsan %s -fPIC -shared -DLIBRARY -fno-sanitize=thread -o %t.library.so +// RUN: %clangxx_tsan %s %t.library.so -o %t + +// Check that without the flag, there are false positives. +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=0 %deflake %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-RACE + +// With ignore_noninstrumented_modules=1, no races are reported. +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %run %t 2>&1 | FileCheck %s --implicit-check-not='ThreadSanitizer' + +// With ignore_noninstrumented_modules=1, races in user's code are still reported. +// RUN: %env_tsan_opts=ignore_noninstrumented_modules=1 %deflake %run %t race 2>&1 | FileCheck %s --check-prefix=CHECK-RACE + +#include "test.h" + +#include + +#ifdef LIBRARY +namespace library { +#endif +char global_buf[64]; + +void *Thread1(void *arg) { + auto barrier_wait = (void (*)())arg; + barrier_wait(); + strcpy(global_buf, "hello world"); // NOLINT + return NULL; +} + +void *Thread2(void *arg) { + auto barrier_wait = (void (*)())arg; + strcpy(global_buf, "world hello"); // NOLINT + barrier_wait(); + return NULL; +} + +void Race(void (*barrier_wait)()) { + pthread_t t[2]; + pthread_create(&t[0], NULL, Thread1, (void *)barrier_wait); + pthread_create(&t[1], NULL, Thread2, (void *)barrier_wait); + pthread_join(t[0], NULL); + pthread_join(t[1], NULL); +} +#ifdef LIBRARY +} // namespace library +#endif + +#ifndef LIBRARY +namespace library { + void Race(void (*barrier_wait)()); +} + +// Pass pointer to this function to un-instrumented library, so it can access +// TSan-invisible barriers. +void my_barrier_wait() { + barrier_wait(&barrier); +} + +int main(int argc, char *argv[]) { + fprintf(stderr, "Hello world.\n"); + + // Race in un-instrumented library + barrier_init(&barrier, 2); + library::Race(my_barrier_wait); + + // Race in user code, if requested + if (argc > 1 && strcmp(argv[1], "race") == 0) { + barrier_init(&barrier, 2); + Race(my_barrier_wait); + } + + fprintf(stderr, "Done.\n"); +} + +#endif // LIBRARY + +// CHECK: Hello world. +// CHECK-RACE: SUMMARY: ThreadSanitizer: data race +// CHECK: Done. diff --git a/compiler-rt/test/tsan/libdispatch/lit.local.cfg b/compiler-rt/test/tsan/libdispatch/lit.local.cfg --- a/compiler-rt/test/tsan/libdispatch/lit.local.cfg +++ b/compiler-rt/test/tsan/libdispatch/lit.local.cfg @@ -13,5 +13,4 @@ else: config.unsupported = True -if config.host_os == 'Darwin': - config.environment['TSAN_OPTIONS'] += ':ignore_noninstrumented_modules=1' +config.environment['TSAN_OPTIONS'] += ':ignore_noninstrumented_modules=1'