From 8dc3460631cfb196bcbdb74890105c1fa164a3e1 Mon Sep 17 00:00:00 2001 From: yinchuang Date: Wed, 27 Mar 2024 17:35:38 +0800 Subject: [PATCH] [Sanitizer] Support ignore_noninstrumented_modules on Linux Pick from https://reviews.llvm.org/D61708 Issue:I9EKYB Signed-off-by: yinchuang --- .../lib/sanitizer_common/sanitizer_common.cpp | 11 ++ .../lib/sanitizer_common/sanitizer_common.h | 6 + .../sanitizer_linux_libcdep.cpp | 136 ++++++++++++++++++ .../test/tsan/ignore-noninstrumented.cpp | 86 +++++++++++ 4 files changed, 239 insertions(+) create mode 100644 compiler-rt/test/tsan/ignore-noninstrumented.cpp diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp index 82236453157f..1fc4a677a10c 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.cpp @@ -134,11 +134,22 @@ void RemoveANSIEscapeSequencesFromString(char *str) { *z = '\0'; } +#if SANITIZER_OHOS +// OHOS_LOCAL begin +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; +} +#else void LoadedModule::set(const char *module_name, uptr base_address) { clear(); full_name_ = internal_strdup(module_name); base_address_ = base_address; } +// OHOS_LOCAL end +#endif void LoadedModule::set(const char *module_name, uptr base_address, ModuleArch arch, u8 uuid[kModuleUUIDSize], diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_common.h b/compiler-rt/lib/sanitizer_common/sanitizer_common.h index c4153201a1a0..787687db26d0 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_common.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_common.h @@ -807,7 +807,13 @@ class LoadedModule { internal_memset(uuid_, 0, kModuleUUIDSize); ranges_.clear(); } +#if SANITIZER_OHOS +// OHOS_LOCAL begin + void set(const char *module_name, uptr base_address, bool instrumented = false); +#else void set(const char *module_name, uptr base_address); +// OHOS_LOCAL end +#endif void set(const char *module_name, uptr base_address, ModuleArch arch, u8 uuid[kModuleUUIDSize], bool instrumented); void setUuid(const char *uuid, uptr size); diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp index 785c399d0238..4f330ffa10b0 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp @@ -645,12 +645,148 @@ struct DlIteratePhdrData { bool first; }; +#if SANITIZER_OHOS +// OHOS_LOCAL begin +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); // OHOS_LOCAL + } + + 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); + } + + // OHOS_LOCAL + if (!last_symbol) { + return 0; + } + + // 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; +} +// OHOS_LOCAL end +#endif + static int AddModuleSegments(const char *module_name, dl_phdr_info *info, InternalMmapVectorNoCtor *modules) { if (module_name[0] == '\0') return 0; LoadedModule cur_module; +#if SANITIZER_OHOS +// OHOS_LOCAL begin + bool instrumented = IsModuleInstrumented(info); + cur_module.set(module_name, info->dlpi_addr, instrumented); +#else cur_module.set(module_name, info->dlpi_addr); +// OHOS_LOCAL end +#endif 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/ignore-noninstrumented.cpp b/compiler-rt/test/tsan/ignore-noninstrumented.cpp new file mode 100644 index 000000000000..6129d7c39006 --- /dev/null +++ b/compiler-rt/test/tsan/ignore-noninstrumented.cpp @@ -0,0 +1,86 @@ +// Check that ignore_noninstrumented_modules=1 suppresses reports originating +// from interceptors that are called from an un-instrumented library. + +// RUN: rm -rf %t-dir +// RUN: mkdir %t-dir + +// RUN: %clangxx_tsan %s -fPIC -shared -DLIBRARY -fno-sanitize=thread -o %t-dir/libignore_noninstrumented.so +// RUN: %clangxx_tsan %s -L%t-dir -lignore_noninstrumented -o %t + +// Check that without the flag, there are false positives. +// RUN: env LD_LIBRARY_PATH=%t-dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} %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 LD_LIBRARY_PATH=%t-dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} %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 LD_LIBRARY_PATH=%t-dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} %env_tsan_opts=ignore_noninstrumented_modules=1 %deflake %run %t race 2>&1 | FileCheck %s --check-prefix=CHECK-RACE + +// REQUIRES: ohos_family + +#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. \ No newline at end of file -- Gitee