| //===-- tsan_interface_ann.cpp --------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is a part of ThreadSanitizer (TSan), a race detector. |
| // |
| //===----------------------------------------------------------------------===// |
| #include "sanitizer_common/sanitizer_libc.h" |
| #include "sanitizer_common/sanitizer_internal_defs.h" |
| #include "sanitizer_common/sanitizer_placement_new.h" |
| #include "sanitizer_common/sanitizer_stacktrace.h" |
| #include "sanitizer_common/sanitizer_vector.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; |
| |
| namespace __tsan { |
| |
| class ScopedAnnotation { |
| public: |
| ScopedAnnotation(ThreadState *thr, const char *aname, uptr pc) |
| : thr_(thr) { |
| FuncEntry(thr_, pc); |
| DPrintf("#%d: annotation %s()\n", thr_->tid, aname); |
| } |
| |
| ~ScopedAnnotation() { |
| FuncExit(thr_); |
| CheckNoLocks(thr_); |
| } |
| private: |
| ThreadState *const thr_; |
| }; |
| |
| #define SCOPED_ANNOTATION_RET(typ, ret) \ |
| if (!flags()->enable_annotations) \ |
| return ret; \ |
| ThreadState *thr = cur_thread(); \ |
| const uptr caller_pc = (uptr)__builtin_return_address(0); \ |
| StatInc(thr, StatAnnotation); \ |
| StatInc(thr, Stat##typ); \ |
| ScopedAnnotation sa(thr, __func__, caller_pc); \ |
| const uptr pc = StackTrace::GetCurrentPc(); \ |
| (void)pc; \ |
| /**/ |
| |
| #define SCOPED_ANNOTATION(typ) SCOPED_ANNOTATION_RET(typ, ) |
| |
| static const int kMaxDescLen = 128; |
| |
| struct ExpectRace { |
| ExpectRace *next; |
| ExpectRace *prev; |
| atomic_uintptr_t hitcount; |
| atomic_uintptr_t addcount; |
| 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) { |
| atomic_store_relaxed(&race->addcount, |
| atomic_load_relaxed(&race->addcount) + 1); |
| return; |
| } |
| } |
| race = (ExpectRace*)internal_alloc(MBlockExpectRace, sizeof(ExpectRace)); |
| race->addr = addr; |
| race->size = size; |
| race->file = f; |
| race->line = l; |
| race->desc[0] = 0; |
| atomic_store_relaxed(&race->hitcount, 0); |
| atomic_store_relaxed(&race->addcount, 1); |
| 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) |
| 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); |
| atomic_fetch_add(&race->hitcount, 1, memory_order_relaxed); |
| 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) { |
| ReadLock 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; |
| } |
| |
| static void CollectMatchedBenignRaces(Vector<ExpectRace> *matched, |
| int *unique_count, int *hit_count, atomic_uintptr_t ExpectRace::*counter) { |
| ExpectRace *list = &dyn_ann_ctx->benign; |
| for (ExpectRace *race = list->next; race != list; race = race->next) { |
| (*unique_count)++; |
| const uptr cnt = atomic_load_relaxed(&(race->*counter)); |
| if (cnt == 0) |
| continue; |
| *hit_count += cnt; |
| uptr i = 0; |
| for (; i < matched->Size(); i++) { |
| ExpectRace *race0 = &(*matched)[i]; |
| if (race->line == race0->line |
| && internal_strcmp(race->file, race0->file) == 0 |
| && internal_strcmp(race->desc, race0->desc) == 0) { |
| atomic_fetch_add(&(race0->*counter), cnt, memory_order_relaxed); |
| break; |
| } |
| } |
| if (i == matched->Size()) |
| matched->PushBack(*race); |
| } |
| } |
| |
| void PrintMatchedBenignRaces() { |
| Lock lock(&dyn_ann_ctx->mtx); |
| int unique_count = 0; |
| int hit_count = 0; |
| int add_count = 0; |
| Vector<ExpectRace> hit_matched; |
| CollectMatchedBenignRaces(&hit_matched, &unique_count, &hit_count, |
| &ExpectRace::hitcount); |
| Vector<ExpectRace> add_matched; |
| CollectMatchedBenignRaces(&add_matched, &unique_count, &add_count, |
| &ExpectRace::addcount); |
| if (hit_matched.Size()) { |
| Printf("ThreadSanitizer: Matched %d \"benign\" races (pid=%d):\n", |
| hit_count, (int)internal_getpid()); |
| for (uptr i = 0; i < hit_matched.Size(); i++) { |
| Printf("%d %s:%d %s\n", |
| atomic_load_relaxed(&hit_matched[i].hitcount), |
| hit_matched[i].file, hit_matched[i].line, hit_matched[i].desc); |
| } |
| } |
| if (hit_matched.Size()) { |
| Printf("ThreadSanitizer: Annotated %d \"benign\" races, %d unique" |
| " (pid=%d):\n", |
| add_count, unique_count, (int)internal_getpid()); |
| for (uptr i = 0; i < add_matched.Size(); i++) { |
| Printf("%d %s:%d %s\n", |
| atomic_load_relaxed(&add_matched[i].addcount), |
| add_matched[i].file, add_matched[i].line, add_matched[i].desc); |
| } |
| } |
| } |
| |
| static void ReportMissedExpectedRace(ExpectRace *race) { |
| Printf("==================\n"); |
| Printf("WARNING: ThreadSanitizer: missed expected data race\n"); |
| Printf(" %s addr=%zx %s:%d\n", |
| race->desc, race->addr, race->file, race->line); |
| Printf("==================\n"); |
| } |
| } // namespace __tsan |
| |
| using namespace __tsan; |
| |
| extern "C" { |
| void INTERFACE_ATTRIBUTE AnnotateHappensBefore(char *f, int l, uptr addr) { |
| SCOPED_ANNOTATION(AnnotateHappensBefore); |
| Release(thr, pc, addr); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateHappensAfter(char *f, int l, uptr addr) { |
| SCOPED_ANNOTATION(AnnotateHappensAfter); |
| Acquire(thr, pc, addr); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateCondVarSignal(char *f, int l, uptr cv) { |
| SCOPED_ANNOTATION(AnnotateCondVarSignal); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateCondVarSignalAll(char *f, int l, uptr cv) { |
| SCOPED_ANNOTATION(AnnotateCondVarSignalAll); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateMutexIsNotPHB(char *f, int l, uptr mu) { |
| SCOPED_ANNOTATION(AnnotateMutexIsNotPHB); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateCondVarWait(char *f, int l, uptr cv, |
| uptr lock) { |
| SCOPED_ANNOTATION(AnnotateCondVarWait); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateRWLockCreate(char *f, int l, uptr m) { |
| SCOPED_ANNOTATION(AnnotateRWLockCreate); |
| MutexCreate(thr, pc, m, MutexFlagWriteReentrant); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateRWLockCreateStatic(char *f, int l, uptr m) { |
| SCOPED_ANNOTATION(AnnotateRWLockCreateStatic); |
| MutexCreate(thr, pc, m, MutexFlagWriteReentrant | MutexFlagLinkerInit); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateRWLockDestroy(char *f, int l, uptr m) { |
| SCOPED_ANNOTATION(AnnotateRWLockDestroy); |
| MutexDestroy(thr, pc, m); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateRWLockAcquired(char *f, int l, uptr m, |
| uptr is_w) { |
| SCOPED_ANNOTATION(AnnotateRWLockAcquired); |
| if (is_w) |
| MutexPostLock(thr, pc, m, MutexFlagDoPreLockOnPostLock); |
| else |
| MutexPostReadLock(thr, pc, m, MutexFlagDoPreLockOnPostLock); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateRWLockReleased(char *f, int l, uptr m, |
| uptr is_w) { |
| SCOPED_ANNOTATION(AnnotateRWLockReleased); |
| if (is_w) |
| MutexUnlock(thr, pc, m); |
| else |
| MutexReadUnlock(thr, pc, m); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateTraceMemory(char *f, int l, uptr mem) { |
| SCOPED_ANNOTATION(AnnotateTraceMemory); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateFlushState(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateFlushState); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateNewMemory(char *f, int l, uptr mem, |
| uptr size) { |
| SCOPED_ANNOTATION(AnnotateNewMemory); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateNoOp(char *f, int l, uptr mem) { |
| SCOPED_ANNOTATION(AnnotateNoOp); |
| } |
| |
| void INTERFACE_ATTRIBUTE 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 (atomic_load_relaxed(&race->hitcount) == 0) { |
| ctx->nmissed_expected++; |
| ReportMissedExpectedRace(race); |
| } |
| race->prev->next = race->next; |
| race->next->prev = race->prev; |
| internal_free(race); |
| } |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateEnableRaceDetection( |
| char *f, int l, int enable) { |
| SCOPED_ANNOTATION(AnnotateEnableRaceDetection); |
| // FIXME: Reconsider this functionality later. It may be irrelevant. |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateMutexIsUsedAsCondVar( |
| char *f, int l, uptr mu) { |
| SCOPED_ANNOTATION(AnnotateMutexIsUsedAsCondVar); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotatePCQGet( |
| char *f, int l, uptr pcq) { |
| SCOPED_ANNOTATION(AnnotatePCQGet); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotatePCQPut( |
| char *f, int l, uptr pcq) { |
| SCOPED_ANNOTATION(AnnotatePCQPut); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotatePCQDestroy( |
| char *f, int l, uptr pcq) { |
| SCOPED_ANNOTATION(AnnotatePCQDestroy); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotatePCQCreate( |
| char *f, int l, uptr pcq) { |
| SCOPED_ANNOTATION(AnnotatePCQCreate); |
| } |
| |
| void INTERFACE_ATTRIBUTE 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 INTERFACE_ATTRIBUTE AnnotateBenignRaceSized( |
| char *f, int l, uptr mem, uptr size, char *desc) { |
| SCOPED_ANNOTATION(AnnotateBenignRaceSized); |
| BenignRaceImpl(f, l, mem, size, desc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateBenignRace( |
| char *f, int l, uptr mem, char *desc) { |
| SCOPED_ANNOTATION(AnnotateBenignRace); |
| BenignRaceImpl(f, l, mem, 1, desc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreReadsBegin(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreReadsBegin); |
| ThreadIgnoreBegin(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreReadsEnd(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreReadsEnd); |
| ThreadIgnoreEnd(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreWritesBegin(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreWritesBegin); |
| ThreadIgnoreBegin(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreWritesEnd(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreWritesEnd); |
| ThreadIgnoreEnd(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreSyncBegin(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreSyncBegin); |
| ThreadIgnoreSyncBegin(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateIgnoreSyncEnd(char *f, int l) { |
| SCOPED_ANNOTATION(AnnotateIgnoreSyncEnd); |
| ThreadIgnoreSyncEnd(thr, pc); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotatePublishMemoryRange( |
| char *f, int l, uptr addr, uptr size) { |
| SCOPED_ANNOTATION(AnnotatePublishMemoryRange); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateUnpublishMemoryRange( |
| char *f, int l, uptr addr, uptr size) { |
| SCOPED_ANNOTATION(AnnotateUnpublishMemoryRange); |
| } |
| |
| void INTERFACE_ATTRIBUTE AnnotateThreadName( |
| char *f, int l, char *name) { |
| SCOPED_ANNOTATION(AnnotateThreadName); |
| ThreadSetName(thr, name); |
| } |
| |
| // We deliberately omit the implementation of WTFAnnotateHappensBefore() and |
| // WTFAnnotateHappensAfter(). Those are being used by Webkit to annotate |
| // atomic operations, which should be handled by ThreadSanitizer correctly. |
| void INTERFACE_ATTRIBUTE WTFAnnotateHappensBefore(char *f, int l, uptr addr) { |
| SCOPED_ANNOTATION(AnnotateHappensBefore); |
| } |
| |
| void INTERFACE_ATTRIBUTE WTFAnnotateHappensAfter(char *f, int l, uptr addr) { |
| SCOPED_ANNOTATION(AnnotateHappensAfter); |
| } |
| |
| void INTERFACE_ATTRIBUTE WTFAnnotateBenignRaceSized( |
| char *f, int l, uptr mem, uptr sz, char *desc) { |
| SCOPED_ANNOTATION(AnnotateBenignRaceSized); |
| BenignRaceImpl(f, l, mem, sz, desc); |
| } |
| |
| int INTERFACE_ATTRIBUTE RunningOnValgrind() { |
| return flags()->running_on_valgrind; |
| } |
| |
| double __attribute__((weak)) INTERFACE_ATTRIBUTE ValgrindSlowdown(void) { |
| return 10.0; |
| } |
| |
| const char INTERFACE_ATTRIBUTE* ThreadSanitizerQuery(const char *query) { |
| if (internal_strcmp(query, "pure_happens_before") == 0) |
| return "1"; |
| else |
| return "0"; |
| } |
| |
| void INTERFACE_ATTRIBUTE |
| AnnotateMemoryIsInitialized(char *f, int l, uptr mem, uptr sz) {} |
| void INTERFACE_ATTRIBUTE |
| AnnotateMemoryIsUninitialized(char *f, int l, uptr mem, uptr sz) {} |
| |
| // Note: the parameter is called flagz, because flags is already taken |
| // by the global function that returns flags. |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_create(void *m, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_create); |
| MutexCreate(thr, pc, (uptr)m, flagz & MutexCreationFlagMask); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_destroy(void *m, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_destroy); |
| MutexDestroy(thr, pc, (uptr)m, flagz); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_pre_lock(void *m, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_pre_lock); |
| if (!(flagz & MutexFlagTryLock)) { |
| if (flagz & MutexFlagReadLock) |
| MutexPreReadLock(thr, pc, (uptr)m); |
| else |
| MutexPreLock(thr, pc, (uptr)m); |
| } |
| ThreadIgnoreBegin(thr, pc, /*save_stack=*/false); |
| ThreadIgnoreSyncBegin(thr, pc, /*save_stack=*/false); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_post_lock(void *m, unsigned flagz, int rec) { |
| SCOPED_ANNOTATION(__tsan_mutex_post_lock); |
| ThreadIgnoreSyncEnd(thr, pc); |
| ThreadIgnoreEnd(thr, pc); |
| if (!(flagz & MutexFlagTryLockFailed)) { |
| if (flagz & MutexFlagReadLock) |
| MutexPostReadLock(thr, pc, (uptr)m, flagz); |
| else |
| MutexPostLock(thr, pc, (uptr)m, flagz, rec); |
| } |
| } |
| |
| INTERFACE_ATTRIBUTE |
| int __tsan_mutex_pre_unlock(void *m, unsigned flagz) { |
| SCOPED_ANNOTATION_RET(__tsan_mutex_pre_unlock, 0); |
| int ret = 0; |
| if (flagz & MutexFlagReadLock) { |
| CHECK(!(flagz & MutexFlagRecursiveUnlock)); |
| MutexReadUnlock(thr, pc, (uptr)m); |
| } else { |
| ret = MutexUnlock(thr, pc, (uptr)m, flagz); |
| } |
| ThreadIgnoreBegin(thr, pc, /*save_stack=*/false); |
| ThreadIgnoreSyncBegin(thr, pc, /*save_stack=*/false); |
| return ret; |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_post_unlock(void *m, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_post_unlock); |
| ThreadIgnoreSyncEnd(thr, pc); |
| ThreadIgnoreEnd(thr, pc); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_pre_signal(void *addr, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_pre_signal); |
| ThreadIgnoreBegin(thr, pc, /*save_stack=*/false); |
| ThreadIgnoreSyncBegin(thr, pc, /*save_stack=*/false); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_post_signal(void *addr, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_post_signal); |
| ThreadIgnoreSyncEnd(thr, pc); |
| ThreadIgnoreEnd(thr, pc); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_pre_divert(void *addr, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_pre_divert); |
| // Exit from ignore region started in __tsan_mutex_pre_lock/unlock/signal. |
| ThreadIgnoreSyncEnd(thr, pc); |
| ThreadIgnoreEnd(thr, pc); |
| } |
| |
| INTERFACE_ATTRIBUTE |
| void __tsan_mutex_post_divert(void *addr, unsigned flagz) { |
| SCOPED_ANNOTATION(__tsan_mutex_post_divert); |
| ThreadIgnoreBegin(thr, pc, /*save_stack=*/false); |
| ThreadIgnoreSyncBegin(thr, pc, /*save_stack=*/false); |
| } |
| } // extern "C" |