Index: lib/tsan/CMakeLists.txt =================================================================== --- lib/tsan/CMakeLists.txt +++ lib/tsan/CMakeLists.txt @@ -25,6 +25,7 @@ set(TSAN_SOURCES rtl/tsan_clock.cc rtl/tsan_debugging.cc + rtl/tsan_external.cc rtl/tsan_fd.cc rtl/tsan_flags.cc rtl/tsan_ignoreset.cc Index: lib/tsan/rtl/tsan_debugging.cc =================================================================== --- lib/tsan/rtl/tsan_debugging.cc +++ lib/tsan/rtl/tsan_debugging.cc @@ -24,6 +24,7 @@ if (typ == ReportTypeVptrRace) return "data-race-vptr"; if (typ == ReportTypeUseAfterFree) return "heap-use-after-free"; if (typ == ReportTypeVptrUseAfterFree) return "heap-use-after-free-vptr"; + if (typ == ReportTypeExternalRace) return "external-race"; if (typ == ReportTypeThreadLeak) return "thread-leak"; if (typ == ReportTypeMutexDestroyLocked) return "locked-mutex-destroy"; if (typ == ReportTypeMutexDoubleLock) return "mutex-double-lock"; Index: lib/tsan/rtl/tsan_defs.h =================================================================== --- lib/tsan/rtl/tsan_defs.h +++ lib/tsan/rtl/tsan_defs.h @@ -149,7 +149,8 @@ // Descriptor of user's memory block. struct MBlock { - u64 siz; + u64 siz : 48; + u64 tag : 16; u32 stk; u16 tid; }; Index: lib/tsan/rtl/tsan_external.h =================================================================== --- lib/tsan/rtl/tsan_external.h +++ lib/tsan/rtl/tsan_external.h @@ -0,0 +1,32 @@ +//===-- tsan_external.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_EXTERNAL_H +#define TSAN_EXTERNAL_H + +#include "tsan_rtl.h" + +namespace __tsan { + +static const uptr kMaxTag = 128; // Limited to 65,536, since MBlock only stores + // tags as 16-bit values, see tsan_defs.h. +struct TagDescription { + const char *library_name; + const char *object_type; +}; + +uptr CreateTag(const char *library_name, const char *object_type); +TagDescription *TagDescriptionFromTag(uptr tag); + +} // namespace __tsan + +#endif // TSAN_INTERFACE_H Index: lib/tsan/rtl/tsan_external.cc =================================================================== --- lib/tsan/rtl/tsan_external.cc +++ lib/tsan/rtl/tsan_external.cc @@ -0,0 +1,74 @@ +//===-- tsan_external.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_external.h" + +namespace __tsan { + +#define CALLERPC ((uptr)__builtin_return_address(0)) + +TagDescription registered_tags[kMaxTag]; +uptr used_tags = 1; // Tag 0 means "no tag". + +uptr CreateTag(const char *library_name, const char *object_type) { + CHECK_LT(used_tags, kMaxTag); + uptr new_tag = used_tags; + used_tags++; + registered_tags[new_tag].library_name = internal_strdup(library_name); + registered_tags[new_tag].object_type = internal_strdup(object_type); + return new_tag; +} + +TagDescription *TagDescriptionFromTag(uptr tag) { + return ®istered_tags[tag]; +} + +extern "C" { +SANITIZER_INTERFACE_ATTRIBUTE +void *__tsan_external_register_tag(const char *library_name, const char *object_type) { + return (void *)CreateTag(library_name, object_type); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_assign_tag(void *addr, void *tag) { + Allocator *a = allocator(); + MBlock *b = nullptr; + if (a->PointerIsMine((void*)addr)) { + void *block_begin = a->GetBlockBegin((void*)addr); + if (block_begin) + b = ctx->metamap.GetBlock((uptr)block_begin); + } + if (b) { + b->tag = (u32)(uptr)tag; + } +} + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_external_read(void *addr, void *caller_pc, void *tag) { + ThreadState *thr = cur_thread(); + thr->is_external_race = true; + thr->external_tag = (uptr)tag; + MemoryRead(thr, CALLERPC, (uptr)addr, kSizeLog8); + thr->is_external_race = false; + thr->external_tag = 0; +} + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_external_write(void *addr, void *caller_pc, void *tag) { + ThreadState *thr = cur_thread(); + thr->is_external_race = true; + thr->external_tag = (uptr)tag; + MemoryWrite(thr, CALLERPC, (uptr)addr, kSizeLog8); + thr->is_external_race = false; + thr->external_tag = 0; +} +} // extern "C" + +} // namespace __tsan Index: lib/tsan/rtl/tsan_interface.h =================================================================== --- lib/tsan/rtl/tsan_interface.h +++ lib/tsan/rtl/tsan_interface.h @@ -79,6 +79,16 @@ SANITIZER_INTERFACE_ATTRIBUTE void __tsan_ignore_thread_end(); SANITIZER_INTERFACE_ATTRIBUTE +void *__tsan_external_register_tag(const char *library_name, + const char *object_type); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_assign_tag(void *addr, void *tag); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_read(void *addr, void *caller_pc, void *tag); +SANITIZER_INTERFACE_ATTRIBUTE +void __tsan_external_write(void *addr, void *caller_pc, void *tag); + +SANITIZER_INTERFACE_ATTRIBUTE void __tsan_read_range(void *addr, unsigned long size); // NOLINT SANITIZER_INTERFACE_ATTRIBUTE void __tsan_write_range(void *addr, unsigned long size); // NOLINT Index: lib/tsan/rtl/tsan_report.h =================================================================== --- lib/tsan/rtl/tsan_report.h +++ lib/tsan/rtl/tsan_report.h @@ -24,6 +24,7 @@ ReportTypeVptrRace, ReportTypeUseAfterFree, ReportTypeVptrUseAfterFree, + ReportTypeExternalRace, ReportTypeThreadLeak, ReportTypeMutexDestroyLocked, ReportTypeMutexDoubleLock, @@ -56,6 +57,7 @@ int size; bool write; bool atomic; + uptr external_tag; Vector mset; ReportStack *stack; @@ -75,6 +77,7 @@ DataInfo global; uptr heap_chunk_start; uptr heap_chunk_size; + uptr external_tag; int tid; int fd; bool suppressable; Index: lib/tsan/rtl/tsan_report.cc =================================================================== --- lib/tsan/rtl/tsan_report.cc +++ lib/tsan/rtl/tsan_report.cc @@ -10,6 +10,7 @@ // This file is a part of ThreadSanitizer (TSan), a race detector. // //===----------------------------------------------------------------------===// +#include "tsan_external.h" #include "tsan_report.h" #include "tsan_platform.h" #include "tsan_rtl.h" @@ -90,6 +91,8 @@ return "heap-use-after-free"; if (typ == ReportTypeVptrUseAfterFree) return "heap-use-after-free (virtual call vs free)"; + if (typ == ReportTypeExternalRace) + return "race on a library object"; if (typ == ReportTypeThreadLeak) return "thread leak"; if (typ == ReportTypeMutexDestroyLocked) @@ -152,14 +155,25 @@ : (write ? "Previous write" : "Previous read")); } +static const char *ExternalMopDesc(bool first, bool write) { + return first ? (write ? "Mutating" : "Read-only") + : (write ? "Previous mutating" : "Previous read-only"); +} + static void PrintMop(const ReportMop *mop, bool first) { Decorator d; char thrbuf[kThreadBufSize]; Printf("%s", d.Access()); - Printf(" %s of size %d at %p by %s", - MopDesc(first, mop->write, mop->atomic), - mop->size, (void*)mop->addr, - thread_name(thrbuf, mop->tid)); + if (mop->external_tag == 0) { + Printf(" %s of size %d at %p by %s", + MopDesc(first, mop->write, mop->atomic), mop->size, + (void *)mop->addr, thread_name(thrbuf, mop->tid)); + } else { + TagDescription *tag = TagDescriptionFromTag(mop->external_tag); + Printf(" %s access of object %s from library %s at %p by %s", + ExternalMopDesc(first, mop->write), tag->object_type, + tag->library_name, (void *)mop->addr, thread_name(thrbuf, mop->tid)); + } PrintMutexSet(mop->mset); Printf(":\n"); Printf("%s", d.EndAccess()); @@ -183,9 +197,18 @@ global.module_offset); } else if (loc->type == ReportLocationHeap) { char thrbuf[kThreadBufSize]; - Printf(" Location is heap block of size %zu at %p allocated by %s:\n", - loc->heap_chunk_size, loc->heap_chunk_start, - thread_name(thrbuf, loc->tid)); + if (loc->external_tag == 0) + Printf(" Location is heap block of size %zu at %p allocated by %s:\n", + loc->heap_chunk_size, loc->heap_chunk_start, + thread_name(thrbuf, loc->tid)); + else { + TagDescription *tag = TagDescriptionFromTag(loc->external_tag); + Printf( + " Location is %s object from library %s of size %zu at %p allocated " + "by %s:\n", + tag->object_type, tag->library_name, loc->heap_chunk_size, + loc->heap_chunk_start, thread_name(thrbuf, loc->tid)); + } print_stack = true; } else if (loc->type == ReportLocationStack) { Printf(" Location is stack of %s.\n\n", thread_name(thrbuf, loc->tid)); Index: lib/tsan/rtl/tsan_rtl.h =================================================================== --- lib/tsan/rtl/tsan_rtl.h +++ lib/tsan/rtl/tsan_rtl.h @@ -410,6 +410,8 @@ bool is_dead; bool is_freeing; bool is_vptr_access; + bool is_external_race; + uptr external_tag; const uptr stk_addr; const uptr stk_size; const uptr tls_addr; @@ -564,7 +566,7 @@ explicit ScopedReport(ReportType typ); ~ScopedReport(); - void AddMemoryAccess(uptr addr, Shadow s, StackTrace stack, + void AddMemoryAccess(uptr addr, uptr external_tag, Shadow s, StackTrace stack, const MutexSet *mset); void AddStack(StackTrace stack, bool suppressable = false); void AddThread(const ThreadContext *tctx, bool suppressable = false); Index: lib/tsan/rtl/tsan_rtl_report.cc =================================================================== --- lib/tsan/rtl/tsan_rtl_report.cc +++ lib/tsan/rtl/tsan_rtl_report.cc @@ -164,8 +164,8 @@ (*rs)->suppressable = suppressable; } -void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, StackTrace stack, - const MutexSet *mset) { +void ScopedReport::AddMemoryAccess(uptr addr, uptr external_tag, Shadow s, + StackTrace stack, const MutexSet *mset) { void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); ReportMop *mop = new(mem) ReportMop; rep_->mops.PushBack(mop); @@ -175,6 +175,7 @@ mop->write = s.IsWrite(); mop->atomic = s.IsAtomic(); mop->stack = SymbolizeStack(stack); + mop->external_tag = external_tag; if (mop->stack) mop->stack->suppressable = true; for (uptr i = 0; i < mset->Size(); i++) { @@ -336,6 +337,7 @@ ReportLocation *loc = ReportLocation::New(ReportLocationHeap); loc->heap_chunk_start = (uptr)allocator()->GetBlockBegin((void *)addr); loc->heap_chunk_size = b->siz; + loc->external_tag = b->tag; loc->tid = tctx ? tctx->tid : b->tid; loc->stack = SymbolizeStackId(b->stk); rep_->locs.PushBack(loc); @@ -622,6 +624,8 @@ typ = ReportTypeVptrRace; else if (freed) typ = ReportTypeUseAfterFree; + else if (thr->is_external_race) + typ = ReportTypeExternalRace; if (IsFiredSuppression(ctx, typ, addr)) return; @@ -650,7 +654,8 @@ ScopedReport rep(typ); for (uptr i = 0; i < kMop; i++) { Shadow s(thr->racy_state[i]); - rep.AddMemoryAccess(addr, s, traces[i], i == 0 ? &thr->mset : mset2); + rep.AddMemoryAccess(addr, thr->external_tag, s, traces[i], + i == 0 ? &thr->mset : mset2); } for (uptr i = 0; i < kMop; i++) { Index: lib/tsan/rtl/tsan_suppressions.cc =================================================================== --- lib/tsan/rtl/tsan_suppressions.cc +++ lib/tsan/rtl/tsan_suppressions.cc @@ -74,6 +74,8 @@ return kSuppressionRace; else if (typ == ReportTypeVptrUseAfterFree) return kSuppressionRace; + else if (typ == ReportTypeExternalRace) + return kSuppressionRace; else if (typ == ReportTypeThreadLeak) return kSuppressionThread; else if (typ == ReportTypeMutexDestroyLocked) Index: lib/tsan/rtl/tsan_sync.cc =================================================================== --- lib/tsan/rtl/tsan_sync.cc +++ lib/tsan/rtl/tsan_sync.cc @@ -64,6 +64,7 @@ u32 idx = block_alloc_.Alloc(&thr->proc()->block_cache); MBlock *b = block_alloc_.Map(idx); b->siz = sz; + b->tag = 0; b->tid = thr->tid; b->stk = CurrentStackId(thr, pc); u32 *meta = MemToMeta(p); Index: test/tsan/Darwin/external.cc =================================================================== --- test/tsan/Darwin/external.cc +++ test/tsan/Darwin/external.cc @@ -0,0 +1,153 @@ +// RUN: %clangxx_tsan %s -o %t-lib-instrumented.dylib -shared -DSHARED_LIB +// RUN: %clangxx_tsan %s -o %t-lib-noninstrumented.dylib -shared -DSHARED_LIB -fno-sanitize=thread +// RUN: %clangxx_tsan %s -o %t-lib-noninstrumented-callbacks.dylib -shared -DSHARED_LIB -fno-sanitize=thread -DUSE_TSAN_CALLBACKS +// RUN: %clangxx_tsan %s %t-lib-instrumented.dylib -o %t-lib-instrumented +// RUN: %clangxx_tsan %s %t-lib-noninstrumented.dylib -o %t-lib-noninstrumented +// RUN: %clangxx_tsan %s %t-lib-noninstrumented-callbacks.dylib -o %t-lib-noninstrumented-callbacks + +// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t-lib-instrumented 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST1 +// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %run %t-lib-noninstrumented 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST2 +// RUN: %env_tsan_opts=ignore_interceptors_accesses=1 %deflake %run %t-lib-noninstrumented-callbacks 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=TEST3 + +#include + +#include +#include +#include + +struct MyObject; +typedef MyObject *MyObjectRef; +extern "C" { + void InitializeLibrary(); + MyObject *ObjectCreate(); + long ObjectRead(MyObject *); + void ObjectWrite(MyObject *, long); + void ObjectWriteAnother(MyObject *, long); +} + +#if defined(SHARED_LIB) + +struct MyObject { + long _val; + long _another; +}; + +#if defined(USE_TSAN_CALLBACKS) +static void *tag; +void *(*callback_register_tag)(const char *library_name, const char *object_type); +void *(*callback_assign_tag)(void *addr, void *tag); +void (*callback_read)(void *addr, void *caller_pc, void *tag); +void (*callback_write)(void *addr, void *caller_pc, void *tag); +#endif + +void InitializeLibrary() { +#if defined(USE_TSAN_CALLBACKS) + callback_register_tag = (decltype(callback_register_tag))dlsym(RTLD_DEFAULT, "__tsan_external_register_tag"); + callback_assign_tag = (decltype(callback_assign_tag))dlsym(RTLD_DEFAULT, "__tsan_external_assign_tag"); + callback_read = (decltype(callback_read))dlsym(RTLD_DEFAULT, "__tsan_external_read"); + callback_write = (decltype(callback_write))dlsym(RTLD_DEFAULT, "__tsan_external_write"); + tag = callback_register_tag("MyLibrary", "MyObject"); +#endif +} + +MyObject *ObjectCreate() { + MyObject *ref = (MyObject *)malloc(sizeof(MyObject)); +#if defined(USE_TSAN_CALLBACKS) + callback_assign_tag(ref, tag); +#endif + return ref; +} + +long ObjectRead(MyObject *ref) { +#if defined(USE_TSAN_CALLBACKS) + callback_read(ref, __builtin_return_address(0), tag); +#endif + return ref->_val; +} + +void ObjectWrite(MyObject *ref, long val) { +#if defined(USE_TSAN_CALLBACKS) + callback_write(ref, __builtin_return_address(0), tag); +#endif + ref->_val = val; +} + +void ObjectWriteAnother(MyObject *ref, long val) { +#if defined(USE_TSAN_CALLBACKS) + callback_write(ref, __builtin_return_address(0), tag); +#endif + ref->_another = val; +} + +#else // defined(SHARED_LIB) + +int main(int argc, char *argv[]) { + InitializeLibrary(); + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectRead(ref); }); + std::thread t2([ref]{ ObjectRead(ref); }); + t1.join(); + t2.join(); + } + + // CHECK-NOT: WARNING: ThreadSanitizer + + fprintf(stderr, "RR test done\n"); + // CHECK: RR test done + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectRead(ref); }); + std::thread t2([ref]{ ObjectWrite(ref, 66); }); + t1.join(); + t2.join(); + } + + // TEST1: WARNING: ThreadSanitizer: data race + // TEST1: {{Write|Read}} of size 8 at + // TEST1: Previous {{write|read}} of size 8 at + // TEST1: Location is heap block of size 16 at + + // TEST2-NOT: WARNING: ThreadSanitizer + + // TEST3: WARNING: ThreadSanitizer: race on a library object + // TEST3: {{Mutating|read-only}} access of object MyObject from library MyLibrary at + // TEST3: {{ObjectWrite|ObjectRead}} + // TEST3: Previous {{mutating|read-only}} access of object MyObject from library MyLibrary at + // TEST3: {{ObjectWrite|ObjectRead}} + // TEST3: Location is MyObject object from library MyLibrary of size 16 at + // TEST3: {{ObjectCreate}} + + fprintf(stderr, "RW test done\n"); + // CHECK: RW test done + + { + MyObjectRef ref = ObjectCreate(); + std::thread t1([ref]{ ObjectWrite(ref, 76); }); + std::thread t2([ref]{ ObjectWriteAnother(ref, 77); }); + t1.join(); + t2.join(); + } + + // TEST1-NOT: WARNING: ThreadSanitizer: data race + + // TEST2-NOT: WARNING: ThreadSanitizer + + // TEST3: WARNING: ThreadSanitizer: race on a library object + // TEST3: Mutating access of object MyObject from library MyLibrary at + // TEST3: {{ObjectWrite|ObjectWriteAnother}} + // TEST3: Previous mutating access of object MyObject from library MyLibrary at + // TEST3: {{ObjectWrite|ObjectWriteAnother}} + // TEST3: Location is MyObject object from library MyLibrary of size 16 at + // TEST3: {{ObjectCreate}} + + fprintf(stderr, "WW test done\n"); + // CHECK: WW test done +} + +#endif // defined(SHARED_LIB)