/* Copyright (c) 2008-2010, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This file is part of ThreadSanitizer, a dynamic data race detector. // Author: Konstantin Serebryany. // Author: Timur Iskhodzhanov. // You can find the details on this tool at // http://code.google.com/p/data-race-test #include "thread_sanitizer.h" #include "common_util.h" #include "suppressions.h" #include "ignore.h" #include "ts_lock.h" #include "ts_atomic_int.h" #include "dense_multimap.h" #include <stdarg.h> // -------- Constants --------------- {{{1 // Segment ID (SID) is in range [1, kMaxSID-1] // Segment Set ID (SSID) is in range [-kMaxSID+1, -1] // This is not a compile-time constant, but it can only be changed at startup. int kMaxSID = (1 << 23); // Flush state after so many SIDs have been allocated. Set by command line flag. int kMaxSIDBeforeFlush; // Lock ID (LID) is in range [1, kMaxLID-1] // Lock Set ID (LSID) is in range [-kMaxLID+1, -1] const int kMaxLID = (1 << 23); // This is not a compile-time constant, but it can be changed only at startup. int kSizeOfHistoryStackTrace = 10; // Maximal number of segments in a SegmentSet. // If you change this constant, you also need to change several places // in SegmentSet code. const int kMaxSegmentSetSize = 4; // -------- Globals --------------- {{{1 // If true, ignore all accesses in all threads. bool global_ignore; bool g_so_far_only_one_thread = false; bool g_has_entered_main = false; bool g_has_exited_main = false; size_t g_last_flush_time; // Incremented on each Lock and Unlock. Used by LockHistory. uint32_t g_lock_era = 0; uintptr_t g_nacl_mem_start = (uintptr_t)-1; uintptr_t g_nacl_mem_end = (uintptr_t)-1; bool g_race_verifier_active = false; bool debug_expected_races = false; bool debug_benign_races = false; bool debug_malloc = false; bool debug_free = false; bool debug_thread = false; bool debug_ignore = false; bool debug_rtn = false; bool debug_lock = false; bool debug_wrap = false; bool debug_ins = false; bool debug_shadow_stack = false; bool debug_happens_before = false; bool debug_cache = false; bool debug_race_verifier = false; bool debug_atomic = false; #define PrintfIf(flag, ...) \ do { if ((flag)) Printf(__VA_ARGS__); } while ((void)0, 0) // -------- TIL --------------- {{{1 // ThreadSanitizer Internal lock (scoped). class TIL { public: TIL(TSLock *lock, int lock_site, bool need_locking = true) : lock_(lock), need_locking_(need_locking) { DCHECK(lock_); if (need_locking_ && (TS_SERIALIZED == 0)) { lock_->Lock(); G_stats->lock_sites[lock_site]++; } } ~TIL() { if (need_locking_ && (TS_SERIALIZED == 0)) lock_->Unlock(); } private: TSLock *lock_; bool need_locking_; }; static TSLock *ts_lock; static TSLock *ts_ignore_below_lock; #ifdef TS_LLVM void ThreadSanitizerLockAcquire() { ts_lock->Lock(); } void ThreadSanitizerLockRelease() { ts_lock->Unlock(); } #endif static INLINE void AssertTILHeld() { if (TS_SERIALIZED == 0 && DEBUG_MODE) { ts_lock->AssertHeld(); } } // -------- Util ----------------------------- {{{1 // Can't use ANNOTATE_UNPROTECTED_READ, it may get instrumented. template <class T> inline T INTERNAL_ANNOTATE_UNPROTECTED_READ(const volatile T &x) { ANNOTATE_IGNORE_READS_BEGIN(); T res = x; ANNOTATE_IGNORE_READS_END(); return res; } static string RemoveFilePrefix(string str) { for (size_t i = 0; i < G_flags->file_prefix_to_cut.size(); i++) { string prefix_to_cut = G_flags->file_prefix_to_cut[i]; size_t pos = str.find(prefix_to_cut); if (pos != string::npos) { str = str.substr(pos + prefix_to_cut.size()); } } if (str.find("./") == 0) { // remove leading ./ str = str.substr(2); } return str; } string PcToRtnNameAndFilePos(uintptr_t pc) { G_stats->pc_to_strings++; string img_name; string file_name; string rtn_name; int line_no = -1; PcToStrings(pc, G_flags->demangle, &img_name, &rtn_name, &file_name, &line_no); if (G_flags->demangle && !G_flags->full_stack_frames) rtn_name = NormalizeFunctionName(rtn_name); file_name = RemoveFilePrefix(file_name); if (file_name == "") { return rtn_name + " " + RemoveFilePrefix(img_name); } char buff[10]; snprintf(buff, sizeof(buff), "%d", line_no); return rtn_name + " " + file_name + ":" + buff; } // -------- ID ---------------------- {{{1 // We wrap int32_t into ID class and then inherit various ID type from ID. // This is done in an attempt to implement type safety of IDs, i.e. // to make it impossible to make implicit cast from one ID type to another. class ID { public: typedef int32_t T; explicit ID(T id) : id_(id) {} ID(const ID &id) : id_(id.id_) {} INLINE bool operator == (const ID &id) const { return id_ == id.id_; } bool operator != (const ID &id) const { return id_ != id.id_; } bool operator < (const ID &id) const { return id_ < id.id_; } bool operator > (const ID &id) const { return id_ > id.id_; } bool operator >= (const ID &id) const { return id_ >= id.id_; } bool operator <= (const ID &id) const { return id_ <= id.id_; } bool IsValid() const { return id_ >= 0; } const ID &operator = (const ID &id) { this->id_ = id.id_; return *this; } T raw() const { return id_; } private: T id_; }; // Thread ID. // id >= 0 class TID: public ID { public: static const int32_t kInvalidTID; explicit TID(T id) : ID(id) {} TID() : ID(kInvalidTID) {} bool valid() const { return raw() >= 0; } }; const int32_t TID::kInvalidTID = -1; // Segment ID. // id > 0 && id < kMaxSID class SID: public ID { public: explicit SID(T id) : ID(id) {} SID() : ID(0) {} bool valid() const { return raw() > 0 && raw() < kMaxSID; } }; // Lock ID. // id > 0 && id < kMaxLID class LID: public ID { public: explicit LID(T id) : ID(id) {} LID() : ID(0) {} bool valid() const { return raw() > 0 && raw() < kMaxLID; } }; // LockSet ID. // Empty lockset: id == 0 // Singleton: id > 0 (id == Lock's id) // Tuple: id < 0 class LSID: public ID { public: explicit LSID(T id) : ID(id) {} LSID() : ID(INT_MAX) {} bool valid() const { return raw() < kMaxLID && raw() > -(kMaxLID); } bool IsEmpty() const { return raw() == 0; } bool IsSingleton() const { return raw() > 0; } LID GetSingleton() const { return LID(raw()); } }; // SegmentSet ID. // Empty SegmentSet: id == 0 // Singleton: id > 0 (id == Segment's id) // Tuple: id < 0 class SSID: public ID { public: explicit SSID(T id) : ID(id) {} explicit SSID(SID sid) : ID(sid.raw()) {} SSID(): ID(INT_MAX) {} bool valid() const { return raw() != 0 && raw() < kMaxSID && raw() > -kMaxSID; } bool IsValidOrEmpty() { return raw() < kMaxSID && raw() > -kMaxSID; } bool IsEmpty() const { return raw() == 0; } bool IsSingleton() const {return raw() > 0; } bool IsTuple() const {return raw() < 0; } SID GetSingleton() const { DCHECK(IsSingleton()); return SID(raw()); } // TODO(timurrrr): need to start SegmentSetArray indices from 1 // to avoid "int ???() { return -raw() - 1; }" }; // -------- Colors ----------------------------- {{{1 // Colors for ansi terminals and for html. const char *c_bold = ""; const char *c_red = ""; const char *c_green = ""; const char *c_magenta = ""; const char *c_cyan = ""; const char *c_blue = ""; const char *c_yellow = ""; const char *c_default = ""; // -------- Forward decls ------ {{{1 static void ForgetAllStateAndStartOver(TSanThread *thr, const char *reason); static void FlushStateIfOutOfSegments(TSanThread *thr); static int32_t raw_tid(TSanThread *t); // -------- Simple Cache ------ {{{1 #include "ts_simple_cache.h" // -------- PairCache & IntPairToIntCache ------ {{{1 template <typename A, typename B, typename Ret, int kHtableSize, int kArraySize = 8> class PairCache { public: PairCache() { CHECK(kHtableSize >= 0); CHECK(sizeof(Entry) == sizeof(A) + sizeof(B) + sizeof(Ret)); Flush(); } void Flush() { memset(this, 0, sizeof(*this)); // Change the first hashtable entry so it doesn't match (0,0) on Lookup. if (kHtableSize != 0) memset(&htable_[0], 1, sizeof(Entry)); // Any Lookup should fail now. for (int i = 0; i < kHtableSize; i++) { Ret tmp; DCHECK(!Lookup(htable_[i].a, htable_[i].b, &tmp)); } CHECK(array_pos_ == 0); CHECK(array_filled_ == false); } void Insert(A a, B b, Ret v) { // fill the hash table if (kHtableSize != 0) { uint32_t idx = compute_idx(a, b); htable_[idx].Fill(a, b, v); } // fill the array Ret dummy; if (kArraySize != 0 && !ArrayLookup(a, b, &dummy)) { array_[array_pos_ % kArraySize].Fill(a, b, v); array_pos_ = (array_pos_ + 1) % kArraySize; if (array_pos_ > kArraySize) array_filled_ = true; } } INLINE bool Lookup(A a, B b, Ret *v) { // check the array if (kArraySize != 0 && ArrayLookup(a, b, v)) { G_stats->ls_cache_fast++; return true; } // check the hash table. if (kHtableSize != 0) { uint32_t idx = compute_idx(a, b); Entry & prev_e = htable_[idx]; if (prev_e.Match(a, b)) { *v = prev_e.v; return true; } } return false; } private: struct Entry { A a; B b; Ret v; void Fill(A a, B b, Ret v) { this->a = a; this->b = b; this->v = v; } bool Match(A a, B b) const { return this->a == a && this->b == b; } }; INLINE bool ArrayLookup(A a, B b, Ret *v) { for (int i = 0; i < (array_filled_ ? kArraySize : array_pos_); i++) { Entry & entry = array_[i]; if (entry.Match(a, b)) { *v = entry.v; return true; } } return false; } uint32_t compute_idx(A a, B b) { if (kHtableSize == 0) return 0; else return combine2(a, b) % kHtableSize; } static uint32_t combine2(int a, int b) { return (a << 16) ^ b; } static uint32_t combine2(SSID a, SID b) { return combine2(a.raw(), b.raw()); } Entry htable_[kHtableSize]; Entry array_[kArraySize]; // array_pos_ - next element to write to the array_ (mod kArraySize) // array_filled_ - set to true once we write the last element of the array int array_pos_; bool array_filled_; }; template<int kHtableSize, int kArraySize = 8> class IntPairToIntCache : public PairCache<int, int, int, kHtableSize, kArraySize> {}; // -------- FreeList --------------- {{{1 class FreeList { public: FreeList(int obj_size, int chunk_size) : list_(0), obj_size_(obj_size), chunk_size_(chunk_size) { CHECK_GE(obj_size_, static_cast<int>(sizeof(NULL))); CHECK((obj_size_ % sizeof(NULL)) == 0); CHECK_GE(chunk_size_, 1); } void *Allocate() { if (!list_) AllocateNewChunk(); CHECK(list_); List *head = list_; list_ = list_->next; return reinterpret_cast<void*>(head); } void Deallocate(void *ptr) { if (DEBUG_MODE) { memset(ptr, 0xac, obj_size_); } List *new_head = reinterpret_cast<List*>(ptr); new_head->next = list_; list_ = new_head; } private: void AllocateNewChunk() { CHECK(list_ == NULL); uint8_t *new_mem = new uint8_t[obj_size_ * chunk_size_]; if (DEBUG_MODE) { memset(new_mem, 0xab, obj_size_ * chunk_size_); } for (int i = 0; i < chunk_size_; i++) { List *new_head = reinterpret_cast<List*>(new_mem + obj_size_ * i); new_head->next = list_; list_ = new_head; } } struct List { struct List *next; }; List *list_; const int obj_size_; const int chunk_size_; }; // -------- StackTrace -------------- {{{1 class StackTraceFreeList { public: uintptr_t *GetNewMemForStackTrace(size_t capacity) { DCHECK(capacity <= (size_t)G_flags->num_callers); return reinterpret_cast<uintptr_t*>(free_lists_[capacity]->Allocate()); } void TakeStackTraceBack(uintptr_t *mem, size_t capacity) { DCHECK(capacity <= (size_t)G_flags->num_callers); free_lists_[capacity]->Deallocate(mem); } StackTraceFreeList() { size_t n = G_flags->num_callers + 1; free_lists_ = new FreeList *[n]; free_lists_[0] = NULL; for (size_t i = 1; i < n; i++) { free_lists_[i] = new FreeList((i+2) * sizeof(uintptr_t), 1024); } } private: FreeList **free_lists_; // Array of G_flags->num_callers lists. }; static StackTraceFreeList *g_stack_trace_free_list; class StackTrace { public: static StackTrace *CreateNewEmptyStackTrace(size_t size, size_t capacity = 0) { ScopedMallocCostCenter cc("StackTrace::CreateNewEmptyStackTrace()"); DCHECK(g_stack_trace_free_list); DCHECK(size != 0); if (capacity == 0) capacity = size; uintptr_t *mem = g_stack_trace_free_list->GetNewMemForStackTrace(capacity); DCHECK(mem); StackTrace *res = new(mem) StackTrace(size, capacity); return res; } static void Delete(StackTrace *trace) { if (!trace) return; DCHECK(g_stack_trace_free_list); g_stack_trace_free_list->TakeStackTraceBack( reinterpret_cast<uintptr_t*>(trace), trace->capacity()); } size_t size() const { return size_; } size_t capacity() const { return capacity_; } void set_size(size_t size) { CHECK(size <= capacity()); size_ = size; } void Set(size_t i, uintptr_t pc) { arr_[i] = pc; } uintptr_t Get(size_t i) const { return arr_[i]; } static bool CutStackBelowFunc(const string func_name) { for (size_t i = 0; i < G_flags->cut_stack_below.size(); i++) { if (StringMatch(G_flags->cut_stack_below[i], func_name)) { return true; } } return false; } static string EmbeddedStackTraceToString(const uintptr_t *emb_trace, size_t n, const char *indent = " ") { string res = ""; const int kBuffSize = 10000; char *buff = new char [kBuffSize]; for (size_t i = 0; i < n; i++) { if (!emb_trace[i]) break; string rtn_and_file = PcToRtnNameAndFilePos(emb_trace[i]); if (rtn_and_file.find("(below main) ") == 0 || rtn_and_file.find("ThreadSanitizerStartThread ") == 0) break; if (i == 0) res += c_bold; if (G_flags->show_pc) { snprintf(buff, kBuffSize, "%s#%-2d %p: ", indent, static_cast<int>(i), reinterpret_cast<void*>(emb_trace[i])); } else { snprintf(buff, kBuffSize, "%s#%-2d ", indent, static_cast<int>(i)); } res += buff; res += rtn_and_file; if (i == 0) res += c_default; res += "\n"; // don't print after main ... if (rtn_and_file.find("main ") == 0) break; // ... and after some default functions (see ThreadSanitizerParseFlags()) // and some more functions specified via command line flag. string rtn = NormalizeFunctionName(PcToRtnName(emb_trace[i], true)); if (CutStackBelowFunc(rtn)) break; } delete [] buff; return res; } string ToString(const char *indent = " ") const { if (!this) return "NO STACK TRACE\n"; if (size() == 0) return "EMPTY STACK TRACE\n"; return EmbeddedStackTraceToString(arr_, size(), indent); } void PrintRaw() const { for (size_t i = 0; i < size(); i++) { Printf("%p ", arr_[i]); } Printf("\n"); } static bool Equals(const StackTrace *t1, const StackTrace *t2) { if (t1->size_ != t2->size_) return false; for (size_t i = 0; i < t1->size_; i++) { if (t1->arr_[i] != t2->arr_[i]) return false; } return true; } struct Less { bool operator() (const StackTrace *t1, const StackTrace *t2) const { size_t size = min(t1->size_, t2->size_); for (size_t i = 0; i < size; i++) { if (t1->arr_[i] != t2->arr_[i]) { return (t1->arr_[i] < t2->arr_[i]); } } return t1->size_ < t2->size_; } }; private: StackTrace(size_t size, size_t capacity) : size_(size), capacity_(capacity) { } ~StackTrace() {} size_t size_; size_t capacity_; uintptr_t arr_[]; }; // -------- Lock -------------------- {{{1 const char *kLockAllocCC = "kLockAllocCC"; class Lock { public: static Lock *Create(uintptr_t lock_addr) { ScopedMallocCostCenter cc("LockLookup"); // Printf("Lock::Create: %p\n", lock_addr); // Destroy(lock_addr); // CHECK(Lookup(lock_addr) == NULL); Lock *res = LookupOrCreate(lock_addr); res->rd_held_ = 0; res->wr_held_ = 0; res->is_pure_happens_before_ = G_flags->pure_happens_before; res->last_lock_site_ = NULL; return res; } static void Destroy(uintptr_t lock_addr) { // Printf("Lock::Destroy: %p\n", lock_addr); // map_.erase(lock_addr); } static NOINLINE Lock *LookupOrCreate(uintptr_t lock_addr) { ScopedMallocCostCenter cc("LockLookup"); Lock **lock = &(*map_)[lock_addr]; if (*lock == NULL) { // Printf("Lock::LookupOrCreate: %p\n", lock_addr); ScopedMallocCostCenter cc_lock("new Lock"); *lock = new Lock(lock_addr, map_->size()); } return *lock; } static NOINLINE Lock *Lookup(uintptr_t lock_addr) { ScopedMallocCostCenter cc("LockLookup"); Map::iterator it = map_->find(lock_addr); if (it == map_->end()) return NULL; return it->second; } int rd_held() const { return rd_held_; } int wr_held() const { return wr_held_; } uintptr_t lock_addr() const { return lock_addr_; } LID lid() const { return lid_; } bool is_pure_happens_before() const { return is_pure_happens_before_; } // When a lock is pure happens-before, we need to create hb arcs // between all Unlock/Lock pairs except RdUnlock/RdLock. // For that purpose have two IDs on which we signal/wait. // One id is the lock_addr itself, the second id is derived // from lock_addr. uintptr_t wr_signal_addr() const { return lock_addr(); } uintptr_t rd_signal_addr() const { return lock_addr() + 1; } void set_is_pure_happens_before(bool x) { is_pure_happens_before_ = x; } void WrLock(TID tid, StackTrace *lock_site) { CHECK(!rd_held_); if (wr_held_ == 0) { thread_holding_me_in_write_mode_ = tid; } else { CHECK(thread_holding_me_in_write_mode_ == tid); } wr_held_++; StackTrace::Delete(last_lock_site_); last_lock_site_ = lock_site; } void WrUnlock() { CHECK(!rd_held_); CHECK(wr_held_ > 0); wr_held_--; } void RdLock(StackTrace *lock_site) { CHECK(!wr_held_); rd_held_++; StackTrace::Delete(last_lock_site_); last_lock_site_ = lock_site; } void RdUnlock() { CHECK(!wr_held_); CHECK(rd_held_); rd_held_--; } void set_name(const char *name) { name_ = name; } const char *name() const { return name_; } string ToString() const { string res; char buff[100]; snprintf(buff, sizeof(buff), "L%d", lid_.raw()); // do we need to print the address? // reinterpret_cast<void*>(lock_addr())); res = buff; if (name()) { res += string(" ") + name(); } return res; } static Lock *LIDtoLock(LID lid) { // slow, but needed only for reports. for (Map::iterator it = map_->begin(); it != map_->end(); ++it) { Lock *l = it->second; if (l->lid_ == lid) { return l; } } return NULL; } static string ToString(LID lid) { Lock *lock = LIDtoLock(lid); CHECK(lock); return lock->ToString(); } static void ReportLockWithOrWithoutContext(LID lid, bool with_context) { if (!with_context) { Report(" L%d\n", lid.raw()); return; } Lock *lock = LIDtoLock(lid); CHECK(lock); if (lock->last_lock_site_) { Report(" %s (%p)\n%s", lock->ToString().c_str(), lock->lock_addr_, lock->last_lock_site_->ToString().c_str()); } else { Report(" %s. This lock was probably destroyed" " w/o calling Unlock()\n", lock->ToString().c_str()); } } static void InitClassMembers() { map_ = new Lock::Map; } private: Lock(uintptr_t lock_addr, int32_t lid) : lock_addr_(lock_addr), lid_(lid), rd_held_(0), wr_held_(0), is_pure_happens_before_(G_flags->pure_happens_before), last_lock_site_(0), name_(NULL) { } // Data members uintptr_t lock_addr_; LID lid_; int rd_held_; int wr_held_; bool is_pure_happens_before_; StackTrace *last_lock_site_; const char *name_; TID thread_holding_me_in_write_mode_; // Static members typedef map<uintptr_t, Lock*> Map; static Map *map_; }; Lock::Map *Lock::map_; // Returns a string like "L123,L234". static string SetOfLocksToString(const set<LID> &locks) { string res; for (set<LID>::const_iterator it = locks.begin(); it != locks.end(); ++it) { LID lid = *it; char buff[100]; snprintf(buff, sizeof(buff), "L%d", lid.raw()); if (it != locks.begin()) res += ", "; res += buff; } return res; } // -------- FixedArray--------------- {{{1 template <typename T, size_t SizeLimit = 1024> class FixedArray { public: explicit INLINE FixedArray(size_t array_size) : size_(array_size), array_((array_size <= SizeLimit ? alloc_space_ : new T[array_size])) { } ~FixedArray() { if (array_ != alloc_space_) { delete[] array_; } } T* begin() { return array_; } T& operator[](int i) { return array_[i]; } private: const size_t size_; T* array_; T alloc_space_[SizeLimit]; }; // -------- LockSet ----------------- {{{1 class LockSet { public: NOINLINE static LSID Add(LSID lsid, Lock *lock) { ScopedMallocCostCenter cc("LockSetAdd"); LID lid = lock->lid(); if (lsid.IsEmpty()) { // adding to an empty lock set G_stats->ls_add_to_empty++; return LSID(lid.raw()); } int cache_res; if (ls_add_cache_->Lookup(lsid.raw(), lid.raw(), &cache_res)) { G_stats->ls_add_cache_hit++; return LSID(cache_res); } LSID res; if (lsid.IsSingleton()) { LSSet set(lsid.GetSingleton(), lid); G_stats->ls_add_to_singleton++; res = ComputeId(set); } else { LSSet set(Get(lsid), lid); G_stats->ls_add_to_multi++; res = ComputeId(set); } ls_add_cache_->Insert(lsid.raw(), lid.raw(), res.raw()); return res; } // If lock is present in lsid, set new_lsid to (lsid \ lock) and return true. // Otherwise set new_lsid to lsid and return false. NOINLINE static bool Remove(LSID lsid, Lock *lock, LSID *new_lsid) { *new_lsid = lsid; if (lsid.IsEmpty()) return false; LID lid = lock->lid(); if (lsid.IsSingleton()) { // removing the only lock -> LSID(0) if (lsid.GetSingleton() != lid) return false; G_stats->ls_remove_from_singleton++; *new_lsid = LSID(0); return true; } int cache_res; if (ls_rem_cache_->Lookup(lsid.raw(), lid.raw(), &cache_res)) { G_stats->ls_rem_cache_hit++; *new_lsid = LSID(cache_res); return true; } LSSet &prev_set = Get(lsid); if (!prev_set.has(lid)) return false; LSSet set(prev_set, LSSet::REMOVE, lid); CHECK(set.size() == prev_set.size() - 1); G_stats->ls_remove_from_multi++; LSID res = ComputeId(set); ls_rem_cache_->Insert(lsid.raw(), lid.raw(), res.raw()); *new_lsid = res; return true; } NOINLINE static bool IntersectionIsEmpty(LSID lsid1, LSID lsid2) { // at least one empty if (lsid1.IsEmpty() || lsid2.IsEmpty()) return true; // empty // both singletons if (lsid1.IsSingleton() && lsid2.IsSingleton()) { return lsid1 != lsid2; } // first is singleton, second is not if (lsid1.IsSingleton()) { const LSSet &set2 = Get(lsid2); return set2.has(LID(lsid1.raw())) == false; } // second is singleton, first is not if (lsid2.IsSingleton()) { const LSSet &set1 = Get(lsid1); return set1.has(LID(lsid2.raw())) == false; } // LockSets are equal and not empty if (lsid1 == lsid2) return false; // both are not singletons - slow path. bool ret = true, cache_hit = false; DCHECK(lsid2.raw() < 0); if (ls_intersection_cache_->Lookup(lsid1.raw(), -lsid2.raw(), &ret)) { if (!DEBUG_MODE) return ret; cache_hit = true; } const LSSet &set1 = Get(lsid1); const LSSet &set2 = Get(lsid2); FixedArray<LID> intersection(min(set1.size(), set2.size())); LID *end = set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), intersection.begin()); DCHECK(!cache_hit || (ret == (end == intersection.begin()))); ret = (end == intersection.begin()); ls_intersection_cache_->Insert(lsid1.raw(), -lsid2.raw(), ret); return ret; } static bool HasNonPhbLocks(LSID lsid) { if (lsid.IsEmpty()) return false; if (lsid.IsSingleton()) return !Lock::LIDtoLock(LID(lsid.raw()))->is_pure_happens_before(); LSSet &set = Get(lsid); for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) if (!Lock::LIDtoLock(*it)->is_pure_happens_before()) return true; return false; } static string ToString(LSID lsid) { if (lsid.IsEmpty()) { return "{}"; } else if (lsid.IsSingleton()) { return "{" + Lock::ToString(lsid.GetSingleton()) + "}"; } const LSSet &set = Get(lsid); string res = "{"; for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) { if (it != set.begin()) res += ", "; res += Lock::ToString(*it); } res += "}"; return res; } static void ReportLockSetWithContexts(LSID lsid, set<LID> *locks_reported, const char *descr) { if (lsid.IsEmpty()) return; Report("%s%s%s\n", c_green, descr, c_default); if (lsid.IsSingleton()) { LID lid = lsid.GetSingleton(); Lock::ReportLockWithOrWithoutContext(lid, locks_reported->count(lid) == 0); locks_reported->insert(lid); } else { const LSSet &set = Get(lsid); for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) { LID lid = *it; Lock::ReportLockWithOrWithoutContext(lid, locks_reported->count(lid) == 0); locks_reported->insert(lid); } } } static void AddLocksToSet(LSID lsid, set<LID> *locks) { if (lsid.IsEmpty()) return; if (lsid.IsSingleton()) { locks->insert(lsid.GetSingleton()); } else { const LSSet &set = Get(lsid); for (LSSet::const_iterator it = set.begin(); it != set.end(); ++it) { locks->insert(*it); } } } static void InitClassMembers() { map_ = new LockSet::Map; vec_ = new LockSet::Vec; ls_add_cache_ = new LSCache; ls_rem_cache_ = new LSCache; ls_rem_cache_ = new LSCache; ls_intersection_cache_ = new LSIntersectionCache; } private: // No instances are allowed. LockSet() { } typedef DenseMultimap<LID, 3> LSSet; static LSSet &Get(LSID lsid) { ScopedMallocCostCenter cc(__FUNCTION__); int idx = -lsid.raw() - 1; DCHECK(idx >= 0); DCHECK(idx < static_cast<int>(vec_->size())); return (*vec_)[idx]; } static LSID ComputeId(const LSSet &set) { CHECK(set.size() > 0); if (set.size() == 1) { // signleton lock set has lsid == lid. return LSID(set.begin()->raw()); } DCHECK(map_); DCHECK(vec_); // multiple locks. ScopedMallocCostCenter cc("LockSet::ComputeId"); int32_t *id = &(*map_)[set]; if (*id == 0) { vec_->push_back(set); *id = map_->size(); if (set.size() == 2) G_stats->ls_size_2++; else if (set.size() == 3) G_stats->ls_size_3++; else if (set.size() == 4) G_stats->ls_size_4++; else if (set.size() == 5) G_stats->ls_size_5++; else G_stats->ls_size_other++; if (*id >= 4096 && ((*id & (*id - 1)) == 0)) { Report("INFO: %d LockSet IDs have been allocated " "(2: %ld 3: %ld 4: %ld 5: %ld o: %ld)\n", *id, G_stats->ls_size_2, G_stats->ls_size_3, G_stats->ls_size_4, G_stats->ls_size_5, G_stats->ls_size_other ); } } return LSID(-*id); } typedef map<LSSet, int32_t> Map; static Map *map_; static const char *kLockSetVecAllocCC; typedef vector<LSSet> Vec; static Vec *vec_; // static const int kPrimeSizeOfLsCache = 307; // static const int kPrimeSizeOfLsCache = 499; static const int kPrimeSizeOfLsCache = 1021; typedef IntPairToIntCache<kPrimeSizeOfLsCache> LSCache; static LSCache *ls_add_cache_; static LSCache *ls_rem_cache_; static LSCache *ls_int_cache_; typedef IntPairToBoolCache<kPrimeSizeOfLsCache> LSIntersectionCache; static LSIntersectionCache *ls_intersection_cache_; }; LockSet::Map *LockSet::map_; LockSet::Vec *LockSet::vec_; const char *LockSet::kLockSetVecAllocCC = "kLockSetVecAllocCC"; LockSet::LSCache *LockSet::ls_add_cache_; LockSet::LSCache *LockSet::ls_rem_cache_; LockSet::LSCache *LockSet::ls_int_cache_; LockSet::LSIntersectionCache *LockSet::ls_intersection_cache_; static string TwoLockSetsToString(LSID rd_lockset, LSID wr_lockset) { string res; if (rd_lockset == wr_lockset) { res = "L"; res += LockSet::ToString(wr_lockset); } else { res = "WR-L"; res += LockSet::ToString(wr_lockset); res += "/RD-L"; res += LockSet::ToString(rd_lockset); } return res; } // -------- VTS ------------------ {{{1 class VTS { public: static size_t MemoryRequiredForOneVts(size_t size) { return sizeof(VTS) + size * sizeof(TS); } static size_t RoundUpSizeForEfficientUseOfFreeList(size_t size) { if (size < 32) return size; if (size < 64) return (size + 7) & ~7; if (size < 128) return (size + 15) & ~15; return (size + 31) & ~31; } static VTS *Create(size_t size) { DCHECK(size > 0); void *mem; size_t rounded_size = RoundUpSizeForEfficientUseOfFreeList(size); DCHECK(size <= rounded_size); if (rounded_size <= kNumberOfFreeLists) { // Small chunk, use FreeList. ScopedMallocCostCenter cc("VTS::Create (from free list)"); mem = free_lists_[rounded_size]->Allocate(); G_stats->vts_create_small++; } else { // Large chunk, use new/delete instead of FreeList. ScopedMallocCostCenter cc("VTS::Create (from new[])"); mem = new int8_t[MemoryRequiredForOneVts(size)]; G_stats->vts_create_big++; } VTS *res = new(mem) VTS(size); G_stats->vts_total_create += size; return res; } static void Unref(VTS *vts) { if (!vts) return; CHECK_GT(vts->ref_count_, 0); if (AtomicDecrementRefcount(&vts->ref_count_) == 0) { size_t size = vts->size_; // can't use vts->size(). size_t rounded_size = RoundUpSizeForEfficientUseOfFreeList(size); if (rounded_size <= kNumberOfFreeLists) { free_lists_[rounded_size]->Deallocate(vts); G_stats->vts_delete_small++; } else { G_stats->vts_delete_big++; delete vts; } G_stats->vts_total_delete += rounded_size; } } static VTS *CreateSingleton(TID tid, int32_t clk = 1) { VTS *res = Create(1); res->arr_[0].tid = tid.raw(); res->arr_[0].clk = clk; return res; } VTS *Clone() { G_stats->vts_clone++; AtomicIncrementRefcount(&ref_count_); return this; } static VTS *CopyAndTick(const VTS *vts, TID id_to_tick) { CHECK(vts->ref_count_); VTS *res = Create(vts->size()); bool found = false; for (size_t i = 0; i < res->size(); i++) { res->arr_[i] = vts->arr_[i]; if (res->arr_[i].tid == id_to_tick.raw()) { res->arr_[i].clk++; found = true; } } CHECK(found); return res; } static VTS *Join(const VTS *vts_a, const VTS *vts_b) { CHECK(vts_a->ref_count_); CHECK(vts_b->ref_count_); FixedArray<TS> result_ts(vts_a->size() + vts_b->size()); TS *t = result_ts.begin(); const TS *a = &vts_a->arr_[0]; const TS *b = &vts_b->arr_[0]; const TS *a_max = a + vts_a->size(); const TS *b_max = b + vts_b->size(); while (a < a_max && b < b_max) { if (a->tid < b->tid) { *t = *a; a++; t++; } else if (a->tid > b->tid) { *t = *b; b++; t++; } else { if (a->clk >= b->clk) { *t = *a; } else { *t = *b; } a++; b++; t++; } } while (a < a_max) { *t = *a; a++; t++; } while (b < b_max) { *t = *b; b++; t++; } VTS *res = VTS::Create(t - result_ts.begin()); for (size_t i = 0; i < res->size(); i++) { res->arr_[i] = result_ts[i]; } return res; } int32_t clk(TID tid) const { // TODO(dvyukov): this function is sub-optimal, // we only need thread's own clock. for (size_t i = 0; i < size_; i++) { if (arr_[i].tid == tid.raw()) { return arr_[i].clk; } } return 0; } static INLINE void FlushHBCache() { hb_cache_->Flush(); } static INLINE bool HappensBeforeCached(const VTS *vts_a, const VTS *vts_b) { bool res = false; if (hb_cache_->Lookup(vts_a->uniq_id_, vts_b->uniq_id_, &res)) { G_stats->n_vts_hb_cached++; DCHECK(res == HappensBefore(vts_a, vts_b)); return res; } res = HappensBefore(vts_a, vts_b); hb_cache_->Insert(vts_a->uniq_id_, vts_b->uniq_id_, res); return res; } // return true if vts_a happens-before vts_b. static NOINLINE bool HappensBefore(const VTS *vts_a, const VTS *vts_b) { CHECK(vts_a->ref_count_); CHECK(vts_b->ref_count_); G_stats->n_vts_hb++; const TS *a = &vts_a->arr_[0]; const TS *b = &vts_b->arr_[0]; const TS *a_max = a + vts_a->size(); const TS *b_max = b + vts_b->size(); bool a_less_than_b = false; while (a < a_max && b < b_max) { if (a->tid < b->tid) { // a->tid is not present in b. return false; } else if (a->tid > b->tid) { // b->tid is not present in a. a_less_than_b = true; b++; } else { // this tid is present in both VTSs. Compare clocks. if (a->clk > b->clk) return false; if (a->clk < b->clk) a_less_than_b = true; a++; b++; } } if (a < a_max) { // Some tids are present in a and not in b return false; } if (b < b_max) { return true; } return a_less_than_b; } size_t size() const { DCHECK(ref_count_); return size_; } string ToString() const { DCHECK(ref_count_); string res = "["; for (size_t i = 0; i < size(); i++) { char buff[100]; snprintf(buff, sizeof(buff), "%d:%d;", arr_[i].tid, arr_[i].clk); if (i) res += " "; res += buff; } return res + "]"; } void print(const char *name) const { string str = ToString(); Printf("%s: %s\n", name, str.c_str()); } static void TestHappensBefore() { // TODO(kcc): need more tests here... const char *test_vts[] = { "[0:1;]", "[0:4; 2:1;]", "[0:4; 2:2; 4:1;]", "[0:4; 3:2; 4:1;]", "[0:4; 3:2; 4:2;]", "[0:4; 3:3; 4:1;]", NULL }; for (int i = 0; test_vts[i]; i++) { const VTS *vts1 = Parse(test_vts[i]); for (int j = 0; test_vts[j]; j++) { const VTS *vts2 = Parse(test_vts[j]); bool hb = HappensBefore(vts1, vts2); Printf("HB = %d\n %s\n %s\n", static_cast<int>(hb), vts1->ToString().c_str(), vts2->ToString().c_str()); delete vts2; } delete vts1; } } static void Test() { Printf("VTS::test();\n"); VTS *v1 = CreateSingleton(TID(0)); VTS *v2 = CreateSingleton(TID(1)); VTS *v3 = CreateSingleton(TID(2)); VTS *v4 = CreateSingleton(TID(3)); VTS *v12 = Join(v1, v2); v12->print("v12"); VTS *v34 = Join(v3, v4); v34->print("v34"); VTS *x1 = Parse("[0:4; 3:6; 4:2;]"); CHECK(x1); x1->print("x1"); TestHappensBefore(); } // Parse VTS string in the form "[0:4; 3:6; 4:2;]". static VTS *Parse(const char *str) { #if 1 // TODO(kcc): need sscanf in valgrind return NULL; #else vector<TS> vec; if (!str) return NULL; if (str[0] != '[') return NULL; str++; int tid = 0, clk = 0; int consumed = 0; while (sscanf(str, "%d:%d;%n", &tid, &clk, &consumed) > 0) { TS ts; ts.tid = TID(tid); ts.clk = clk; vec.push_back(ts); str += consumed; // Printf("%d:%d\n", tid, clk); } if (*str != ']') return NULL; VTS *res = Create(vec.size()); for (size_t i = 0; i < vec.size(); i++) { res->arr_[i] = vec[i]; } return res; #endif } static void InitClassMembers() { hb_cache_ = new HBCache; free_lists_ = new FreeList *[kNumberOfFreeLists+1]; free_lists_[0] = 0; for (size_t i = 1; i <= kNumberOfFreeLists; i++) { free_lists_[i] = new FreeList(MemoryRequiredForOneVts(i), (kNumberOfFreeLists * 4) / i); } } int32_t uniq_id() const { return uniq_id_; } private: explicit VTS(size_t size) : ref_count_(1), size_(size) { uniq_id_counter_++; // If we've got overflow, we are in trouble, need to have 64-bits... CHECK_GT(uniq_id_counter_, 0); uniq_id_ = uniq_id_counter_; } ~VTS() {} struct TS { int32_t tid; int32_t clk; }; // data members int32_t ref_count_; int32_t uniq_id_; size_t size_; TS arr_[]; // array of size_ elements. // static data members static int32_t uniq_id_counter_; static const int kCacheSize = 4999; // Has to be prime. typedef IntPairToBoolCache<kCacheSize> HBCache; static HBCache *hb_cache_; static const size_t kNumberOfFreeLists = 512; // Must be power of two. // static const size_t kNumberOfFreeLists = 64; // Must be power of two. static FreeList **free_lists_; // Array of kNumberOfFreeLists elements. }; int32_t VTS::uniq_id_counter_; VTS::HBCache *VTS::hb_cache_; FreeList **VTS::free_lists_; // This class is somewhat similar to VTS, // but it's mutable, not reference counted and not sorted. class VectorClock { public: VectorClock() : size_(), clock_() { } void reset() { free(clock_); size_ = 0; clock_ = NULL; } int32_t clock(TID tid) const { for (size_t i = 0; i != size_; i += 1) { if (clock_[i].tid == tid.raw()) { return clock_[i].clk; } } return 0; } void update(TID tid, int32_t clk) { for (size_t i = 0; i != size_; i += 1) { if (clock_[i].tid == tid.raw()) { clock_[i].clk = clk; return; } } size_ += 1; clock_ = (TS*)realloc(clock_, size_ * sizeof(TS)); clock_[size_ - 1].tid = tid.raw(); clock_[size_ - 1].clk = clk; } private: struct TS { int32_t tid; int32_t clk; }; size_t size_; TS* clock_; }; // -------- Mask -------------------- {{{1 // A bit mask (32-bits on 32-bit arch and 64-bits on 64-bit arch). class Mask { public: static const uintptr_t kOne = 1; static const uintptr_t kNBits = sizeof(uintptr_t) * 8; static const uintptr_t kNBitsLog = kNBits == 32 ? 5 : 6; Mask() : m_(0) {} Mask(const Mask &m) : m_(m.m_) { } explicit Mask(uintptr_t m) : m_(m) { } INLINE bool Get(uintptr_t idx) const { return m_ & (kOne << idx); } INLINE void Set(uintptr_t idx) { m_ |= kOne << idx; } INLINE void Clear(uintptr_t idx) { m_ &= ~(kOne << idx); } INLINE bool Empty() const {return m_ == 0; } // Clear bits in range [a,b) and return old [a,b) range. INLINE Mask ClearRangeAndReturnOld(uintptr_t a, uintptr_t b) { DCHECK(a < b); DCHECK(b <= kNBits); uintptr_t res; uintptr_t n_bits_in_mask = (b - a); if (n_bits_in_mask == kNBits) { res = m_; m_ = 0; } else { uintptr_t t = (kOne << n_bits_in_mask); uintptr_t mask = (t - 1) << a; res = m_ & mask; m_ &= ~mask; } return Mask(res); } INLINE void ClearRange(uintptr_t a, uintptr_t b) { ClearRangeAndReturnOld(a, b); } INLINE void SetRange(uintptr_t a, uintptr_t b) { DCHECK(a < b); DCHECK(b <= kNBits); uintptr_t n_bits_in_mask = (b - a); if (n_bits_in_mask == kNBits) { m_ = ~0; } else { uintptr_t t = (kOne << n_bits_in_mask); uintptr_t mask = (t - 1) << a; m_ |= mask; } } INLINE uintptr_t GetRange(uintptr_t a, uintptr_t b) const { // a bug was fixed here DCHECK(a < b); DCHECK(b <= kNBits); uintptr_t n_bits_in_mask = (b - a); if (n_bits_in_mask == kNBits) { return m_; } else { uintptr_t t = (kOne << n_bits_in_mask); uintptr_t mask = (t - 1) << a; return m_ & mask; } } // Get index of some set bit (asumes mask is non zero). size_t GetSomeSetBit() { DCHECK(m_); size_t ret; #ifdef __GNUC__ ret = __builtin_ctzl(m_); #elif defined(_MSC_VER) unsigned long index; DCHECK(sizeof(uintptr_t) == 4); _BitScanReverse(&index, m_); ret = index; #else # error "Unsupported" #endif DCHECK(this->Get(ret)); return ret; } size_t PopCount() { #ifdef VGO_linux return __builtin_popcountl(m_); #else CHECK(0); return 0; #endif } void Subtract(Mask m) { m_ &= ~m.m_; } void Union(Mask m) { m_ |= m.m_; } static Mask Intersection(Mask m1, Mask m2) { return Mask(m1.m_ & m2.m_); } void Clear() { m_ = 0; } string ToString() const { char buff[kNBits+1]; for (uintptr_t i = 0; i < kNBits; i++) { buff[i] = Get(i) ? '1' : '0'; } buff[kNBits] = 0; return buff; } static void Test() { Mask m; m.Set(2); Printf("%s\n", m.ToString().c_str()); m.ClearRange(0, kNBits); Printf("%s\n", m.ToString().c_str()); } private: uintptr_t m_; }; // -------- BitSet -------------------{{{1 // Poor man's sparse bit set. class BitSet { public: // Add range [a,b). The range should be within one line (kNBitsLog). void Add(uintptr_t a, uintptr_t b) { uintptr_t line = a & ~(Mask::kNBits - 1); DCHECK(a < b); DCHECK(a - line < Mask::kNBits); if (!(b - line <= Mask::kNBits)) { Printf("XXXXX %p %p %p b-line=%ld size=%ld a-line=%ld\n", a, b, line, b - line, b - a, a - line); return; } DCHECK(b - line <= Mask::kNBits); DCHECK(line == ((b - 1) & ~(Mask::kNBits - 1))); Mask &mask= map_[line]; mask.SetRange(a - line, b - line); } bool empty() { return map_.empty(); } size_t size() { size_t res = 0; for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { res += it->second.PopCount(); } return res; } string ToString() { char buff[100]; string res; int lines = 0; snprintf(buff, sizeof(buff), " %ld lines %ld bits:", (long)map_.size(), (long)size()); res += buff; for (Map::iterator it = map_.begin(); it != map_.end(); ++it) { Mask mask = it->second; snprintf(buff, sizeof(buff), " l%d (%ld):", lines++, (long)mask.PopCount()); res += buff; uintptr_t line = it->first; bool is_in = false; for (size_t i = 0; i < Mask::kNBits; i++) { uintptr_t addr = line + i; if (mask.Get(i)) { if (!is_in) { snprintf(buff, sizeof(buff), " [%lx,", (long)addr); res += buff; is_in = true; } } else { if (is_in) { snprintf(buff, sizeof(buff), "%lx);", (long)addr); res += buff; is_in = false; } } } if (is_in) { snprintf(buff, sizeof(buff), "%lx);", (long)(line + Mask::kNBits)); res += buff; } } return res; } void Clear() { map_.clear(); } private: typedef map<uintptr_t, Mask> Map; Map map_; }; // -------- Segment -------------------{{{1 class Segment { public: // for debugging... static bool ProfileSeg(SID sid) { // return (sid.raw() % (1 << 14)) == 0; return false; } // non-static methods VTS *vts() const { return vts_; } TID tid() const { return TID(tid_); } LSID lsid(bool is_w) const { return lsid_[is_w]; } uint32_t lock_era() const { return lock_era_; } // static methods static INLINE uintptr_t *embedded_stack_trace(SID sid) { DCHECK(sid.valid()); DCHECK(kSizeOfHistoryStackTrace > 0); size_t chunk_idx = (unsigned)sid.raw() / kChunkSizeForStacks; size_t idx = (unsigned)sid.raw() % kChunkSizeForStacks; DCHECK(chunk_idx < n_stack_chunks_); DCHECK(all_stacks_[chunk_idx] != NULL); return &all_stacks_[chunk_idx][idx * kSizeOfHistoryStackTrace]; } static void ensure_space_for_stack_trace(SID sid) { ScopedMallocCostCenter malloc_cc(__FUNCTION__); DCHECK(sid.valid()); DCHECK(kSizeOfHistoryStackTrace > 0); size_t chunk_idx = (unsigned)sid.raw() / kChunkSizeForStacks; DCHECK(chunk_idx < n_stack_chunks_); if (all_stacks_[chunk_idx]) return; for (size_t i = 0; i <= chunk_idx; i++) { if (all_stacks_[i]) continue; all_stacks_[i] = new uintptr_t[ kChunkSizeForStacks * kSizeOfHistoryStackTrace]; // we don't clear this memory, it will be clreared later lazily. // We also never delete it because it will be used until the very end. } } static string StackTraceString(SID sid) { DCHECK(kSizeOfHistoryStackTrace > 0); return StackTrace::EmbeddedStackTraceToString( embedded_stack_trace(sid), kSizeOfHistoryStackTrace); } // Allocate `n` fresh segments, put SIDs into `fresh_sids`. static INLINE void AllocateFreshSegments(size_t n, SID *fresh_sids) { ScopedMallocCostCenter malloc_cc(__FUNCTION__); size_t i = 0; size_t n_reusable = min(n, reusable_sids_->size()); // First, allocate from reusable_sids_. for (; i < n_reusable; i++) { G_stats->seg_reuse++; DCHECK(!reusable_sids_->empty()); SID sid = reusable_sids_->back(); reusable_sids_->pop_back(); Segment *seg = GetInternal(sid); DCHECK(!seg->seg_ref_count_); DCHECK(!seg->vts()); DCHECK(!seg->tid().valid()); CHECK(sid.valid()); if (ProfileSeg(sid)) { Printf("Segment: reused SID %d\n", sid.raw()); } fresh_sids[i] = sid; } // allocate the rest from new sids. for (; i < n; i++) { G_stats->seg_create++; CHECK(n_segments_ < kMaxSID); Segment *seg = GetSegmentByIndex(n_segments_); // This VTS may not be empty due to ForgetAllState(). VTS::Unref(seg->vts_); seg->vts_ = 0; seg->seg_ref_count_ = 0; if (ProfileSeg(SID(n_segments_))) { Printf("Segment: allocated SID %d\n", n_segments_); } SID sid = fresh_sids[i] = SID(n_segments_); if (kSizeOfHistoryStackTrace > 0) { ensure_space_for_stack_trace(sid); } n_segments_++; } } // Initialize the contents of the given segment. static INLINE void SetupFreshSid(SID sid, TID tid, VTS *vts, LSID rd_lockset, LSID wr_lockset) { DCHECK(vts); DCHECK(tid.valid()); DCHECK(sid.valid()); Segment *seg = GetInternal(sid); DCHECK(seg); DCHECK(seg->seg_ref_count_ == 0); seg->seg_ref_count_ = 0; seg->tid_ = tid; seg->lsid_[0] = rd_lockset; seg->lsid_[1] = wr_lockset; seg->vts_ = vts; seg->lock_era_ = g_lock_era; if (kSizeOfHistoryStackTrace) { embedded_stack_trace(sid)[0] = 0; } } static INLINE SID AddNewSegment(TID tid, VTS *vts, LSID rd_lockset, LSID wr_lockset) { ScopedMallocCostCenter malloc_cc("Segment::AddNewSegment()"); SID sid; AllocateFreshSegments(1, &sid); SetupFreshSid(sid, tid, vts, rd_lockset, wr_lockset); return sid; } static bool Alive(SID sid) { Segment *seg = GetInternal(sid); return seg->vts() != NULL; } static void AssertLive(SID sid, int line) { if (DEBUG_MODE) { if (!(sid.raw() < INTERNAL_ANNOTATE_UNPROTECTED_READ(n_segments_))) { Printf("Segment::AssertLive: failed on sid=%d n_segments = %dline=%d\n", sid.raw(), n_segments_, line); } Segment *seg = GetInternal(sid); if (!seg->vts()) { Printf("Segment::AssertLive: failed on sid=%d line=%d\n", sid.raw(), line); } DCHECK(seg->vts()); DCHECK(seg->tid().valid()); } } static INLINE Segment *Get(SID sid) { AssertLive(sid, __LINE__); Segment *res = GetInternal(sid); DCHECK(res->vts()); DCHECK(res->tid().valid()); return res; } static INLINE void RecycleOneFreshSid(SID sid) { Segment *seg = GetInternal(sid); seg->tid_ = TID(); seg->vts_ = NULL; reusable_sids_->push_back(sid); if (ProfileSeg(sid)) { Printf("Segment: recycled SID %d\n", sid.raw()); } } static bool RecycleOneSid(SID sid) { ScopedMallocCostCenter malloc_cc("Segment::RecycleOneSid()"); Segment *seg = GetInternal(sid); DCHECK(seg->seg_ref_count_ == 0); DCHECK(sid.raw() < n_segments_); if (!seg->vts()) return false; // Already recycled. VTS::Unref(seg->vts_); RecycleOneFreshSid(sid); return true; } int32_t ref_count() const { return INTERNAL_ANNOTATE_UNPROTECTED_READ(seg_ref_count_); } static void INLINE Ref(SID sid, const char *where) { Segment *seg = GetInternal(sid); if (ProfileSeg(sid)) { Printf("SegRef : %d ref=%d %s; tid=%d\n", sid.raw(), seg->seg_ref_count_, where, seg->tid().raw()); } DCHECK(seg->seg_ref_count_ >= 0); AtomicIncrementRefcount(&seg->seg_ref_count_); } static INLINE intptr_t UnrefNoRecycle(SID sid, const char *where) { Segment *seg = GetInternal(sid); if (ProfileSeg(sid)) { Printf("SegUnref : %d ref=%d %s\n", sid.raw(), seg->seg_ref_count_, where); } DCHECK(seg->seg_ref_count_ > 0); return AtomicDecrementRefcount(&seg->seg_ref_count_); } static void INLINE Unref(SID sid, const char *where) { if (UnrefNoRecycle(sid, where) == 0) { RecycleOneSid(sid); } } static void ForgetAllState() { n_segments_ = 1; reusable_sids_->clear(); // vts_'es will be freed in AddNewSegment. } static string ToString(SID sid) { char buff[100]; snprintf(buff, sizeof(buff), "T%d/S%d", Get(sid)->tid().raw(), sid.raw()); return buff; } static string ToStringTidOnly(SID sid) { char buff[100]; snprintf(buff, sizeof(buff), "T%d", Get(sid)->tid().raw()); return buff; } static string ToStringWithLocks(SID sid) { char buff[100]; Segment *seg = Get(sid); snprintf(buff, sizeof(buff), "T%d/S%d ", seg->tid().raw(), sid.raw()); string res = buff; res += TwoLockSetsToString(seg->lsid(false), seg->lsid(true)); return res; } static bool INLINE HappensBeforeOrSameThread(SID a, SID b) { if (a == b) return true; if (Get(a)->tid() == Get(b)->tid()) return true; return HappensBefore(a, b); } static bool INLINE HappensBefore(SID a, SID b) { DCHECK(a != b); G_stats->n_seg_hb++; bool res = false; const Segment *seg_a = Get(a); const Segment *seg_b = Get(b); DCHECK(seg_a->tid() != seg_b->tid()); const VTS *vts_a = seg_a->vts(); const VTS *vts_b = seg_b->vts(); res = VTS::HappensBeforeCached(vts_a, vts_b); #if 0 if (DEBUG_MODE) { Printf("HB = %d\n %s\n %s\n", res, vts_a->ToString().c_str(), vts_b->ToString().c_str()); } #endif return res; } static int32_t NumberOfSegments() { return n_segments_; } static void ShowSegmentStats() { Printf("Segment::ShowSegmentStats:\n"); Printf("n_segments_: %d\n", n_segments_); Printf("reusable_sids_: %ld\n", reusable_sids_->size()); map<int, int> ref_to_freq_map; for (int i = 1; i < n_segments_; i++) { Segment *seg = GetInternal(SID(i)); int32_t refcount = seg->seg_ref_count_; if (refcount > 10) refcount = 10; ref_to_freq_map[refcount]++; } for (map<int, int>::iterator it = ref_to_freq_map.begin(); it != ref_to_freq_map.end(); ++it) { Printf("ref %d => freq %d\n", it->first, it->second); } } static void InitClassMembers() { if (G_flags->keep_history == 0) kSizeOfHistoryStackTrace = 0; Report("INFO: Allocating %ldMb (%ld * %ldM) for Segments.\n", (sizeof(Segment) * kMaxSID) >> 20, sizeof(Segment), kMaxSID >> 20); if (kSizeOfHistoryStackTrace) { Report("INFO: Will allocate up to %ldMb for 'previous' stack traces.\n", (kSizeOfHistoryStackTrace * sizeof(uintptr_t) * kMaxSID) >> 20); } all_segments_ = new Segment[kMaxSID]; // initialization all segments to 0. memset(all_segments_, 0, kMaxSID * sizeof(Segment)); // initialize all_segments_[0] with garbage memset(all_segments_, -1, sizeof(Segment)); if (kSizeOfHistoryStackTrace > 0) { n_stack_chunks_ = kMaxSID / kChunkSizeForStacks; if (n_stack_chunks_ * kChunkSizeForStacks < (size_t)kMaxSID) n_stack_chunks_++; all_stacks_ = new uintptr_t*[n_stack_chunks_]; memset(all_stacks_, 0, sizeof(uintptr_t*) * n_stack_chunks_); } n_segments_ = 1; reusable_sids_ = new vector<SID>; } private: static INLINE Segment *GetSegmentByIndex(int32_t index) { return &all_segments_[index]; } static INLINE Segment *GetInternal(SID sid) { DCHECK(sid.valid()); DCHECK(sid.raw() < INTERNAL_ANNOTATE_UNPROTECTED_READ(n_segments_)); Segment *res = GetSegmentByIndex(sid.raw()); return res; } // Data members. int32_t seg_ref_count_; LSID lsid_[2]; TID tid_; uint32_t lock_era_; VTS *vts_; // static class members. // One large array of segments. The size is set by a command line (--max-sid) // and never changes. Once we are out of vacant segments, we flush the state. static Segment *all_segments_; // We store stack traces separately because their size is unknown // at compile time and because they are needed less often. // The stacks are stored as an array of chunks, instead of one array, // so that for small tests we do not require too much RAM. // We don't use vector<> or another resizable array to avoid expensive // resizing. enum { kChunkSizeForStacks = DEBUG_MODE ? 512 : 1 * 1024 * 1024 }; static uintptr_t **all_stacks_; static size_t n_stack_chunks_; static int32_t n_segments_; static vector<SID> *reusable_sids_; }; Segment *Segment::all_segments_; uintptr_t **Segment::all_stacks_; size_t Segment::n_stack_chunks_; int32_t Segment::n_segments_; vector<SID> *Segment::reusable_sids_; // -------- SegmentSet -------------- {{{1 class SegmentSet { public: static NOINLINE SSID AddSegmentToSS(SSID old_ssid, SID new_sid); static NOINLINE SSID RemoveSegmentFromSS(SSID old_ssid, SID sid_to_remove); static INLINE SSID AddSegmentToTupleSS(SSID ssid, SID new_sid); static INLINE SSID RemoveSegmentFromTupleSS(SSID old_ssid, SID sid_to_remove); SSID ComputeSSID() { SSID res = map_->GetIdOrZero(this); CHECK_NE(res.raw(), 0); return res; } int ref_count() const { return ref_count_; } static void AssertLive(SSID ssid, int line) { DCHECK(ssid.valid()); if (DEBUG_MODE) { if (ssid.IsSingleton()) { Segment::AssertLive(ssid.GetSingleton(), line); } else { DCHECK(ssid.IsTuple()); int idx = -ssid.raw()-1; DCHECK(idx < static_cast<int>(vec_->size())); DCHECK(idx >= 0); SegmentSet *res = (*vec_)[idx]; DCHECK(res); DCHECK(res->ref_count_ >= 0); res->Validate(line); if (!res) { Printf("SegmentSet::AssertLive failed at line %d (ssid=%d)\n", line, ssid.raw()); DCHECK(0); } } } } static SegmentSet *Get(SSID ssid) { DCHECK(ssid.valid()); DCHECK(!ssid.IsSingleton()); int idx = -ssid.raw()-1; ANNOTATE_IGNORE_READS_BEGIN(); DCHECK(idx < static_cast<int>(vec_->size()) && idx >= 0); ANNOTATE_IGNORE_READS_END(); SegmentSet *res = (*vec_)[idx]; DCHECK(res); DCHECK(res->size() >= 2); return res; } void RecycleOneSegmentSet(SSID ssid) { DCHECK(ref_count_ == 0); DCHECK(ssid.valid()); DCHECK(!ssid.IsSingleton()); int idx = -ssid.raw()-1; DCHECK(idx < static_cast<int>(vec_->size()) && idx >= 0); CHECK((*vec_)[idx] == this); // Printf("SegmentSet::RecycleOneSegmentSet: %d\n", ssid.raw()); // // Recycle segments for (int i = 0; i < kMaxSegmentSetSize; i++) { SID sid = this->GetSID(i); if (sid.raw() == 0) break; Segment::Unref(sid, "SegmentSet::Recycle"); } ref_count_ = -1; map_->Erase(this); ready_to_be_reused_->push_back(ssid); G_stats->ss_recycle++; } static void INLINE Ref(SSID ssid, const char *where) { AssertTILHeld(); // The reference counting logic below is not thread-safe DCHECK(ssid.valid()); if (ssid.IsSingleton()) { Segment::Ref(ssid.GetSingleton(), where); } else { SegmentSet *sset = Get(ssid); // Printf("SSRef : %d ref=%d %s\n", ssid.raw(), sset->ref_count_, where); DCHECK(sset->ref_count_ >= 0); sset->ref_count_++; } } static void INLINE Unref(SSID ssid, const char *where) { AssertTILHeld(); // The reference counting logic below is not thread-safe DCHECK(ssid.valid()); if (ssid.IsSingleton()) { Segment::Unref(ssid.GetSingleton(), where); } else { SegmentSet *sset = Get(ssid); // Printf("SSUnref : %d ref=%d %s\n", ssid.raw(), sset->ref_count_, where); DCHECK(sset->ref_count_ > 0); sset->ref_count_--; if (sset->ref_count_ == 0) { // We don't delete unused SSID straightaway due to performance reasons // (to avoid flushing caches too often and because SSID may be reused // again soon) // // Instead, we use two queues (deques): // ready_to_be_recycled_ and ready_to_be_reused_. // The algorithm is following: // 1) When refcount_ becomes zero, we push the SSID into // ready_to_be_recycled_. // 2) When ready_to_be_recycled_ becomes too large, we call // FlushRecycleQueue(). // In FlushRecycleQueue(), we pop the first half of // ready_to_be_recycled_ and for each popped SSID we do // * if "refcount_ > 0", do nothing (this SSID is in use again) // * otherwise, we recycle this SSID (delete its VTS, etc) and push // it into ready_to_be_reused_ // 3) When a new SegmentSet is about to be created, we re-use SSID from // ready_to_be_reused_ (if available) ready_to_be_recycled_->push_back(ssid); if (UNLIKELY(ready_to_be_recycled_->size() > 2 * G_flags->segment_set_recycle_queue_size)) { FlushRecycleQueue(); } } } } static void FlushRecycleQueue() { while (ready_to_be_recycled_->size() > G_flags->segment_set_recycle_queue_size) { SSID rec_ssid = ready_to_be_recycled_->front(); ready_to_be_recycled_->pop_front(); int idx = -rec_ssid.raw()-1; SegmentSet *rec_ss = (*vec_)[idx]; DCHECK(rec_ss); DCHECK(rec_ss == Get(rec_ssid)); // We should check that this SSID haven't been referenced again. if (rec_ss->ref_count_ == 0) { rec_ss->RecycleOneSegmentSet(rec_ssid); } } // SSIDs will be reused soon - need to flush some caches. FlushCaches(); } string ToString() const; void Print() { Printf("SS%d:%s\n", -ComputeSSID().raw(), ToString().c_str()); } static string ToString(SSID ssid) { CHECK(ssid.IsValidOrEmpty()); if (ssid.IsSingleton()) { return "{" + Segment::ToStringTidOnly(SID(ssid.raw())) + "}"; } else if (ssid.IsEmpty()) { return "{}"; } else { AssertLive(ssid, __LINE__); return Get(ssid)->ToString(); } } static string ToStringWithLocks(SSID ssid); static void FlushCaches() { add_segment_cache_->Flush(); remove_segment_cache_->Flush(); } static void ForgetAllState() { for (size_t i = 0; i < vec_->size(); i++) { delete (*vec_)[i]; } map_->Clear(); vec_->clear(); ready_to_be_reused_->clear(); ready_to_be_recycled_->clear(); FlushCaches(); } static void Test(); static int32_t Size(SSID ssid) { if (ssid.IsEmpty()) return 0; if (ssid.IsSingleton()) return 1; return Get(ssid)->size(); } SID GetSID(int32_t i) const { DCHECK(i >= 0 && i < kMaxSegmentSetSize); DCHECK(i == 0 || sids_[i-1].raw() != 0); return sids_[i]; } void SetSID(int32_t i, SID sid) { DCHECK(i >= 0 && i < kMaxSegmentSetSize); DCHECK(i == 0 || sids_[i-1].raw() != 0); sids_[i] = sid; } static SID GetSID(SSID ssid, int32_t i, int line) { DCHECK(ssid.valid()); if (ssid.IsSingleton()) { DCHECK(i == 0); Segment::AssertLive(ssid.GetSingleton(), line); return ssid.GetSingleton(); } else { AssertLive(ssid, __LINE__); SID sid = Get(ssid)->GetSID(i); Segment::AssertLive(sid, line); return sid; } } static bool INLINE Contains(SSID ssid, SID seg) { if (LIKELY(ssid.IsSingleton())) { return ssid.GetSingleton() == seg; } else if (LIKELY(ssid.IsEmpty())) { return false; } SegmentSet *ss = Get(ssid); for (int i = 0; i < kMaxSegmentSetSize; i++) { SID sid = ss->GetSID(i); if (sid.raw() == 0) break; if (sid == seg) return true; } return false; } static Segment *GetSegmentForNonSingleton(SSID ssid, int32_t i, int line) { return Segment::Get(GetSID(ssid, i, line)); } void NOINLINE Validate(int line) const; static size_t NumberOfSegmentSets() { return vec_->size(); } static void InitClassMembers() { map_ = new Map; vec_ = new vector<SegmentSet *>; ready_to_be_recycled_ = new deque<SSID>; ready_to_be_reused_ = new deque<SSID>; add_segment_cache_ = new SsidSidToSidCache; remove_segment_cache_ = new SsidSidToSidCache; } private: SegmentSet() // Private CTOR : ref_count_(0) { // sids_ are filled with zeroes due to SID default CTOR. if (DEBUG_MODE) { for (int i = 0; i < kMaxSegmentSetSize; i++) CHECK_EQ(sids_[i].raw(), 0); } } int size() const { for (int i = 0; i < kMaxSegmentSetSize; i++) { if (sids_[i].raw() == 0) { CHECK_GE(i, 2); return i; } } return kMaxSegmentSetSize; } static INLINE SSID AllocateAndCopy(SegmentSet *ss) { DCHECK(ss->ref_count_ == 0); DCHECK(sizeof(int32_t) == sizeof(SID)); SSID res_ssid; SegmentSet *res_ss = 0; if (!ready_to_be_reused_->empty()) { res_ssid = ready_to_be_reused_->front(); ready_to_be_reused_->pop_front(); int idx = -res_ssid.raw()-1; res_ss = (*vec_)[idx]; DCHECK(res_ss); DCHECK(res_ss->ref_count_ == -1); G_stats->ss_reuse++; for (int i = 0; i < kMaxSegmentSetSize; i++) { res_ss->sids_[i] = SID(0); } } else { // create a new one ScopedMallocCostCenter cc("SegmentSet::CreateNewSegmentSet"); G_stats->ss_create++; res_ss = new SegmentSet; vec_->push_back(res_ss); res_ssid = SSID(-((int32_t)vec_->size())); CHECK(res_ssid.valid()); } DCHECK(res_ss); res_ss->ref_count_ = 0; for (int i = 0; i < kMaxSegmentSetSize; i++) { SID sid = ss->GetSID(i); if (sid.raw() == 0) break; Segment::Ref(sid, "SegmentSet::FindExistingOrAlocateAndCopy"); res_ss->SetSID(i, sid); } DCHECK(res_ss == Get(res_ssid)); map_->Insert(res_ss, res_ssid); return res_ssid; } static NOINLINE SSID FindExistingOrAlocateAndCopy(SegmentSet *ss) { if (DEBUG_MODE) { int size = ss->size(); if (size == 2) G_stats->ss_size_2++; if (size == 3) G_stats->ss_size_3++; if (size == 4) G_stats->ss_size_4++; if (size > 4) G_stats->ss_size_other++; } // First, check if there is such set already. SSID ssid = map_->GetIdOrZero(ss); if (ssid.raw() != 0) { // Found. AssertLive(ssid, __LINE__); G_stats->ss_find++; return ssid; } // If no such set, create one. return AllocateAndCopy(ss); } static INLINE SSID DoubletonSSID(SID sid1, SID sid2) { SegmentSet tmp; tmp.SetSID(0, sid1); tmp.SetSID(1, sid2); return FindExistingOrAlocateAndCopy(&tmp); } // testing only static SegmentSet *AddSegmentToTupleSS(SegmentSet *ss, SID new_sid) { SSID ssid = AddSegmentToTupleSS(ss->ComputeSSID(), new_sid); AssertLive(ssid, __LINE__); return Get(ssid); } static SegmentSet *Doubleton(SID sid1, SID sid2) { SSID ssid = DoubletonSSID(sid1, sid2); AssertLive(ssid, __LINE__); return Get(ssid); } // static data members struct Less { INLINE bool operator() (const SegmentSet *ss1, const SegmentSet *ss2) const { for (int i = 0; i < kMaxSegmentSetSize; i++) { SID sid1 = ss1->sids_[i], sid2 = ss2->sids_[i]; if (sid1 != sid2) return sid1 < sid2; } return false; } }; struct SSEq { INLINE bool operator() (const SegmentSet *ss1, const SegmentSet *ss2) const { G_stats->sseq_calls++; for (int i = 0; i < kMaxSegmentSetSize; i++) { SID sid1 = ss1->sids_[i], sid2 = ss2->sids_[i]; if (sid1 != sid2) return false; } return true; } }; struct SSHash { INLINE size_t operator() (const SegmentSet *ss) const { uintptr_t res = 0; uint32_t* sids_array = (uint32_t*)ss->sids_; // We must have even number of SIDs. DCHECK((kMaxSegmentSetSize % 2) == 0); G_stats->sshash_calls++; // xor all SIDs together, half of them bswap-ed. for (int i = 0; i < kMaxSegmentSetSize; i += 2) { uintptr_t t1 = sids_array[i]; uintptr_t t2 = sids_array[i+1]; if (t2) t2 = tsan_bswap(t2); res = res ^ t1 ^ t2; } return res; } }; struct SSTraits { enum { // These values are taken from the hash_compare defaults. bucket_size = 4, // Must be greater than zero. min_buckets = 8, // Must be power of 2. }; INLINE size_t operator()(const SegmentSet *ss) const { SSHash sshash; return sshash(ss); } INLINE bool operator()(const SegmentSet *ss1, const SegmentSet *ss2) const { Less less; return less(ss1, ss2); } }; template <class MapType> static SSID GetIdOrZeroFromMap(MapType *map, SegmentSet *ss) { typename MapType::iterator it = map->find(ss); if (it == map->end()) return SSID(0); return it->second; } class Map { public: SSID GetIdOrZero(SegmentSet *ss) { return GetIdOrZeroFromMap(&map_, ss); } void Insert(SegmentSet *ss, SSID id) { map_[ss] = id; } void Erase(SegmentSet *ss) { CHECK(map_.erase(ss)); } void Clear() { map_.clear(); } private: // TODO(timurrrr): consider making a custom hash_table. #if defined(_MSC_VER) typedef stdext::hash_map<SegmentSet*, SSID, SSTraits > MapType__; #elif 1 typedef unordered_map<SegmentSet*, SSID, SSHash, SSEq > MapType__; #else // Old code, may be useful for debugging. typedef map<SegmentSet*, SSID, Less > MapType__; #endif MapType__ map_; }; // typedef map<SegmentSet*, SSID, Less> Map; static Map *map_; // TODO(kcc): use vector<SegmentSet> instead. static vector<SegmentSet *> *vec_; static deque<SSID> *ready_to_be_reused_; static deque<SSID> *ready_to_be_recycled_; typedef PairCache<SSID, SID, SSID, 1009, 1> SsidSidToSidCache; static SsidSidToSidCache *add_segment_cache_; static SsidSidToSidCache *remove_segment_cache_; // sids_ contains up to kMaxSegmentSetSize SIDs. // Contains zeros at the end if size < kMaxSegmentSetSize. SID sids_[kMaxSegmentSetSize]; int32_t ref_count_; }; SegmentSet::Map *SegmentSet::map_; vector<SegmentSet *> *SegmentSet::vec_; deque<SSID> *SegmentSet::ready_to_be_reused_; deque<SSID> *SegmentSet::ready_to_be_recycled_; SegmentSet::SsidSidToSidCache *SegmentSet::add_segment_cache_; SegmentSet::SsidSidToSidCache *SegmentSet::remove_segment_cache_; SSID SegmentSet::RemoveSegmentFromSS(SSID old_ssid, SID sid_to_remove) { DCHECK(old_ssid.IsValidOrEmpty()); DCHECK(sid_to_remove.valid()); SSID res; if (remove_segment_cache_->Lookup(old_ssid, sid_to_remove, &res)) { return res; } if (old_ssid.IsEmpty()) { res = old_ssid; // Nothing to remove. } else if (LIKELY(old_ssid.IsSingleton())) { SID sid = old_ssid.GetSingleton(); if (Segment::HappensBeforeOrSameThread(sid, sid_to_remove)) res = SSID(0); // Empty. else res = old_ssid; } else { res = RemoveSegmentFromTupleSS(old_ssid, sid_to_remove); } remove_segment_cache_->Insert(old_ssid, sid_to_remove, res); return res; } // static // // This method returns a SSID of a SegmentSet containing "new_sid" and all those // segments from "old_ssid" which do not happen-before "new_sid". // // For details, see // http://code.google.com/p/data-race-test/wiki/ThreadSanitizerAlgorithm#State_machine SSID SegmentSet::AddSegmentToSS(SSID old_ssid, SID new_sid) { DCHECK(old_ssid.raw() == 0 || old_ssid.valid()); DCHECK(new_sid.valid()); Segment::AssertLive(new_sid, __LINE__); SSID res; // These two TIDs will only be used if old_ssid.IsSingleton() == true. TID old_tid; TID new_tid; if (LIKELY(old_ssid.IsSingleton())) { SID old_sid(old_ssid.raw()); DCHECK(old_sid.valid()); Segment::AssertLive(old_sid, __LINE__); if (UNLIKELY(old_sid == new_sid)) { // The new segment equals the old one - nothing has changed. return old_ssid; } old_tid = Segment::Get(old_sid)->tid(); new_tid = Segment::Get(new_sid)->tid(); if (LIKELY(old_tid == new_tid)) { // The new segment is in the same thread - just replace the SID. return SSID(new_sid); } if (Segment::HappensBefore(old_sid, new_sid)) { // The new segment is in another thread, but old segment // happens before the new one - just replace the SID. return SSID(new_sid); } DCHECK(!Segment::HappensBefore(new_sid, old_sid)); // The only other case is Signleton->Doubleton transition, see below. } else if (LIKELY(old_ssid.IsEmpty())) { return SSID(new_sid); } // Lookup the cache. if (add_segment_cache_->Lookup(old_ssid, new_sid, &res)) { SegmentSet::AssertLive(res, __LINE__); return res; } if (LIKELY(old_ssid.IsSingleton())) { // Signleton->Doubleton transition. // These two TIDs were initialized before cache lookup (see above). DCHECK(old_tid.valid()); DCHECK(new_tid.valid()); SID old_sid(old_ssid.raw()); DCHECK(old_sid.valid()); DCHECK(!Segment::HappensBefore(new_sid, old_sid)); DCHECK(!Segment::HappensBefore(old_sid, new_sid)); res = (old_tid < new_tid ? DoubletonSSID(old_sid, new_sid) : DoubletonSSID(new_sid, old_sid)); SegmentSet::AssertLive(res, __LINE__); } else { res = AddSegmentToTupleSS(old_ssid, new_sid); SegmentSet::AssertLive(res, __LINE__); } // Put the result into cache. add_segment_cache_->Insert(old_ssid, new_sid, res); return res; } SSID SegmentSet::RemoveSegmentFromTupleSS(SSID ssid, SID sid_to_remove) { DCHECK(ssid.IsTuple()); DCHECK(ssid.valid()); AssertLive(ssid, __LINE__); SegmentSet *ss = Get(ssid); int32_t old_size = 0, new_size = 0; SegmentSet tmp; SID * tmp_sids = tmp.sids_; CHECK(sizeof(int32_t) == sizeof(SID)); for (int i = 0; i < kMaxSegmentSetSize; i++, old_size++) { SID sid = ss->GetSID(i); if (sid.raw() == 0) break; DCHECK(sid.valid()); Segment::AssertLive(sid, __LINE__); if (Segment::HappensBeforeOrSameThread(sid, sid_to_remove)) continue; // Skip this segment from the result. tmp_sids[new_size++] = sid; } if (new_size == old_size) return ssid; if (new_size == 0) return SSID(0); if (new_size == 1) return SSID(tmp_sids[0]); if (DEBUG_MODE) tmp.Validate(__LINE__); SSID res = FindExistingOrAlocateAndCopy(&tmp); if (DEBUG_MODE) Get(res)->Validate(__LINE__); return res; } // static SSID SegmentSet::AddSegmentToTupleSS(SSID ssid, SID new_sid) { DCHECK(ssid.IsTuple()); DCHECK(ssid.valid()); AssertLive(ssid, __LINE__); SegmentSet *ss = Get(ssid); Segment::AssertLive(new_sid, __LINE__); const Segment *new_seg = Segment::Get(new_sid); TID new_tid = new_seg->tid(); int32_t old_size = 0, new_size = 0; SID tmp_sids[kMaxSegmentSetSize + 1]; CHECK(sizeof(int32_t) == sizeof(SID)); bool inserted_new_sid = false; // traverse all SID in current ss. tids are ordered. for (int i = 0; i < kMaxSegmentSetSize; i++, old_size++) { SID sid = ss->GetSID(i); if (sid.raw() == 0) break; DCHECK(sid.valid()); Segment::AssertLive(sid, __LINE__); const Segment *seg = Segment::Get(sid); TID tid = seg->tid(); if (sid == new_sid) { // we are trying to insert a sid which is already there. // SS will not change. return ssid; } if (tid == new_tid) { if (seg->vts() == new_seg->vts() && seg->lsid(true) == new_seg->lsid(true) && seg->lsid(false) == new_seg->lsid(false)) { // Optimization: if a segment with the same VTS and LS // as in the current is already inside SS, don't modify the SS. // Improves performance with --keep-history >= 1. return ssid; } // we have another segment from the same thread => replace it. tmp_sids[new_size++] = new_sid; inserted_new_sid = true; continue; } if (tid > new_tid && !inserted_new_sid) { // there was no segment with this tid, put it now. tmp_sids[new_size++] = new_sid; inserted_new_sid = true; } if (!Segment::HappensBefore(sid, new_sid)) { DCHECK(!Segment::HappensBefore(new_sid, sid)); tmp_sids[new_size++] = sid; } } if (!inserted_new_sid) { tmp_sids[new_size++] = new_sid; } CHECK_GT(new_size, 0); if (new_size == 1) { return SSID(new_sid.raw()); // Singleton. } if (new_size > kMaxSegmentSetSize) { CHECK(new_size == kMaxSegmentSetSize + 1); // we need to forget one segment. Which? The oldest one. int seg_to_forget = 0; Segment *oldest_segment = NULL; for (int i = 0; i < new_size; i++) { SID sid = tmp_sids[i]; if (sid == new_sid) continue; Segment *s = Segment::Get(tmp_sids[i]); if (oldest_segment == NULL || oldest_segment->vts()->uniq_id() > s->vts()->uniq_id()) { oldest_segment = s; seg_to_forget = i; } } DCHECK(oldest_segment); // Printf("seg_to_forget: %d T%d\n", tmp_sids[seg_to_forget].raw(), // oldest_segment->tid().raw()); for (int i = seg_to_forget; i < new_size - 1; i++) { tmp_sids[i] = tmp_sids[i+1]; } new_size--; } CHECK(new_size <= kMaxSegmentSetSize); SegmentSet tmp; for (int i = 0; i < new_size; i++) tmp.sids_[i] = tmp_sids[i]; // TODO(timurrrr): avoid copying? if (DEBUG_MODE) tmp.Validate(__LINE__); SSID res = FindExistingOrAlocateAndCopy(&tmp); if (DEBUG_MODE) Get(res)->Validate(__LINE__); return res; } void NOINLINE SegmentSet::Validate(int line) const { // This is expensive! int my_size = size(); for (int i = 0; i < my_size; i++) { SID sid1 = GetSID(i); CHECK(sid1.valid()); Segment::AssertLive(sid1, __LINE__); for (int j = i + 1; j < my_size; j++) { SID sid2 = GetSID(j); CHECK(sid2.valid()); Segment::AssertLive(sid2, __LINE__); bool hb1 = Segment::HappensBefore(sid1, sid2); bool hb2 = Segment::HappensBefore(sid2, sid1); if (hb1 || hb2) { Printf("BAD at line %d: %d %d %s %s\n %s\n %s\n", line, static_cast<int>(hb1), static_cast<int>(hb2), Segment::ToString(sid1).c_str(), Segment::ToString(sid2).c_str(), Segment::Get(sid1)->vts()->ToString().c_str(), Segment::Get(sid2)->vts()->ToString().c_str()); } CHECK(!Segment::HappensBefore(GetSID(i), GetSID(j))); CHECK(!Segment::HappensBefore(GetSID(j), GetSID(i))); CHECK(Segment::Get(sid1)->tid() < Segment::Get(sid2)->tid()); } } for (int i = my_size; i < kMaxSegmentSetSize; i++) { CHECK_EQ(sids_[i].raw(), 0); } } string SegmentSet::ToStringWithLocks(SSID ssid) { if (ssid.IsEmpty()) return ""; string res = ""; for (int i = 0; i < Size(ssid); i++) { SID sid = GetSID(ssid, i, __LINE__); if (i) res += ", "; res += Segment::ToStringWithLocks(sid); } return res; } string SegmentSet::ToString() const { Validate(__LINE__); string res = "{"; for (int i = 0; i < size(); i++) { SID sid = GetSID(i); if (i) res += ", "; CHECK(sid.valid()); Segment::AssertLive(sid, __LINE__); res += Segment::ToStringTidOnly(sid).c_str(); } res += "}"; return res; } // static void SegmentSet::Test() { LSID ls(0); // dummy SID sid1 = Segment::AddNewSegment(TID(0), VTS::Parse("[0:2;]"), ls, ls); SID sid2 = Segment::AddNewSegment(TID(1), VTS::Parse("[0:1; 1:1]"), ls, ls); SID sid3 = Segment::AddNewSegment(TID(2), VTS::Parse("[0:1; 2:1]"), ls, ls); SID sid4 = Segment::AddNewSegment(TID(3), VTS::Parse("[0:1; 3:1]"), ls, ls); SID sid5 = Segment::AddNewSegment(TID(4), VTS::Parse("[0:3; 2:2; 3:2;]"), ls, ls); SID sid6 = Segment::AddNewSegment(TID(4), VTS::Parse("[0:3; 1:2; 2:2; 3:2;]"), ls, ls); // SS1:{T0/S1, T2/S3} SegmentSet *d1 = SegmentSet::Doubleton(sid1, sid3); d1->Print(); CHECK(SegmentSet::Doubleton(sid1, sid3) == d1); // SS2:{T0/S1, T1/S2, T2/S3} SegmentSet *d2 = SegmentSet::AddSegmentToTupleSS(d1, sid2); CHECK(SegmentSet::AddSegmentToTupleSS(d1, sid2) == d2); d2->Print(); // SS3:{T0/S1, T2/S3, T3/S4} SegmentSet *d3 = SegmentSet::AddSegmentToTupleSS(d1, sid4); CHECK(SegmentSet::AddSegmentToTupleSS(d1, sid4) == d3); d3->Print(); // SS4:{T0/S1, T1/S2, T2/S3, T3/S4} SegmentSet *d4 = SegmentSet::AddSegmentToTupleSS(d2, sid4); CHECK(SegmentSet::AddSegmentToTupleSS(d2, sid4) == d4); CHECK(SegmentSet::AddSegmentToTupleSS(d3, sid2) == d4); d4->Print(); // SS5:{T1/S2, T4/S5} SegmentSet *d5 = SegmentSet::AddSegmentToTupleSS(d4, sid5); d5->Print(); SSID ssid6 = SegmentSet::AddSegmentToTupleSS(d4->ComputeSSID(), sid6); CHECK(ssid6.IsSingleton()); Printf("%s\n", ToString(ssid6).c_str()); CHECK_EQ(sid6.raw(), 6); CHECK_EQ(ssid6.raw(), 6); } // -------- Shadow Value ------------ {{{1 class ShadowValue { public: ShadowValue() { if (DEBUG_MODE) { rd_ssid_ = 0xDEADBEEF; wr_ssid_ = 0xDEADBEEF; } } void Clear() { rd_ssid_ = 0; wr_ssid_ = 0; } INLINE bool IsNew() const { return rd_ssid_ == 0 && wr_ssid_ == 0; } // new experimental state machine. SSID rd_ssid() const { return SSID(rd_ssid_); } SSID wr_ssid() const { return SSID(wr_ssid_); } INLINE void set(SSID rd_ssid, SSID wr_ssid) { rd_ssid_ = rd_ssid.raw(); wr_ssid_ = wr_ssid.raw(); } // comparison INLINE bool operator == (const ShadowValue &sval) const { return rd_ssid_ == sval.rd_ssid_ && wr_ssid_ == sval.wr_ssid_; } bool operator != (const ShadowValue &sval) const { return !(*this == sval); } bool operator < (const ShadowValue &sval) const { if (rd_ssid_ < sval.rd_ssid_) return true; if (rd_ssid_ == sval.rd_ssid_ && wr_ssid_ < sval.wr_ssid_) return true; return false; } void Ref(const char *where) { if (!rd_ssid().IsEmpty()) { DCHECK(rd_ssid().valid()); SegmentSet::Ref(rd_ssid(), where); } if (!wr_ssid().IsEmpty()) { DCHECK(wr_ssid().valid()); SegmentSet::Ref(wr_ssid(), where); } } void Unref(const char *where) { if (!rd_ssid().IsEmpty()) { DCHECK(rd_ssid().valid()); SegmentSet::Unref(rd_ssid(), where); } if (!wr_ssid().IsEmpty()) { DCHECK(wr_ssid().valid()); SegmentSet::Unref(wr_ssid(), where); } } string ToString() const { char buff[1000]; if (IsNew()) { return "{New}"; } snprintf(buff, sizeof(buff), "R: %s; W: %s", SegmentSet::ToStringWithLocks(rd_ssid()).c_str(), SegmentSet::ToStringWithLocks(wr_ssid()).c_str()); return buff; } private: int32_t rd_ssid_; int32_t wr_ssid_; }; // -------- CacheLine --------------- {{{1 // The CacheLine is a set of Mask::kNBits (32 or 64) Shadow Values. // The shadow values in a cache line are grouped in subsets of 8 values. // If a particular address of memory is always accessed by aligned 8-byte // read/write instructions, only the shadow value correspoding to the // first byte is set, the rest shadow values are not used. // Ditto to aligned 4- and 2-byte accesses. // If a memory was accessed as 8 bytes and then it was accesed as 4 bytes, // (e.g. someone used a C union) we need to split the shadow value into two. // If the memory was accessed as 4 bytes and is now accessed as 8 bytes, // we need to try joining the shadow values. // // Hence the concept of granularity_mask (which is a string of 16 bits). // 0000000000000000 -- no accesses were observed to these 8 bytes. // 0000000000000001 -- all accesses were 8 bytes (aligned). // 0000000000000110 -- all accesses were 4 bytes (aligned). // 0000000001111000 -- all accesses were 2 bytes (aligned). // 0111111110000000 -- all accesses were 1 byte. // 0110000000100010 -- First 4 bytes were accessed by 4 byte insns, // next 2 bytes by 2 byte insns, last 2 bytes by 1 byte insns. INLINE bool GranularityIs8(uintptr_t off, uint16_t gr) { return gr & 1; } INLINE bool GranularityIs4(uintptr_t off, uint16_t gr) { uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1. return ((gr >> (1 + off_within_8_bytes)) & 1); } INLINE bool GranularityIs2(uintptr_t off, uint16_t gr) { uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3 return ((gr >> (3 + off_within_8_bytes)) & 1); } INLINE bool GranularityIs1(uintptr_t off, uint16_t gr) { uintptr_t off_within_8_bytes = (off) & 7; // 0, ..., 7 return ((gr >> (7 + off_within_8_bytes)) & 1); } class CacheLine { public: static const uintptr_t kLineSizeBits = Mask::kNBitsLog; // Don't change this. static const uintptr_t kLineSize = Mask::kNBits; static CacheLine *CreateNewCacheLine(uintptr_t tag) { ScopedMallocCostCenter cc("CreateNewCacheLine"); void *mem = free_list_->Allocate(); DCHECK(mem); return new (mem) CacheLine(tag); } static void Delete(CacheLine *line) { free_list_->Deallocate(line); } const Mask &has_shadow_value() const { return has_shadow_value_; } Mask &traced() { return traced_; } Mask &published() { return published_; } Mask &racey() { return racey_; } uintptr_t tag() { return tag_; } void DebugTrace(uintptr_t off, const char *where_str, int where_int) { (void)off; (void)where_str; (void)where_int; #if 0 if (DEBUG_MODE && tag() == G_flags->trace_addr) { uintptr_t off8 = off & ~7; Printf("CacheLine %p, off=%ld off8=%ld gr=%d " "has_sval: %d%d%d%d%d%d%d%d (%s:%d)\n", tag(), off, off8, granularity_[off/8], has_shadow_value_.Get(off8 + 0), has_shadow_value_.Get(off8 + 1), has_shadow_value_.Get(off8 + 2), has_shadow_value_.Get(off8 + 3), has_shadow_value_.Get(off8 + 4), has_shadow_value_.Get(off8 + 5), has_shadow_value_.Get(off8 + 6), has_shadow_value_.Get(off8 + 7), where_str, where_int ); } #endif } // Add a new shadow value to a place where there was no shadow value before. ShadowValue *AddNewSvalAtOffset(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); CHECK(!has_shadow_value().Get(off)); has_shadow_value_.Set(off); published_.Clear(off); ShadowValue *res = GetValuePointer(off); res->Clear(); DebugTrace(off, __FUNCTION__, __LINE__); return res; } // Return true if this line has no useful information in it. bool Empty() { // The line has shadow values. if (!has_shadow_value().Empty()) return false; // If the line is traced, racey or published, we want to keep it. if (!traced().Empty()) return false; if (!racey().Empty()) return false; if (!published().Empty()) return false; return true; } INLINE Mask ClearRangeAndReturnOldUsed(uintptr_t from, uintptr_t to) { traced_.ClearRange(from, to); published_.ClearRange(from, to); racey_.ClearRange(from, to); for (uintptr_t x = (from + 7) / 8; x < to / 8; x++) { granularity_[x] = 0; } return has_shadow_value_.ClearRangeAndReturnOld(from, to); } void Clear() { has_shadow_value_.Clear(); traced_.Clear(); published_.Clear(); racey_.Clear(); for (size_t i = 0; i < TS_ARRAY_SIZE(granularity_); i++) granularity_[i] = 0; } ShadowValue *GetValuePointer(uintptr_t offset) { DCHECK(offset < kLineSize); return &vals_[offset]; } ShadowValue GetValue(uintptr_t offset) { return *GetValuePointer(offset); } static uintptr_t ComputeOffset(uintptr_t a) { return a & (kLineSize - 1); } static uintptr_t ComputeTag(uintptr_t a) { return a & ~(kLineSize - 1); } static uintptr_t ComputeNextTag(uintptr_t a) { return ComputeTag(a) + kLineSize; } uint16_t *granularity_mask(uintptr_t off) { DCHECK(off < kLineSize); return &granularity_[off / 8]; } void Split_8_to_4(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); uint16_t gr = *granularity_mask(off); if (GranularityIs8(off, gr)) { DCHECK(!GranularityIs4(off, gr)); DCHECK(!GranularityIs2(off, gr)); DCHECK(!GranularityIs1(off, gr)); uintptr_t off_8_aligned = off & ~7; if (has_shadow_value_.Get(off_8_aligned)) { ShadowValue sval = GetValue(off_8_aligned); sval.Ref("Split_8_to_4"); DCHECK(!has_shadow_value_.Get(off_8_aligned + 4)); *AddNewSvalAtOffset(off_8_aligned + 4) = sval; } *granularity_mask(off) = gr = 3 << 1; DCHECK(GranularityIs4(off, gr)); DebugTrace(off, __FUNCTION__, __LINE__); } } void Split_4_to_2(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); uint16_t gr = *granularity_mask(off); if (GranularityIs4(off, gr)) { DCHECK(!GranularityIs8(off, gr)); DCHECK(!GranularityIs2(off, gr)); DCHECK(!GranularityIs1(off, gr)); uint16_t off_4_aligned = off & ~3; if (has_shadow_value_.Get(off_4_aligned)) { ShadowValue sval = GetValue(off_4_aligned); sval.Ref("Split_4_to_2"); DCHECK(!has_shadow_value_.Get(off_4_aligned + 2)); *AddNewSvalAtOffset(off_4_aligned + 2) = sval; } // Clear this 4-granularity bit. uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1. gr &= ~(1 << (1 + off_within_8_bytes)); // Set two 2-granularity bits. gr |= 3 << (3 + 2 * off_within_8_bytes); *granularity_mask(off) = gr; DebugTrace(off, __FUNCTION__, __LINE__); } } void Split_2_to_1(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); uint16_t gr = *granularity_mask(off); if (GranularityIs2(off, gr)) { DCHECK(!GranularityIs8(off, gr)); DCHECK(!GranularityIs4(off, gr)); DCHECK(!GranularityIs1(off, gr)); uint16_t off_2_aligned = off & ~1; if (has_shadow_value_.Get(off_2_aligned)) { ShadowValue sval = GetValue(off_2_aligned); sval.Ref("Split_2_to_1"); DCHECK(!has_shadow_value_.Get(off_2_aligned + 1)); *AddNewSvalAtOffset(off_2_aligned + 1) = sval; } // Clear this 2-granularity bit. uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3 gr &= ~(1 << (3 + off_within_8_bytes)); // Set two 1-granularity bits. gr |= 3 << (7 + 2 * off_within_8_bytes); *granularity_mask(off) = gr; DebugTrace(off, __FUNCTION__, __LINE__); } } void Join_1_to_2(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); DCHECK((off & 1) == 0); uint16_t gr = *granularity_mask(off); if (GranularityIs1(off, gr)) { DCHECK(GranularityIs1(off + 1, gr)); if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 1)) { if (GetValue(off) == GetValue(off + 1)) { ShadowValue *sval_p = GetValuePointer(off + 1); sval_p->Unref("Join_1_to_2"); sval_p->Clear(); has_shadow_value_.Clear(off + 1); uintptr_t off_within_8_bytes = (off >> 1) & 3; // 0, 1, 2, or 3 // Clear two 1-granularity bits. gr &= ~(3 << (7 + 2 * off_within_8_bytes)); // Set one 2-granularity bit. gr |= 1 << (3 + off_within_8_bytes); *granularity_mask(off) = gr; DebugTrace(off, __FUNCTION__, __LINE__); } } } } void Join_2_to_4(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); DCHECK((off & 3) == 0); uint16_t gr = *granularity_mask(off); if (GranularityIs2(off, gr) && GranularityIs2(off + 2, gr)) { if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 2)) { if (GetValue(off) == GetValue(off + 2)) { ShadowValue *sval_p = GetValuePointer(off + 2); sval_p->Unref("Join_2_to_4"); sval_p->Clear(); has_shadow_value_.Clear(off + 2); uintptr_t off_within_8_bytes = (off >> 2) & 1; // 0 or 1. // Clear two 2-granularity bits. gr &= ~(3 << (3 + 2 * off_within_8_bytes)); // Set one 4-granularity bit. gr |= 1 << (1 + off_within_8_bytes); *granularity_mask(off) = gr; DebugTrace(off, __FUNCTION__, __LINE__); } } } } void Join_4_to_8(uintptr_t off) { DebugTrace(off, __FUNCTION__, __LINE__); DCHECK((off & 7) == 0); uint16_t gr = *granularity_mask(off); if (GranularityIs4(off, gr) && GranularityIs4(off + 4, gr)) { if (has_shadow_value_.Get(off) && has_shadow_value_.Get(off + 4)) { if (GetValue(off) == GetValue(off + 4)) { ShadowValue *sval_p = GetValuePointer(off + 4); sval_p->Unref("Join_4_to_8"); sval_p->Clear(); has_shadow_value_.Clear(off + 4); *granularity_mask(off) = 1; DebugTrace(off, __FUNCTION__, __LINE__); } } } } static void InitClassMembers() { if (DEBUG_MODE) { Printf("sizeof(CacheLine) = %ld\n", sizeof(CacheLine)); } free_list_ = new FreeList(sizeof(CacheLine), 1024); } private: explicit CacheLine(uintptr_t tag) { tag_ = tag; Clear(); } ~CacheLine() { } uintptr_t tag_; // data members Mask has_shadow_value_; Mask traced_; Mask racey_; Mask published_; uint16_t granularity_[kLineSize / 8]; ShadowValue vals_[kLineSize]; // static data members. static FreeList *free_list_; }; FreeList *CacheLine::free_list_; // If range [a,b) fits into one line, return that line's tag. // Else range [a,b) is broken into these ranges: // [a, line1_tag) // [line1_tag, line2_tag) // [line2_tag, b) // and 0 is returned. uintptr_t GetCacheLinesForRange(uintptr_t a, uintptr_t b, uintptr_t *line1_tag, uintptr_t *line2_tag) { uintptr_t a_tag = CacheLine::ComputeTag(a); uintptr_t next_tag = CacheLine::ComputeNextTag(a); if (b < next_tag) { return a_tag; } *line1_tag = next_tag; *line2_tag = CacheLine::ComputeTag(b); return 0; } // -------- Cache ------------------ {{{1 class Cache { public: Cache() { memset(lines_, 0, sizeof(lines_)); ANNOTATE_BENIGN_RACE_SIZED(lines_, sizeof(lines_), "Cache::lines_ accessed without a lock"); } INLINE static CacheLine *kLineIsLocked() { return (CacheLine*)1; } INLINE static bool LineIsNullOrLocked(CacheLine *line) { return (uintptr_t)line <= 1; } INLINE CacheLine *TidMagic(int32_t tid) { return kLineIsLocked(); } // Try to get a CacheLine for exclusive use. // May return NULL or kLineIsLocked. INLINE CacheLine *TryAcquireLine(TSanThread *thr, uintptr_t a, int call_site) { uintptr_t cli = ComputeCacheLineIndexInCache(a); CacheLine **addr = &lines_[cli]; CacheLine *res = (CacheLine*)AtomicExchange( (uintptr_t*)addr, (uintptr_t)kLineIsLocked()); if (DEBUG_MODE && debug_cache) { uintptr_t tag = CacheLine::ComputeTag(a); if (res && res != kLineIsLocked()) Printf("TryAcquire %p empty=%d tag=%lx cli=%lx site=%d\n", res, res->Empty(), res->tag(), cli, call_site); else Printf("TryAcquire tag=%lx cli=%d site=%d\n", tag, cli, call_site); } if (res) { ANNOTATE_HAPPENS_AFTER((void*)cli); } return res; } INLINE CacheLine *AcquireLine(TSanThread *thr, uintptr_t a, int call_site) { CacheLine *line = NULL; int iter = 0; const int max_iter = 1 << 30; for (;;) { line = TryAcquireLine(thr, a, call_site); if (line != kLineIsLocked()) break; iter++; if ((iter % (1 << 6)) == 0) { YIELD(); G_stats->try_acquire_line_spin++; if (DEBUG_MODE && debug_cache && ((iter & (iter - 1)) == 0)) { Printf("T%d %s a=%p iter=%d\n", raw_tid(thr), __FUNCTION__, a, iter); } } else { for (int active_spin = 0; active_spin != 10; active_spin += 1) { PROCESSOR_YIELD(); } } if (DEBUG_MODE && debug_cache && iter == max_iter) { Printf("Failed to acquire a cache line: T%d a=%p site=%d\n", raw_tid(thr), a, call_site); CHECK(iter < max_iter); } } DCHECK(lines_[ComputeCacheLineIndexInCache(a)] == TidMagic(raw_tid(thr))); return line; } // Release a CacheLine from exclusive use. INLINE void ReleaseLine(TSanThread *thr, uintptr_t a, CacheLine *line, int call_site) { if (TS_SERIALIZED) return; DCHECK(line != kLineIsLocked()); uintptr_t cli = ComputeCacheLineIndexInCache(a); DCHECK(line == NULL || cli == ComputeCacheLineIndexInCache(line->tag())); CacheLine **addr = &lines_[cli]; DCHECK(*addr == TidMagic(raw_tid(thr))); ReleaseStore((uintptr_t*)addr, (uintptr_t)line); ANNOTATE_HAPPENS_BEFORE((void*)cli); if (DEBUG_MODE && debug_cache) { uintptr_t tag = CacheLine::ComputeTag(a); if (line) Printf("Release %p empty=%d tag=%lx cli=%lx site=%d\n", line, line->Empty(), line->tag(), cli, call_site); else Printf("Release tag=%lx cli=%d site=%d\n", tag, cli, call_site); } } void AcquireAllLines(TSanThread *thr) { CHECK(TS_SERIALIZED == 0); for (size_t i = 0; i < (size_t)kNumLines; i++) { uintptr_t tag = i << CacheLine::kLineSizeBits; AcquireLine(thr, tag, __LINE__); CHECK(lines_[i] == kLineIsLocked()); } } // Get a CacheLine. This operation should be performed under a lock // (whatever that is), but other threads may be acquiring the same line // concurrently w/o a lock. // Every call to GetLine() which returns non-null line // should be followed by a call to ReleaseLine(). INLINE CacheLine *GetLine(TSanThread *thr, uintptr_t a, bool create_new_if_need, int call_site) { uintptr_t tag = CacheLine::ComputeTag(a); DCHECK(tag <= a); DCHECK(tag + CacheLine::kLineSize > a); uintptr_t cli = ComputeCacheLineIndexInCache(a); CacheLine *res = NULL; CacheLine *line = NULL; if (create_new_if_need == false && lines_[cli] == 0) { // There is no such line in the cache, nor should it be in the storage. // Check that the storage indeed does not have this line. // Such DCHECK is racey if tsan is multi-threaded. DCHECK(TS_SERIALIZED == 0 || storage_.count(tag) == 0); return NULL; } if (TS_SERIALIZED) { line = lines_[cli]; } else { line = AcquireLine(thr, tag, call_site); } if (LIKELY(line && line->tag() == tag)) { res = line; } else { res = WriteBackAndFetch(thr, line, tag, cli, create_new_if_need); if (!res) { ReleaseLine(thr, a, line, call_site); } } if (DEBUG_MODE && debug_cache) { if (res) Printf("GetLine %p empty=%d tag=%lx\n", res, res->Empty(), res->tag()); else Printf("GetLine res=NULL, line=%p tag=%lx cli=%lx\n", line, tag, cli); } return res; } INLINE CacheLine *GetLineOrCreateNew(TSanThread *thr, uintptr_t a, int call_site) { return GetLine(thr, a, true, call_site); } INLINE CacheLine *GetLineIfExists(TSanThread *thr, uintptr_t a, int call_site) { return GetLine(thr, a, false, call_site); } void ForgetAllState(TSanThread *thr) { for (int i = 0; i < kNumLines; i++) { if (TS_SERIALIZED == 0) CHECK(LineIsNullOrLocked(lines_[i])); lines_[i] = NULL; } map<uintptr_t, Mask> racey_masks; for (Map::iterator i = storage_.begin(); i != storage_.end(); ++i) { CacheLine *line = i->second; if (!line->racey().Empty()) { racey_masks[line->tag()] = line->racey(); } CacheLine::Delete(line); } storage_.clear(); // Restore the racey masks. for (map<uintptr_t, Mask>::iterator it = racey_masks.begin(); it != racey_masks.end(); it++) { CacheLine *line = GetLineOrCreateNew(thr, it->first, __LINE__); line->racey() = it->second; DCHECK(!line->racey().Empty()); ReleaseLine(thr, line->tag(), line, __LINE__); } } void PrintStorageStats() { if (!G_flags->show_stats) return; set<ShadowValue> all_svals; map<size_t, int> sizes; for (Map::iterator it = storage_.begin(); it != storage_.end(); ++it) { CacheLine *line = it->second; // uintptr_t cli = ComputeCacheLineIndexInCache(line->tag()); //if (lines_[cli] == line) { // this line is in cache -- ignore it. // continue; //} set<ShadowValue> s; for (uintptr_t i = 0; i < CacheLine::kLineSize; i++) { if (line->has_shadow_value().Get(i)) { ShadowValue sval = *(line->GetValuePointer(i)); s.insert(sval); all_svals.insert(sval); } } size_t size = s.size(); if (size > 10) size = 10; sizes[size]++; } Printf("Storage sizes: %ld\n", storage_.size()); for (size_t size = 0; size <= CacheLine::kLineSize; size++) { if (sizes[size]) { Printf(" %ld => %d\n", size, sizes[size]); } } Printf("Different svals: %ld\n", all_svals.size()); set <SSID> all_ssids; for (set<ShadowValue>::iterator it = all_svals.begin(); it != all_svals.end(); ++it) { ShadowValue sval = *it; for (int i = 0; i < 2; i++) { SSID ssid = i ? sval.rd_ssid() : sval.wr_ssid(); all_ssids.insert(ssid); } } Printf("Different ssids: %ld\n", all_ssids.size()); set <SID> all_sids; for (set<SSID>::iterator it = all_ssids.begin(); it != all_ssids.end(); ++it) { int size = SegmentSet::Size(*it); for (int i = 0; i < size; i++) { SID sid = SegmentSet::GetSID(*it, i, __LINE__); all_sids.insert(sid); } } Printf("Different sids: %ld\n", all_sids.size()); for (int i = 1; i < Segment::NumberOfSegments(); i++) { if (Segment::ProfileSeg(SID(i)) && all_sids.count(SID(i)) == 0) { // Printf("Segment SID %d: missing in storage; ref=%d\n", i, // Segment::Get(SID(i))->ref_count()); } } } private: INLINE uintptr_t ComputeCacheLineIndexInCache(uintptr_t addr) { return (addr >> CacheLine::kLineSizeBits) & (kNumLines - 1); } NOINLINE CacheLine *WriteBackAndFetch(TSanThread *thr, CacheLine *old_line, uintptr_t tag, uintptr_t cli, bool create_new_if_need) { ScopedMallocCostCenter cc("Cache::WriteBackAndFetch"); CacheLine *res; size_t old_storage_size = storage_.size(); (void)old_storage_size; CacheLine **line_for_this_tag = NULL; if (create_new_if_need) { line_for_this_tag = &storage_[tag]; } else { Map::iterator it = storage_.find(tag); if (it == storage_.end()) { if (DEBUG_MODE && debug_cache) { Printf("WriteBackAndFetch: old_line=%ld tag=%lx cli=%ld\n", old_line, tag, cli); } return NULL; } line_for_this_tag = &(it->second); } CHECK(line_for_this_tag); DCHECK(old_line != kLineIsLocked()); if (*line_for_this_tag == NULL) { // creating a new cache line CHECK(storage_.size() == old_storage_size + 1); res = CacheLine::CreateNewCacheLine(tag); if (DEBUG_MODE && debug_cache) { Printf("%s %d new line %p cli=%lx\n", __FUNCTION__, __LINE__, res, cli); } *line_for_this_tag = res; G_stats->cache_new_line++; } else { // taking an existing cache line from storage. res = *line_for_this_tag; if (DEBUG_MODE && debug_cache) { Printf("%s %d exi line %p tag=%lx old=%p empty=%d cli=%lx\n", __FUNCTION__, __LINE__, res, res->tag(), old_line, res->Empty(), cli); } DCHECK(!res->Empty()); G_stats->cache_fetch++; } if (TS_SERIALIZED) { lines_[cli] = res; } else { DCHECK(lines_[cli] == TidMagic(raw_tid(thr))); } if (old_line) { if (DEBUG_MODE && debug_cache) { Printf("%s %d old line %p empty=%d\n", __FUNCTION__, __LINE__, old_line, old_line->Empty()); } if (old_line->Empty()) { storage_.erase(old_line->tag()); CacheLine::Delete(old_line); G_stats->cache_delete_empty_line++; } else { if (debug_cache) { DebugOnlyCheckCacheLineWhichWeReplace(old_line, res); } } } DCHECK(res->tag() == tag); if (G_stats->cache_max_storage_size < storage_.size()) { G_stats->cache_max_storage_size = storage_.size(); } return res; } void DebugOnlyCheckCacheLineWhichWeReplace(CacheLine *old_line, CacheLine *new_line) { static int c = 0; c++; if ((c % 1024) == 1) { set<int64_t> s; for (uintptr_t i = 0; i < CacheLine::kLineSize; i++) { if (old_line->has_shadow_value().Get(i)) { int64_t sval = *reinterpret_cast<int64_t*>( old_line->GetValuePointer(i)); s.insert(sval); } } Printf("\n[%d] Cache Size=%ld %s different values: %ld\n", c, storage_.size(), old_line->has_shadow_value().ToString().c_str(), s.size()); Printf("new line: %p %p\n", new_line->tag(), new_line->tag() + CacheLine::kLineSize); G_stats->PrintStatsForCache(); } } static const int kNumLines = 1 << (DEBUG_MODE ? 14 : 21); CacheLine *lines_[kNumLines]; // tag => CacheLine typedef unordered_map<uintptr_t, CacheLine*> Map; Map storage_; }; static Cache *G_cache; // -------- Published range -------------------- {{{1 struct PublishInfo { uintptr_t tag; // Tag of the cache line where the mem is published. Mask mask; // The bits that are actually published. VTS *vts; // The point where this range has been published. }; typedef multimap<uintptr_t, PublishInfo> PublishInfoMap; // Maps 'mem+size' to the PublishInfoMap{mem, size, vts}. static PublishInfoMap *g_publish_info_map; const int kDebugPublish = 0; // Get a VTS where 'a' has been published, // return NULL if 'a' was not published. static const VTS *GetPublisherVTS(uintptr_t a) { uintptr_t tag = CacheLine::ComputeTag(a); uintptr_t off = CacheLine::ComputeOffset(a); typedef PublishInfoMap::iterator Iter; pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(tag); for (Iter it = eq_range.first; it != eq_range.second; ++it) { PublishInfo &info = it->second; DCHECK(info.tag == tag); if (info.mask.Get(off)) { G_stats->publish_get++; // Printf("GetPublisherVTS: a=%p vts=%p\n", a, info.vts); return info.vts; } } Printf("GetPublisherVTS returned NULL: a=%p\n", a); return NULL; } static bool CheckSanityOfPublishedMemory(uintptr_t tag, int line) { if (!DEBUG_MODE) return true; if (kDebugPublish) Printf("CheckSanityOfPublishedMemory: line=%d\n", line); typedef PublishInfoMap::iterator Iter; pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(tag); Mask union_of_masks(0); // iterate over all entries for this tag for (Iter it = eq_range.first; it != eq_range.second; ++it) { PublishInfo &info = it->second; CHECK(info.tag == tag); CHECK(it->first == tag); CHECK(info.vts); Mask mask(info.mask); CHECK(!mask.Empty()); // Mask should not be empty.. // And should not intersect with other masks. CHECK(Mask::Intersection(union_of_masks, mask).Empty()); union_of_masks.Union(mask); } return true; } // Clear the publish attribute for the bytes from 'line' that are set in 'mask' static void ClearPublishedAttribute(CacheLine *line, Mask mask) { CHECK(CheckSanityOfPublishedMemory(line->tag(), __LINE__)); typedef PublishInfoMap::iterator Iter; bool deleted_some = true; if (kDebugPublish) Printf(" ClearPublishedAttribute: %p %s\n", line->tag(), mask.ToString().c_str()); while (deleted_some) { deleted_some = false; pair<Iter, Iter> eq_range = g_publish_info_map->equal_range(line->tag()); for (Iter it = eq_range.first; it != eq_range.second; ++it) { PublishInfo &info = it->second; DCHECK(info.tag == line->tag()); if (kDebugPublish) Printf("?ClearPublishedAttribute: %p %s\n", line->tag(), info.mask.ToString().c_str()); info.mask.Subtract(mask); if (kDebugPublish) Printf("+ClearPublishedAttribute: %p %s\n", line->tag(), info.mask.ToString().c_str()); G_stats->publish_clear++; if (info.mask.Empty()) { VTS::Unref(info.vts); g_publish_info_map->erase(it); deleted_some = true; break; } } } CHECK(CheckSanityOfPublishedMemory(line->tag(), __LINE__)); } // Publish range [a, b) in addr's CacheLine with vts. static void PublishRangeInOneLine(TSanThread *thr, uintptr_t addr, uintptr_t a, uintptr_t b, VTS *vts) { ScopedMallocCostCenter cc("PublishRangeInOneLine"); DCHECK(b <= CacheLine::kLineSize); DCHECK(a < b); uintptr_t tag = CacheLine::ComputeTag(addr); CHECK(CheckSanityOfPublishedMemory(tag, __LINE__)); CacheLine *line = G_cache->GetLineOrCreateNew(thr, tag, __LINE__); if (1 || line->published().GetRange(a, b)) { Mask mask(0); mask.SetRange(a, b); // TODO(timurrrr): add warning for re-publishing. ClearPublishedAttribute(line, mask); } line->published().SetRange(a, b); G_cache->ReleaseLine(thr, tag, line, __LINE__); PublishInfo pub_info; pub_info.tag = tag; pub_info.mask.SetRange(a, b); pub_info.vts = vts->Clone(); g_publish_info_map->insert(make_pair(tag, pub_info)); G_stats->publish_set++; if (kDebugPublish) Printf("PublishRange : [%p,%p) %p %s vts=%p\n", a, b, tag, pub_info.mask.ToString().c_str(), vts); CHECK(CheckSanityOfPublishedMemory(tag, __LINE__)); } // Publish memory range [a, b). static void PublishRange(TSanThread *thr, uintptr_t a, uintptr_t b, VTS *vts) { CHECK(a); CHECK(a < b); if (kDebugPublish) Printf("PublishRange : [%p,%p), size=%d, tag=%p\n", a, b, (int)(b - a), CacheLine::ComputeTag(a)); uintptr_t line1_tag = 0, line2_tag = 0; uintptr_t tag = GetCacheLinesForRange(a, b, &line1_tag, &line2_tag); if (tag) { PublishRangeInOneLine(thr, tag, a - tag, b - tag, vts); return; } uintptr_t a_tag = CacheLine::ComputeTag(a); PublishRangeInOneLine(thr, a, a - a_tag, CacheLine::kLineSize, vts); for (uintptr_t tag_i = line1_tag; tag_i < line2_tag; tag_i += CacheLine::kLineSize) { PublishRangeInOneLine(thr, tag_i, 0, CacheLine::kLineSize, vts); } if (b > line2_tag) { PublishRangeInOneLine(thr, line2_tag, 0, b - line2_tag, vts); } } // -------- ThreadSanitizerReport -------------- {{{1 struct ThreadSanitizerReport { // Types of reports. enum ReportType { DATA_RACE, UNLOCK_FOREIGN, UNLOCK_NONLOCKED, INVALID_LOCK, ATOMICITY_VIOLATION, }; // Common fields. ReportType type; TID tid; StackTrace *stack_trace; const char *ReportName() const { switch (type) { case DATA_RACE: return "Race"; case UNLOCK_FOREIGN: return "UnlockForeign"; case UNLOCK_NONLOCKED: return "UnlockNonLocked"; case INVALID_LOCK: return "InvalidLock"; case ATOMICITY_VIOLATION: return "AtomicityViolation"; } CHECK(0); return NULL; } virtual ~ThreadSanitizerReport() { StackTrace::Delete(stack_trace); } }; static bool ThreadSanitizerPrintReport(ThreadSanitizerReport *report); // DATA_RACE. struct ThreadSanitizerDataRaceReport : public ThreadSanitizerReport { uintptr_t racey_addr; string racey_addr_description; uintptr_t last_access_size; TID last_access_tid; SID last_access_sid; bool last_access_is_w; LSID last_acces_lsid[2]; ShadowValue new_sval; ShadowValue old_sval; bool is_expected; bool racey_addr_was_published; }; // Report for bad unlock (UNLOCK_FOREIGN, UNLOCK_NONLOCKED). struct ThreadSanitizerBadUnlockReport : public ThreadSanitizerReport { LID lid; }; // Report for invalid lock addresses (INVALID_LOCK). struct ThreadSanitizerInvalidLockReport : public ThreadSanitizerReport { uintptr_t lock_addr; }; class AtomicityRegion; struct ThreadSanitizerAtomicityViolationReport : public ThreadSanitizerReport { AtomicityRegion *r1, *r2, *r3; }; // -------- LockHistory ------------- {{{1 // For each thread we store a limited amount of history of locks and unlocks. // If there is a race report (in hybrid mode) we try to guess a lock // which might have been used to pass the ownership of the object between // threads. // // Thread1: Thread2: // obj->UpdateMe(); // mu.Lock(); // flag = true; // mu.Unlock(); // (*) // mu.Lock(); // (**) // bool f = flag; // mu.Unlock(); // if (f) // obj->UpdateMeAgain(); // // For this code a hybrid detector may report a false race. // LockHistory will find the lock mu and report it. struct LockHistory { public: // LockHistory which will track no more than `size` recent locks // and the same amount of unlocks. LockHistory(size_t size): size_(size) { } // Record a Lock event. void OnLock(LID lid) { g_lock_era++; Push(LockHistoryElement(lid, g_lock_era), &locks_); } // Record an Unlock event. void OnUnlock(LID lid) { g_lock_era++; Push(LockHistoryElement(lid, g_lock_era), &unlocks_); } // Find locks such that: // - A Lock happend in `l`. // - An Unlock happened in `u`. // - Lock's era is greater than Unlock's era. // - Both eras are greater or equal than min_lock_era. static bool Intersect(const LockHistory &l, const LockHistory &u, int32_t min_lock_era, set<LID> *locks) { const Queue &lq = l.locks_; const Queue &uq = u.unlocks_; for (size_t i = 0; i < lq.size(); i++) { int32_t l_era = lq[i].lock_era; if (l_era < min_lock_era) continue; LID lid = lq[i].lid; // We don't want to report pure happens-before locks since // they already create h-b arcs. if (Lock::LIDtoLock(lid)->is_pure_happens_before()) continue; for (size_t j = 0; j < uq.size(); j++) { int32_t u_era = uq[j].lock_era; if (lid != uq[j].lid) continue; // Report("LockHistory::Intersect: L%d %d %d %d\n", lid.raw(), min_lock_era, u_era, l_era); if (u_era < min_lock_era) continue; if (u_era > l_era) continue; locks->insert(lid); } } return !locks->empty(); } void PrintLocks() const { Print(&locks_); } void PrintUnlocks() const { Print(&unlocks_); } private: struct LockHistoryElement { LID lid; uint32_t lock_era; LockHistoryElement(LID l, uint32_t era) : lid(l), lock_era(era) { } }; typedef deque<LockHistoryElement> Queue; void Push(LockHistoryElement e, Queue *q) { CHECK(q->size() <= size_); if (q->size() == size_) q->pop_front(); q->push_back(e); } void Print(const Queue *q) const { set<LID> printed; for (size_t i = 0; i < q->size(); i++) { const LockHistoryElement &e = (*q)[i]; if (printed.count(e.lid)) continue; Report("era %d: \n", e.lock_era); Lock::ReportLockWithOrWithoutContext(e.lid, true); printed.insert(e.lid); } } Queue locks_; Queue unlocks_; size_t size_; }; // -------- RecentSegmentsCache ------------- {{{1 // For each thread we store a limited amount of recent segments with // the same VTS and LS as the current segment. // When a thread enters a new basic block, we can sometimes reuse a // recent segment if it is the same or not used anymore (see Search()). // // We need to flush the cache when current lockset changes or the current // VTS changes or we do ForgetAllState. // TODO(timurrrr): probably we can cache segments with different LSes and // compare their LS with the current LS. struct RecentSegmentsCache { public: RecentSegmentsCache(int cache_size) : cache_size_(cache_size) {} ~RecentSegmentsCache() { Clear(); } void Clear() { ShortenQueue(0); } void Push(SID sid) { queue_.push_front(sid); Segment::Ref(sid, "RecentSegmentsCache::ShortenQueue"); ShortenQueue(cache_size_); } void ForgetAllState() { queue_.clear(); // Don't unref - the segments are already dead. } INLINE SID Search(CallStack *curr_stack, SID curr_sid, /*OUT*/ bool *needs_refill) { // TODO(timurrrr): we can probably move the matched segment to the head // of the queue. deque<SID>::iterator it = queue_.begin(); for (; it != queue_.end(); it++) { SID sid = *it; Segment::AssertLive(sid, __LINE__); Segment *seg = Segment::Get(sid); if (seg->ref_count() == 1 + (sid == curr_sid)) { // The current segment is not used anywhere else, // so just replace the stack trace in it. // The refcount of an unused segment is equal to // *) 1 if it is stored only in the cache, // *) 2 if it is the current segment of the Thread. *needs_refill = true; return sid; } // Check three top entries of the call stack of the recent segment. // If they match the current segment stack, don't create a new segment. // This can probably lead to a little bit wrong stack traces in rare // occasions but we don't really care that much. if (kSizeOfHistoryStackTrace > 0) { size_t n = curr_stack->size(); uintptr_t *emb_trace = Segment::embedded_stack_trace(sid); if(*emb_trace && // This stack trace was filled curr_stack->size() >= 3 && emb_trace[0] == (*curr_stack)[n-1] && emb_trace[1] == (*curr_stack)[n-2] && emb_trace[2] == (*curr_stack)[n-3]) { *needs_refill = false; return sid; } } } return SID(); } private: void ShortenQueue(size_t flush_to_length) { while (queue_.size() > flush_to_length) { SID sid = queue_.back(); Segment::Unref(sid, "RecentSegmentsCache::ShortenQueue"); queue_.pop_back(); } } deque<SID> queue_; size_t cache_size_; }; // -------- TraceInfo ------------------ {{{1 vector<TraceInfo*> *TraceInfo::g_all_traces; TraceInfo *TraceInfo::NewTraceInfo(size_t n_mops, uintptr_t pc) { ScopedMallocCostCenter cc("TraceInfo::NewTraceInfo"); size_t mem_size = (sizeof(TraceInfo) + (n_mops - 1) * sizeof(MopInfo)); uint8_t *mem = new uint8_t[mem_size]; memset(mem, 0xab, mem_size); TraceInfo *res = new (mem) TraceInfo; res->n_mops_ = n_mops; res->pc_ = ThreadSanitizerWantToCreateSegmentsOnSblockEntry(pc) ? pc : 0; res->counter_ = 0; if (g_all_traces == NULL) { g_all_traces = new vector<TraceInfo*>; } res->literace_storage = NULL; if (G_flags->literace_sampling != 0) { ScopedMallocCostCenter cc("TraceInfo::NewTraceInfo::LiteRaceStorage"); size_t index_of_this_trace = g_all_traces->size(); if ((index_of_this_trace % kLiteRaceStorageSize) == 0) { res->literace_storage = (LiteRaceStorage*) new LiteRaceCounters [kLiteRaceStorageSize * kLiteRaceNumTids]; memset(res->literace_storage, 0, sizeof(LiteRaceStorage)); } else { CHECK(index_of_this_trace > 0); res->literace_storage = (*g_all_traces)[index_of_this_trace - 1]->literace_storage; CHECK(res->literace_storage); } res->storage_index = index_of_this_trace % kLiteRaceStorageSize; } g_all_traces->push_back(res); return res; } void TraceInfo::PrintTraceProfile() { if (!G_flags->trace_profile) return; if (!g_all_traces) return; int64_t total_counter = 0; multimap<size_t, TraceInfo*> traces; for (size_t i = 0; i < g_all_traces->size(); i++) { TraceInfo *trace = (*g_all_traces)[i]; traces.insert(make_pair(trace->counter(), trace)); total_counter += trace->counter(); } if (total_counter == 0) return; Printf("TraceProfile: %ld traces, %lld hits\n", g_all_traces->size(), total_counter); int i = 0; for (multimap<size_t, TraceInfo*>::reverse_iterator it = traces.rbegin(); it != traces.rend(); ++it, i++) { TraceInfo *trace = it->second; int64_t c = it->first; int64_t permile = (c * 1000) / total_counter; CHECK(trace->n_mops() > 0); uintptr_t pc = trace->GetMop(0)->pc(); CHECK(pc); if (permile == 0 || i >= 20) break; Printf("TR=%p pc: %p %p c=%lld (%lld/1000) n_mops=%ld %s\n", trace, trace->pc(), pc, c, permile, trace->n_mops(), PcToRtnNameAndFilePos(pc).c_str()); } } // -------- Atomicity --------------- {{{1 // An attempt to detect atomicity violations (aka high level races). // Here we try to find a very restrictive pattern: // Thread1 Thread2 // r1: { // mu.Lock(); // code_r1(); // mu.Unlock(); // } // r2: { // mu.Lock(); // code_r2(); // mu.Unlock(); // } // r3: { // mu.Lock(); // code_r3(); // mu.Unlock(); // } // We have 3 regions of code such that // - two of them are in one thread and 3-rd in another thread. // - all 3 regions have the same lockset, // - the distance between r1 and r2 is small, // - there is no h-b arc between r2 and r3, // - r1 and r2 have different stack traces, // // In this situation we report a 'Suspected atomicity violation'. // // Current status: // this code detects atomicity violations on our two motivating examples // (--gtest_filter=*Atomicity* --gtest_also_run_disabled_tests) and does // not overwhelm with false reports. // However, this functionality is still raw and not tuned for performance. // TS_ATOMICITY is on in debug mode or if we enabled it at the build time. #ifndef TS_ATOMICITY # define TS_ATOMICITY DEBUG_MODE #endif struct AtomicityRegion { int lock_era; TID tid; VTS *vts; StackTrace *stack_trace; LSID lsid[2]; BitSet access_set[2]; bool used; int n_mops_since_start; void Print() { Report("T%d era=%d nmss=%ld AtomicityRegion:\n rd: %s\n wr: %s\n %s\n%s", tid.raw(), lock_era, n_mops_since_start, access_set[0].ToString().c_str(), access_set[1].ToString().c_str(), TwoLockSetsToString(lsid[false], lsid[true]).c_str(), stack_trace->ToString().c_str() ); } }; bool SimilarLockSetForAtomicity(AtomicityRegion *r1, AtomicityRegion *r2) { // Compare only reader locksets (in case one region took reader locks) return ((r1->lsid[0] == r2->lsid[0])); } static deque<AtomicityRegion *> *g_atomicity_regions; static map<StackTrace *, int, StackTrace::Less> *reported_atomicity_stacks_; const size_t kMaxAtomicityRegions = 8; static void HandleAtomicityRegion(AtomicityRegion *atomicity_region) { if (!g_atomicity_regions) { g_atomicity_regions = new deque<AtomicityRegion*>; reported_atomicity_stacks_ = new map<StackTrace *, int, StackTrace::Less>; } if (g_atomicity_regions->size() >= kMaxAtomicityRegions) { AtomicityRegion *to_delete = g_atomicity_regions->back(); g_atomicity_regions->pop_back(); if (!to_delete->used) { VTS::Unref(to_delete->vts); StackTrace::Delete(to_delete->stack_trace); delete to_delete; } } g_atomicity_regions->push_front(atomicity_region); size_t n = g_atomicity_regions->size(); if (0) { for (size_t i = 0; i < n; i++) { AtomicityRegion *r = (*g_atomicity_regions)[i]; r->Print(); } } AtomicityRegion *r3 = (*g_atomicity_regions)[0]; for (size_t i = 1; i < n; i++) { AtomicityRegion *r2 = (*g_atomicity_regions)[i]; if (r2->tid != r3->tid && SimilarLockSetForAtomicity(r2, r3) && !VTS::HappensBeforeCached(r2->vts, r3->vts)) { for (size_t j = i + 1; j < n; j++) { AtomicityRegion *r1 = (*g_atomicity_regions)[j]; if (r1->tid != r2->tid) continue; CHECK(r2->lock_era > r1->lock_era); if (r2->lock_era - r1->lock_era > 2) break; if (!SimilarLockSetForAtomicity(r1, r2)) continue; if (StackTrace::Equals(r1->stack_trace, r2->stack_trace)) continue; if (!(r1->access_set[1].empty() && !r2->access_set[1].empty() && !r3->access_set[1].empty())) continue; CHECK(r1->n_mops_since_start <= r2->n_mops_since_start); if (r2->n_mops_since_start - r1->n_mops_since_start > 5) continue; if ((*reported_atomicity_stacks_)[r1->stack_trace] > 0) continue; (*reported_atomicity_stacks_)[r1->stack_trace]++; (*reported_atomicity_stacks_)[r2->stack_trace]++; (*reported_atomicity_stacks_)[r3->stack_trace]++; r1->used = r2->used = r3->used = true; ThreadSanitizerAtomicityViolationReport *report = new ThreadSanitizerAtomicityViolationReport; report->type = ThreadSanitizerReport::ATOMICITY_VIOLATION; report->tid = TID(0); report->stack_trace = r1->stack_trace; report->r1 = r1; report->r2 = r2; report->r3 = r3; ThreadSanitizerPrintReport(report); break; } } } } // -------- TSanThread ------------------ {{{1 struct TSanThread { public: ThreadLocalStats stats; TSanThread(TID tid, TID parent_tid, VTS *vts, StackTrace *creation_context, CallStack *call_stack) : is_running_(true), tid_(tid), sid_(0), parent_tid_(parent_tid), max_sp_(0), min_sp_(0), stack_size_for_ignore_(0), fun_r_ignore_(0), min_sp_for_ignore_(0), n_mops_since_start_(0), creation_context_(creation_context), announced_(false), rd_lockset_(0), wr_lockset_(0), expensive_bits_(0), vts_at_exit_(NULL), call_stack_(call_stack), lock_history_(128), recent_segments_cache_(G_flags->recent_segments_cache_size), inside_atomic_op_(), rand_state_((unsigned)(tid.raw() + (uintptr_t)vts + (uintptr_t)creation_context + (uintptr_t)call_stack)) { NewSegmentWithoutUnrefingOld("TSanThread Creation", vts); ignore_depth_[0] = ignore_depth_[1] = 0; HandleRtnCall(0, 0, IGNORE_BELOW_RTN_UNKNOWN); ignore_context_[0] = NULL; ignore_context_[1] = NULL; if (tid != TID(0) && parent_tid.valid()) { CHECK(creation_context_); } // Add myself to the array of threads. CHECK(tid.raw() < G_flags->max_n_threads); CHECK(all_threads_[tid.raw()] == NULL); n_threads_ = max(n_threads_, tid.raw() + 1); all_threads_[tid.raw()] = this; dead_sids_.reserve(kMaxNumDeadSids); fresh_sids_.reserve(kMaxNumFreshSids); ComputeExpensiveBits(); } TID tid() const { return tid_; } TID parent_tid() const { return parent_tid_; } void increment_n_mops_since_start() { n_mops_since_start_++; } // STACK uintptr_t max_sp() const { return max_sp_; } uintptr_t min_sp() const { return min_sp_; } unsigned random() { return tsan_prng(&rand_state_); } bool ShouldReportRaces() const { return (inside_atomic_op_ == 0); } void SetStack(uintptr_t stack_min, uintptr_t stack_max) { CHECK(stack_min < stack_max); // Stay sane. Expect stack less than 64M. CHECK(stack_max - stack_min <= 64 * 1024 * 1024); min_sp_ = stack_min; max_sp_ = stack_max; if (G_flags->ignore_stack) { min_sp_for_ignore_ = min_sp_; stack_size_for_ignore_ = max_sp_ - min_sp_; } else { CHECK(min_sp_for_ignore_ == 0 && stack_size_for_ignore_ == 0); } } bool MemoryIsInStack(uintptr_t a) { return a >= min_sp_ && a <= max_sp_; } bool IgnoreMemoryIfInStack(uintptr_t a) { return (a - min_sp_for_ignore_) < stack_size_for_ignore_; } bool Announce() { if (announced_) return false; announced_ = true; if (tid_ == TID(0)) { Report("INFO: T0 is program's main thread\n"); } else { if (G_flags->announce_threads) { Report("INFO: T%d has been created by T%d at this point: {{{\n%s}}}\n", tid_.raw(), parent_tid_.raw(), creation_context_->ToString().c_str()); TSanThread * parent = GetIfExists(parent_tid_); CHECK(parent); parent->Announce(); } else { Report("INFO: T%d has been created by T%d. " "Use --announce-threads to see the creation stack.\n", tid_.raw(), parent_tid_.raw()); } } return true; } string ThreadName() const { char buff[100]; snprintf(buff, sizeof(buff), "T%d", tid().raw()); string res = buff; if (thread_name_.length() > 0) { res += " ("; res += thread_name_; res += ")"; } return res; } bool is_running() const { return is_running_; } INLINE void ComputeExpensiveBits() { bool has_expensive_flags = G_flags->trace_level > 0 || G_flags->show_stats > 1 || G_flags->sample_events > 0; expensive_bits_ = (ignore_depth_[0] != 0) | ((ignore_depth_[1] != 0) << 1) | ((has_expensive_flags == true) << 2); } int expensive_bits() { return expensive_bits_; } int ignore_reads() { return expensive_bits() & 1; } int ignore_writes() { return (expensive_bits() >> 1) & 1; } // ignore INLINE void set_ignore_accesses(bool is_w, bool on) { ignore_depth_[is_w] += on ? 1 : -1; CHECK(ignore_depth_[is_w] >= 0); ComputeExpensiveBits(); if (on && G_flags->save_ignore_context) { StackTrace::Delete(ignore_context_[is_w]); ignore_context_[is_w] = CreateStackTrace(0, 3); } } INLINE void set_ignore_all_accesses(bool on) { set_ignore_accesses(false, on); set_ignore_accesses(true, on); } StackTrace *GetLastIgnoreContext(bool is_w) { return ignore_context_[is_w]; } SID sid() const { return sid_; } Segment *segment() const { CHECK(sid().valid()); Segment::AssertLive(sid(), __LINE__); return Segment::Get(sid()); } VTS *vts() const { return segment()->vts(); } void set_thread_name(const char *name) { thread_name_ = string(name); } void HandleThreadEnd() { CHECK(is_running_); is_running_ = false; CHECK(!vts_at_exit_); vts_at_exit_ = vts()->Clone(); CHECK(vts_at_exit_); FlushDeadSids(); ReleaseFreshSids(); call_stack_ = NULL; } // Return the TID of the joined child and it's vts TID HandleThreadJoinAfter(VTS **vts_at_exit, TID joined_tid) { CHECK(joined_tid.raw() > 0); CHECK(GetIfExists(joined_tid) != NULL); TSanThread* joined_thread = TSanThread::Get(joined_tid); // Sometimes the joined thread is not truly dead yet. // In that case we just take the current vts. if (joined_thread->is_running_) *vts_at_exit = joined_thread->vts()->Clone(); else *vts_at_exit = joined_thread->vts_at_exit_; if (*vts_at_exit == NULL) { Printf("vts_at_exit==NULL; parent=%d, child=%d\n", tid().raw(), joined_tid.raw()); } CHECK(*vts_at_exit); if (0) Printf("T%d: vts_at_exit_: %s\n", joined_tid.raw(), (*vts_at_exit)->ToString().c_str()); return joined_tid; } static int NumberOfThreads() { return INTERNAL_ANNOTATE_UNPROTECTED_READ(n_threads_); } static TSanThread *GetIfExists(TID tid) { if (tid.raw() < NumberOfThreads()) return Get(tid); return NULL; } static TSanThread *Get(TID tid) { DCHECK(tid.raw() < NumberOfThreads()); return all_threads_[tid.raw()]; } void HandleAccessSet() { BitSet *rd_set = lock_era_access_set(false); BitSet *wr_set = lock_era_access_set(true); if (rd_set->empty() && wr_set->empty()) return; CHECK(G_flags->atomicity && !G_flags->pure_happens_before); AtomicityRegion *atomicity_region = new AtomicityRegion; atomicity_region->lock_era = g_lock_era; atomicity_region->tid = tid(); atomicity_region->vts = vts()->Clone(); atomicity_region->lsid[0] = lsid(0); atomicity_region->lsid[1] = lsid(1); atomicity_region->access_set[0] = *rd_set; atomicity_region->access_set[1] = *wr_set; atomicity_region->stack_trace = CreateStackTrace(); atomicity_region->used = false; atomicity_region->n_mops_since_start = this->n_mops_since_start_; // atomicity_region->Print(); // Printf("----------- %s\n", __FUNCTION__); // ReportStackTrace(0, 7); HandleAtomicityRegion(atomicity_region); } // Locks void HandleLock(uintptr_t lock_addr, bool is_w_lock) { Lock *lock = Lock::LookupOrCreate(lock_addr); if (debug_lock) { Printf("T%d lid=%d %sLock %p; %s\n", tid_.raw(), lock->lid().raw(), is_w_lock ? "Wr" : "Rd", lock_addr, LockSet::ToString(lsid(is_w_lock)).c_str()); ReportStackTrace(0, 7); } // NOTE: we assume that all locks can be acquired recurively. // No warning about recursive locking will be issued. if (is_w_lock) { // Recursive locks are properly handled because LockSet is in fact a // multiset. wr_lockset_ = LockSet::Add(wr_lockset_, lock); rd_lockset_ = LockSet::Add(rd_lockset_, lock); lock->WrLock(tid_, CreateStackTrace()); } else { if (lock->wr_held()) { ReportStackTrace(); } rd_lockset_ = LockSet::Add(rd_lockset_, lock); lock->RdLock(CreateStackTrace()); } if (lock->is_pure_happens_before()) { if (is_w_lock) { HandleWait(lock->wr_signal_addr()); } else { HandleWait(lock->rd_signal_addr()); } } if (G_flags->suggest_happens_before_arcs) { lock_history_.OnLock(lock->lid()); } NewSegmentForLockingEvent(); lock_era_access_set_[0].Clear(); lock_era_access_set_[1].Clear(); } void HandleUnlock(uintptr_t lock_addr) { HandleAccessSet(); Lock *lock = Lock::Lookup(lock_addr); // If the lock is not found, report an error. if (lock == NULL) { ThreadSanitizerInvalidLockReport *report = new ThreadSanitizerInvalidLockReport; report->type = ThreadSanitizerReport::INVALID_LOCK; report->tid = tid(); report->lock_addr = lock_addr; report->stack_trace = CreateStackTrace(); ThreadSanitizerPrintReport(report); return; } bool is_w_lock = lock->wr_held(); if (debug_lock) { Printf("T%d lid=%d %sUnlock %p; %s\n", tid_.raw(), lock->lid().raw(), is_w_lock ? "Wr" : "Rd", lock_addr, LockSet::ToString(lsid(is_w_lock)).c_str()); ReportStackTrace(0, 7); } if (lock->is_pure_happens_before()) { // reader unlock signals only to writer lock, // writer unlock signals to both. if (is_w_lock) { HandleSignal(lock->rd_signal_addr()); } HandleSignal(lock->wr_signal_addr()); } if (!lock->wr_held() && !lock->rd_held()) { ThreadSanitizerBadUnlockReport *report = new ThreadSanitizerBadUnlockReport; report->type = ThreadSanitizerReport::UNLOCK_NONLOCKED; report->tid = tid(); report->lid = lock->lid(); report->stack_trace = CreateStackTrace(); ThreadSanitizerPrintReport(report); return; } bool removed = false; if (is_w_lock) { lock->WrUnlock(); removed = LockSet::Remove(wr_lockset_, lock, &wr_lockset_) && LockSet::Remove(rd_lockset_, lock, &rd_lockset_); } else { lock->RdUnlock(); removed = LockSet::Remove(rd_lockset_, lock, &rd_lockset_); } if (!removed) { ThreadSanitizerBadUnlockReport *report = new ThreadSanitizerBadUnlockReport; report->type = ThreadSanitizerReport::UNLOCK_FOREIGN; report->tid = tid(); report->lid = lock->lid(); report->stack_trace = CreateStackTrace(); ThreadSanitizerPrintReport(report); } if (G_flags->suggest_happens_before_arcs) { lock_history_.OnUnlock(lock->lid()); } NewSegmentForLockingEvent(); lock_era_access_set_[0].Clear(); lock_era_access_set_[1].Clear(); } // Handles memory access with race reports suppressed. void HandleAtomicMop(uintptr_t a, uintptr_t pc, tsan_atomic_op op, tsan_memory_order mo, size_t size); void HandleForgetSignaller(uintptr_t cv) { SignallerMap::iterator it = signaller_map_->find(cv); if (it != signaller_map_->end()) { if (debug_happens_before) { Printf("T%d: ForgetSignaller: %p:\n %s\n", tid_.raw(), cv, (it->second.vts)->ToString().c_str()); if (G_flags->debug_level >= 1) { ReportStackTrace(); } } VTS::Unref(it->second.vts); signaller_map_->erase(it); } } LSID lsid(bool is_w) { return is_w ? wr_lockset_ : rd_lockset_; } const LockHistory &lock_history() { return lock_history_; } // SIGNAL/WAIT events. void HandleWait(uintptr_t cv) { SignallerMap::iterator it = signaller_map_->find(cv); if (it != signaller_map_->end()) { const VTS *signaller_vts = it->second.vts; NewSegmentForWait(signaller_vts); } if (debug_happens_before) { Printf("T%d: Wait: %p:\n %s %s\n", tid_.raw(), cv, vts()->ToString().c_str(), Segment::ToString(sid()).c_str()); if (G_flags->debug_level >= 1) { ReportStackTrace(); } } } void HandleSignal(uintptr_t cv) { Signaller *signaller = &(*signaller_map_)[cv]; if (!signaller->vts) { signaller->vts = vts()->Clone(); } else { VTS *new_vts = VTS::Join(signaller->vts, vts()); VTS::Unref(signaller->vts); signaller->vts = new_vts; } NewSegmentForSignal(); if (debug_happens_before) { Printf("T%d: Signal: %p:\n %s %s\n %s\n", tid_.raw(), cv, vts()->ToString().c_str(), Segment::ToString(sid()).c_str(), (signaller->vts)->ToString().c_str()); if (G_flags->debug_level >= 1) { ReportStackTrace(); } } } void INLINE NewSegmentWithoutUnrefingOld(const char *call_site, VTS *new_vts) { DCHECK(new_vts); SID new_sid = Segment::AddNewSegment(tid(), new_vts, rd_lockset_, wr_lockset_); SID old_sid = sid(); if (old_sid.raw() != 0 && new_vts != vts()) { // Flush the cache if VTS changed - the VTS won't repeat. recent_segments_cache_.Clear(); } sid_ = new_sid; Segment::Ref(new_sid, "TSanThread::NewSegmentWithoutUnrefingOld"); if (kSizeOfHistoryStackTrace > 0) { FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid())); } if (0) Printf("2: %s T%d/S%d old_sid=%d NewSegment: %s\n", call_site, tid().raw(), sid().raw(), old_sid.raw(), vts()->ToString().c_str()); } void INLINE NewSegment(const char *call_site, VTS *new_vts) { SID old_sid = sid(); NewSegmentWithoutUnrefingOld(call_site, new_vts); Segment::Unref(old_sid, "TSanThread::NewSegment"); } void NewSegmentForLockingEvent() { // Flush the cache since we can't reuse segments with different lockset. recent_segments_cache_.Clear(); NewSegment(__FUNCTION__, vts()->Clone()); } void NewSegmentForMallocEvent() { // Flush the cache since we can't reuse segments with different lockset. recent_segments_cache_.Clear(); NewSegment(__FUNCTION__, vts()->Clone()); } void SetTopPc(uintptr_t pc) { if (pc) { DCHECK(!call_stack_->empty()); call_stack_->back() = pc; } } void NOINLINE HandleSblockEnterSlowLocked() { AssertTILHeld(); FlushStateIfOutOfSegments(this); this->stats.history_creates_new_segment++; VTS *new_vts = vts()->Clone(); NewSegment("HandleSblockEnter", new_vts); recent_segments_cache_.Push(sid()); GetSomeFreshSids(); // fill the thread-local SID cache. } INLINE bool HandleSblockEnter(uintptr_t pc, bool allow_slow_path) { DCHECK(G_flags->keep_history); if (!pc) return true; this->stats.events[SBLOCK_ENTER]++; SetTopPc(pc); bool refill_stack = false; SID match = recent_segments_cache_.Search(call_stack_, sid(), /*OUT*/&refill_stack); DCHECK(kSizeOfHistoryStackTrace > 0); if (match.valid()) { // This part is 100% thread-local, no need for locking. if (sid_ != match) { Segment::Ref(match, "TSanThread::HandleSblockEnter"); this->AddDeadSid(sid_, "TSanThread::HandleSblockEnter"); sid_ = match; } if (refill_stack) { this->stats.history_reuses_segment++; FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid())); } else { this->stats.history_uses_same_segment++; } } else if (fresh_sids_.size() > 0) { // We have a fresh ready-to-use segment in thread local cache. SID fresh_sid = fresh_sids_.back(); fresh_sids_.pop_back(); Segment::SetupFreshSid(fresh_sid, tid(), vts()->Clone(), rd_lockset_, wr_lockset_); this->AddDeadSid(sid_, "TSanThread::HandleSblockEnter-1"); Segment::Ref(fresh_sid, "TSanThread::HandleSblockEnter-1"); sid_ = fresh_sid; recent_segments_cache_.Push(sid()); FillEmbeddedStackTrace(Segment::embedded_stack_trace(sid())); this->stats.history_uses_preallocated_segment++; } else { if (!allow_slow_path) return false; AssertTILHeld(); // No fresh SIDs available, have to grab a lock and get few. HandleSblockEnterSlowLocked(); } return true; } void NewSegmentForWait(const VTS *signaller_vts) { const VTS *current_vts = vts(); if (0) Printf("T%d NewSegmentForWait: \n %s\n %s\n", tid().raw(), current_vts->ToString().c_str(), signaller_vts->ToString().c_str()); // We don't want to create a happens-before arc if it will be redundant. if (!VTS::HappensBeforeCached(signaller_vts, current_vts)) { VTS *new_vts = VTS::Join(current_vts, signaller_vts); NewSegment("NewSegmentForWait", new_vts); } DCHECK(VTS::HappensBeforeCached(signaller_vts, vts())); } void NewSegmentForSignal() { VTS *cur_vts = vts(); VTS *new_vts = VTS::CopyAndTick(cur_vts, tid()); NewSegment("NewSegmentForSignal", new_vts); } // When creating a child thread, we need to know // 1. where the thread was created (ctx) // 2. What was the vector clock of the parent thread (vts). struct ThreadCreateInfo { StackTrace *ctx; VTS *vts; }; static void StopIgnoringAccessesInT0BecauseNewThreadStarted() { AssertTILHeld(); if (g_so_far_only_one_thread) { g_so_far_only_one_thread = false; Get(TID(0))->set_ignore_all_accesses(false); } } // This event comes before the child is created (e.g. just // as we entered pthread_create). void HandleThreadCreateBefore(TID parent_tid, uintptr_t pc) { CHECK(parent_tid == tid()); StopIgnoringAccessesInT0BecauseNewThreadStarted(); // Store ctx and vts under TID(0). ThreadCreateInfo info; info.ctx = CreateStackTrace(pc); info.vts = vts()->Clone(); CHECK(info.ctx && info.vts); child_tid_to_create_info_[TID(0)] = info; // Tick vts. this->NewSegmentForSignal(); if (debug_thread) { Printf("T%d: THR_CREATE_BEFORE\n", parent_tid.raw()); } } // This event comes when we are exiting the thread creation routine. // It may appear before *or* after THR_START event, at least with PIN. void HandleThreadCreateAfter(TID parent_tid, TID child_tid) { CHECK(parent_tid == tid()); // Place the info under child_tid if we did not use it yet. if (child_tid_to_create_info_.count(TID(0))){ child_tid_to_create_info_[child_tid] = child_tid_to_create_info_[TID(0)]; child_tid_to_create_info_.erase(TID(0)); } if (debug_thread) { Printf("T%d: THR_CREATE_AFTER %d\n", parent_tid.raw(), child_tid.raw()); } } void HandleChildThreadStart(TID child_tid, VTS **vts, StackTrace **ctx) { TSanThread *parent = this; ThreadCreateInfo info; if (child_tid_to_create_info_.count(child_tid)) { // We already seen THR_CREATE_AFTER, so the info is under child_tid. info = child_tid_to_create_info_[child_tid]; child_tid_to_create_info_.erase(child_tid); CHECK(info.ctx && info.vts); } else if (child_tid_to_create_info_.count(TID(0))){ // We have not seen THR_CREATE_AFTER, but already seen THR_CREATE_BEFORE. info = child_tid_to_create_info_[TID(0)]; child_tid_to_create_info_.erase(TID(0)); CHECK(info.ctx && info.vts); } else { // We have not seen THR_CREATE_BEFORE/THR_CREATE_AFTER. // If the tool is single-threaded (valgrind) these events are redundant. info.ctx = parent->CreateStackTrace(); info.vts = parent->vts()->Clone(); parent->NewSegmentForSignal(); } *ctx = info.ctx; VTS *singleton = VTS::CreateSingleton(child_tid); *vts = VTS::Join(singleton, info.vts); VTS::Unref(singleton); VTS::Unref(info.vts); if (debug_thread) { Printf("T%d: THR_START parent: T%d : %s %s\n", child_tid.raw(), parent->tid().raw(), parent->vts()->ToString().c_str(), (*vts)->ToString().c_str()); if (G_flags->announce_threads) { Printf("%s\n", (*ctx)->ToString().c_str()); } } // Parent should have ticked its VTS so there should be no h-b. DCHECK(!VTS::HappensBefore(parent->vts(), *vts)); } // Support for Cyclic Barrier, e.g. pthread_barrier_t. // We need to create (barrier_count-1)^2 h-b arcs between // threads blocking on a barrier. We should not create any h-b arcs // for two calls to barrier_wait if the barrier was reset between then. struct CyclicBarrierInfo { // The value given to barrier_init. uint32_t barrier_count; // How many times we may block on this barrier before resetting. int32_t calls_before_reset; // How many times we entered the 'wait-before' and 'wait-after' handlers. int32_t n_wait_before, n_wait_after; }; // The following situation is possible: // - N threads blocked on a barrier. // - All N threads reached the barrier and we started getting 'wait-after' // events, but did not yet get all of them. // - N threads blocked on the barrier again and we started getting // 'wait-before' events from the next barrier epoch. // - We continue getting 'wait-after' events from the previous epoch. // // We don't want to create h-b arcs between barrier events of different // epochs, so we use 'barrier + (epoch % 4)' as an object on which we // signal and wait (it is unlikely that more than 4 epochs are live at once. enum { kNumberOfPossibleBarrierEpochsLiveAtOnce = 4 }; // Maps the barrier pointer to CyclicBarrierInfo. typedef unordered_map<uintptr_t, CyclicBarrierInfo> CyclicBarrierMap; CyclicBarrierInfo &GetCyclicBarrierInfo(uintptr_t barrier) { if (cyclic_barrier_map_ == NULL) { cyclic_barrier_map_ = new CyclicBarrierMap; } return (*cyclic_barrier_map_)[barrier]; } void HandleBarrierInit(uintptr_t barrier, uint32_t n) { CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier); CHECK(n > 0); memset(&info, 0, sizeof(CyclicBarrierInfo)); info.barrier_count = n; } void HandleBarrierWaitBefore(uintptr_t barrier) { CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier); CHECK(info.calls_before_reset >= 0); int32_t epoch = info.n_wait_before / info.barrier_count; epoch %= kNumberOfPossibleBarrierEpochsLiveAtOnce; info.n_wait_before++; if (info.calls_before_reset == 0) { // We are blocking the first time after reset. Clear the VTS. info.calls_before_reset = info.barrier_count; Signaller &signaller = (*signaller_map_)[barrier + epoch]; VTS::Unref(signaller.vts); signaller.vts = NULL; if (debug_happens_before) { Printf("T%d barrier %p (epoch %d) reset\n", tid().raw(), barrier, epoch); } } info.calls_before_reset--; // Signal to all threads that blocked on this barrier. if (debug_happens_before) { Printf("T%d barrier %p (epoch %d) wait before\n", tid().raw(), barrier, epoch); } HandleSignal(barrier + epoch); } void HandleBarrierWaitAfter(uintptr_t barrier) { CyclicBarrierInfo &info = GetCyclicBarrierInfo(barrier); int32_t epoch = info.n_wait_after / info.barrier_count; epoch %= kNumberOfPossibleBarrierEpochsLiveAtOnce; info.n_wait_after++; if (debug_happens_before) { Printf("T%d barrier %p (epoch %d) wait after\n", tid().raw(), barrier, epoch); } HandleWait(barrier + epoch); } // Call stack ------------- void PopCallStack() { CHECK(!call_stack_->empty()); call_stack_->pop_back(); } void HandleRtnCall(uintptr_t call_pc, uintptr_t target_pc, IGNORE_BELOW_RTN ignore_below) { this->stats.events[RTN_CALL]++; if (!call_stack_->empty() && call_pc) { call_stack_->back() = call_pc; } call_stack_->push_back(target_pc); bool ignore = false; if (ignore_below == IGNORE_BELOW_RTN_UNKNOWN) { if (ignore_below_cache_.Lookup(target_pc, &ignore) == false) { ignore = ThreadSanitizerIgnoreAccessesBelowFunction(target_pc); ignore_below_cache_.Insert(target_pc, ignore); G_stats->ignore_below_cache_miss++; } else { // Just in case, check the result of caching. DCHECK(ignore == ThreadSanitizerIgnoreAccessesBelowFunction(target_pc)); } } else { DCHECK(ignore_below == IGNORE_BELOW_RTN_YES || ignore_below == IGNORE_BELOW_RTN_NO); ignore = ignore_below == IGNORE_BELOW_RTN_YES; } if (fun_r_ignore_) { fun_r_ignore_++; } else if (ignore) { fun_r_ignore_ = 1; set_ignore_all_accesses(true); } } void HandleRtnExit() { this->stats.events[RTN_EXIT]++; if (!call_stack_->empty()) { call_stack_->pop_back(); if (fun_r_ignore_) { if (--fun_r_ignore_ == 0) { set_ignore_all_accesses(false); } } } } uintptr_t GetCallstackEntry(size_t offset_from_top) { if (offset_from_top >= call_stack_->size()) return 0; return (*call_stack_)[call_stack_->size() - offset_from_top - 1]; } string CallStackRtnName(size_t offset_from_top = 0) { if (call_stack_->size() <= offset_from_top) return ""; uintptr_t pc = (*call_stack_)[call_stack_->size() - offset_from_top - 1]; return PcToRtnName(pc, false); } string CallStackToStringRtnOnly(int len) { string res; for (int i = 0; i < len; i++) { if (i) res += " "; res += CallStackRtnName(i); } return res; } uintptr_t CallStackTopPc() { if (call_stack_->empty()) return 0; return call_stack_->back(); } INLINE void FillEmbeddedStackTrace(uintptr_t *emb_trace) { size_t size = min(call_stack_->size(), (size_t)kSizeOfHistoryStackTrace); size_t idx = call_stack_->size() - 1; uintptr_t *pcs = call_stack_->pcs(); for (size_t i = 0; i < size; i++, idx--) { emb_trace[i] = pcs[idx]; } if (size < (size_t) kSizeOfHistoryStackTrace) { emb_trace[size] = 0; } } INLINE void FillStackTrace(StackTrace *trace, size_t size) { size_t idx = call_stack_->size() - 1; uintptr_t *pcs = call_stack_->pcs(); for (size_t i = 0; i < size; i++, idx--) { trace->Set(i, pcs[idx]); } } INLINE StackTrace *CreateStackTrace(uintptr_t pc = 0, int max_len = -1, int capacity = 0) { if (!call_stack_->empty() && pc) { call_stack_->back() = pc; } if (max_len <= 0) { max_len = G_flags->num_callers; } int size = call_stack_->size(); if (size > max_len) size = max_len; StackTrace *res = StackTrace::CreateNewEmptyStackTrace(size, capacity); FillStackTrace(res, size); return res; } void ReportStackTrace(uintptr_t pc = 0, int max_len = -1) { StackTrace *trace = CreateStackTrace(pc, max_len); Report("%s", trace->ToString().c_str()); StackTrace::Delete(trace); } static void ForgetAllState() { // G_flags->debug_level = 2; for (int i = 0; i < TSanThread::NumberOfThreads(); i++) { TSanThread *thr = Get(TID(i)); thr->recent_segments_cache_.ForgetAllState(); thr->sid_ = SID(); // Reset the old SID so we don't try to read its VTS. VTS *singleton_vts = VTS::CreateSingleton(TID(i), 2); if (thr->is_running()) { thr->NewSegmentWithoutUnrefingOld("ForgetAllState", singleton_vts); } for (map<TID, ThreadCreateInfo>::iterator j = thr->child_tid_to_create_info_.begin(); j != thr->child_tid_to_create_info_.end(); ++j) { ThreadCreateInfo &info = j->second; VTS::Unref(info.vts); // The parent's VTS should neither happen-before nor equal the child's. info.vts = VTS::CreateSingleton(TID(i), 1); } if (thr->vts_at_exit_) { VTS::Unref(thr->vts_at_exit_); thr->vts_at_exit_ = singleton_vts->Clone(); } thr->dead_sids_.clear(); thr->fresh_sids_.clear(); } signaller_map_->ClearAndDeleteElements(); } static void InitClassMembers() { ScopedMallocCostCenter malloc_cc("InitClassMembers"); all_threads_ = new TSanThread*[G_flags->max_n_threads]; memset(all_threads_, 0, sizeof(TSanThread*) * G_flags->max_n_threads); n_threads_ = 0; signaller_map_ = new SignallerMap; } BitSet *lock_era_access_set(int is_w) { return &lock_era_access_set_[is_w]; } // --------- dead SIDs, fresh SIDs // When running fast path w/o a lock we need to recycle SIDs to a thread-local // pool. HasRoomForDeadSids and AddDeadSid may be called w/o a lock. // FlushDeadSids should be called under a lock. // When creating a new segment on SBLOCK_ENTER, we need to get a fresh SID // from somewhere. We keep a pile of fresh ready-to-use SIDs in // a thread-local array. enum { kMaxNumDeadSids = 64, kMaxNumFreshSids = 256, }; INLINE void AddDeadSid(SID sid, const char *where) { if (TS_SERIALIZED) { Segment::Unref(sid, where); } else { if (Segment::UnrefNoRecycle(sid, where) == 0) { dead_sids_.push_back(sid); } } } INLINE void FlushDeadSids() { if (TS_SERIALIZED) return; size_t n = dead_sids_.size(); for (size_t i = 0; i < n; i++) { SID sid = dead_sids_[i]; Segment::AssertLive(sid, __LINE__); DCHECK(Segment::Get(sid)->ref_count() == 0); Segment::RecycleOneSid(sid); } dead_sids_.clear(); } INLINE bool HasRoomForDeadSids() const { return TS_SERIALIZED ? false : dead_sids_.size() < kMaxNumDeadSids - 2; } void GetSomeFreshSids() { size_t cur_size = fresh_sids_.size(); DCHECK(cur_size <= kMaxNumFreshSids); if (cur_size > kMaxNumFreshSids / 2) { // We already have quite a few fresh SIDs, do nothing. return; } DCHECK(fresh_sids_.capacity() >= kMaxNumFreshSids); size_t n_requested_sids = kMaxNumFreshSids - cur_size; fresh_sids_.resize(kMaxNumFreshSids); Segment::AllocateFreshSegments(n_requested_sids, &fresh_sids_[cur_size]); } void ReleaseFreshSids() { for (size_t i = 0; i < fresh_sids_.size(); i++) { Segment::RecycleOneFreshSid(fresh_sids_[i]); } fresh_sids_.clear(); } private: bool is_running_; string thread_name_; TID tid_; // This thread's tid. SID sid_; // Current segment ID. TID parent_tid_; // Parent's tid. bool thread_local_copy_of_g_has_expensive_flags_; uintptr_t max_sp_; uintptr_t min_sp_; uintptr_t stack_size_for_ignore_; uintptr_t fun_r_ignore_; // > 0 if we are inside a fun_r-ed function. uintptr_t min_sp_for_ignore_; uintptr_t n_mops_since_start_; StackTrace *creation_context_; bool announced_; LSID rd_lockset_; LSID wr_lockset_; // These bits should be read in the hottest loop, so we combine them all // together. // bit 1 -- ignore reads. // bit 2 -- ignore writes. // bit 3 -- have expensive flags int expensive_bits_; int ignore_depth_[2]; StackTrace *ignore_context_[2]; VTS *vts_at_exit_; CallStack *call_stack_; vector<SID> dead_sids_; vector<SID> fresh_sids_; PtrToBoolCache<251> ignore_below_cache_; LockHistory lock_history_; BitSet lock_era_access_set_[2]; RecentSegmentsCache recent_segments_cache_; map<TID, ThreadCreateInfo> child_tid_to_create_info_; // This var is used to suppress race reports // when handling atomic memory accesses. // That is, an atomic memory access can't race with other accesses, // however plain memory accesses can race with atomic memory accesses. int inside_atomic_op_; prng_t rand_state_; struct Signaller { VTS *vts; }; class SignallerMap: public unordered_map<uintptr_t, Signaller> { public: void ClearAndDeleteElements() { for (iterator it = begin(); it != end(); ++it) { VTS::Unref(it->second.vts); } clear(); } }; // All threads. The main thread has tid 0. static TSanThread **all_threads_; static int n_threads_; // signaller address -> VTS static SignallerMap *signaller_map_; static CyclicBarrierMap *cyclic_barrier_map_; }; INLINE static int32_t raw_tid(TSanThread *t) { return t->tid().raw(); } // TSanThread:: static members TSanThread **TSanThread::all_threads_; int TSanThread::n_threads_; TSanThread::SignallerMap *TSanThread::signaller_map_; TSanThread::CyclicBarrierMap *TSanThread::cyclic_barrier_map_; // -------- TsanAtomicCore ------------------ {{{1 // Responsible for handling of atomic memory accesses. class TsanAtomicCore { public: TsanAtomicCore(); void HandleWrite(TSanThread* thr, uintptr_t a, uint64_t v, uint64_t prev, bool is_acquire, bool is_release, bool is_rmw); uint64_t HandleRead(TSanThread* thr, uintptr_t a, uint64_t v, bool is_acquire); void ClearMemoryState(uintptr_t a, uintptr_t b); private: // Represents one value in modification history // of an atomic variable. struct AtomicHistoryEntry { // Actual value. // (atomics of size more than uint64_t are not supported as of now) uint64_t val; // ID of a thread that did the modification. TID tid; // The thread's clock during the modification. int32_t clk; // Vector clock that is acquired by a thread // that loads the value. // Similar to Signaller::vts. VTS* vts; }; // Descriptor of an atomic variable. struct Atomic { // Number of stored entries in the modification order of the variable. // This represents space-modelling preciseness trade-off. // 4 values should be generally enough. static int32_t const kHistSize = 4; // Current position in the modification order. int32_t hist_pos; // Modification history organized as a circular buffer. // That is, old values are discarded. AtomicHistoryEntry hist [kHistSize]; // It's basically a tid->hist_pos map that tracks what threads // had seen what values. It's required to meet the following requirement: // even relaxed loads must not be reordered in a single thread. VectorClock last_seen; Atomic(); void reset(bool init = false); }; typedef map<uintptr_t, Atomic> AtomicMap; AtomicMap atomic_map_; void AtomicFixHist(Atomic* atomic, uint64_t prev); TsanAtomicCore(TsanAtomicCore const&); void operator=(TsanAtomicCore const&); }; static TsanAtomicCore* g_atomicCore; // -------- Clear Memory State ------------------ {{{1 static void INLINE UnrefSegmentsInMemoryRange(uintptr_t a, uintptr_t b, Mask mask, CacheLine *line) { while (!mask.Empty()) { uintptr_t x = mask.GetSomeSetBit(); DCHECK(mask.Get(x)); mask.Clear(x); line->GetValuePointer(x)->Unref("Detector::UnrefSegmentsInMemoryRange"); } } void INLINE ClearMemoryStateInOneLine(TSanThread *thr, uintptr_t addr, uintptr_t beg, uintptr_t end) { AssertTILHeld(); CacheLine *line = G_cache->GetLineIfExists(thr, addr, __LINE__); // CacheLine *line = G_cache->GetLineOrCreateNew(addr, __LINE__); if (line) { DCHECK(beg < CacheLine::kLineSize); DCHECK(end <= CacheLine::kLineSize); DCHECK(beg < end); Mask published = line->published(); if (UNLIKELY(!published.Empty())) { Mask mask(published.GetRange(beg, end)); ClearPublishedAttribute(line, mask); } Mask old_used = line->ClearRangeAndReturnOldUsed(beg, end); UnrefSegmentsInMemoryRange(beg, end, old_used, line); G_cache->ReleaseLine(thr, addr, line, __LINE__); } } // clear memory state for [a,b) void NOINLINE ClearMemoryState(TSanThread *thr, uintptr_t a, uintptr_t b) { if (a == b) return; CHECK(a < b); uintptr_t line1_tag = 0, line2_tag = 0; uintptr_t single_line_tag = GetCacheLinesForRange(a, b, &line1_tag, &line2_tag); if (single_line_tag) { ClearMemoryStateInOneLine(thr, a, a - single_line_tag, b - single_line_tag); return; } uintptr_t a_tag = CacheLine::ComputeTag(a); ClearMemoryStateInOneLine(thr, a, a - a_tag, CacheLine::kLineSize); for (uintptr_t tag_i = line1_tag; tag_i < line2_tag; tag_i += CacheLine::kLineSize) { ClearMemoryStateInOneLine(thr, tag_i, 0, CacheLine::kLineSize); } if (b > line2_tag) { ClearMemoryStateInOneLine(thr, line2_tag, 0, b - line2_tag); } if (DEBUG_MODE && G_flags->debug_level >= 2) { // Check that we've cleared it. Slow! for (uintptr_t x = a; x < b; x++) { uintptr_t off = CacheLine::ComputeOffset(x); (void)off; CacheLine *line = G_cache->GetLineOrCreateNew(thr, x, __LINE__); CHECK(!line->has_shadow_value().Get(off)); G_cache->ReleaseLine(thr, x, line, __LINE__); } } g_atomicCore->ClearMemoryState(a, b); } // -------- PCQ --------------------- {{{1 struct PCQ { uintptr_t pcq_addr; deque<VTS*> putters; }; typedef map<uintptr_t, PCQ> PCQMap; static PCQMap *g_pcq_map; // -------- Heap info ---------------------- {{{1 #include "ts_heap_info.h" // Information about heap memory. struct HeapInfo { uintptr_t ptr; uintptr_t size; SID sid; HeapInfo() : ptr(0), size(0), sid(0) { } Segment *seg() { return Segment::Get(sid); } TID tid() { return seg()->tid(); } string StackTraceString() { return Segment::StackTraceString(sid); } }; static HeapMap<HeapInfo> *G_heap_map; struct ThreadStackInfo { uintptr_t ptr; uintptr_t size; ThreadStackInfo() : ptr(0), size(0) { } }; static HeapMap<ThreadStackInfo> *G_thread_stack_map; // -------- Forget all state -------- {{{1 // We need to forget all state and start over because we've // run out of some resources (most likely, segment IDs). static void ForgetAllStateAndStartOver(TSanThread *thr, const char *reason) { // This is done under the main lock. AssertTILHeld(); size_t start_time = g_last_flush_time = TimeInMilliSeconds(); Report("T%d INFO: %s. Flushing state.\n", raw_tid(thr), reason); if (TS_SERIALIZED == 0) { // We own the lock, but we also must acquire all cache lines // so that the fast-path (unlocked) code does not execute while // we are flushing. G_cache->AcquireAllLines(thr); } if (0) { Report("INFO: Thread Sanitizer will now forget all history.\n"); Report("INFO: This is experimental, and may fail!\n"); if (G_flags->keep_history > 0) { Report("INFO: Consider re-running with --keep_history=0\n"); } if (G_flags->show_stats) { G_stats->PrintStats(); } } G_stats->n_forgets++; Segment::ForgetAllState(); SegmentSet::ForgetAllState(); TSanThread::ForgetAllState(); VTS::FlushHBCache(); G_heap_map->Clear(); g_publish_info_map->clear(); for (PCQMap::iterator it = g_pcq_map->begin(); it != g_pcq_map->end(); ++it) { PCQ &pcq = it->second; for (deque<VTS*>::iterator it2 = pcq.putters.begin(); it2 != pcq.putters.end(); ++it2) { VTS::Unref(*it2); *it2 = VTS::CreateSingleton(TID(0), 1); } } // Must be the last one to flush as it effectively releases the // cach lines and enables fast path code to run in other threads. G_cache->ForgetAllState(thr); size_t stop_time = TimeInMilliSeconds(); if (DEBUG_MODE || (stop_time - start_time > 0)) { Report("T%d INFO: Flush took %ld ms\n", raw_tid(thr), stop_time - start_time); } } static INLINE void FlushStateIfOutOfSegments(TSanThread *thr) { if (Segment::NumberOfSegments() > kMaxSIDBeforeFlush) { // too few sids left -- flush state. if (DEBUG_MODE) { G_cache->PrintStorageStats(); Segment::ShowSegmentStats(); } ForgetAllStateAndStartOver(thr, "run out of segment IDs"); } } // -------- Expected Race ---------------------- {{{1 typedef HeapMap<ExpectedRace> ExpectedRacesMap; static ExpectedRacesMap *G_expected_races_map; static bool g_expecting_races; static int g_found_races_since_EXPECT_RACE_BEGIN; ExpectedRace* ThreadSanitizerFindExpectedRace(uintptr_t addr) { return G_expected_races_map->GetInfo(addr); } // -------- Suppressions ----------------------- {{{1 static const char default_suppressions[] = // TODO(kcc): as it gets bigger, move it into a separate object file. "# We need to have some default suppressions, but we don't want to \n" "# keep them in a separate text file, so we keep the in the code. \n" #ifdef VGO_darwin "{ \n" " dyld tries to unlock an invalid mutex when adding/removing image. \n" " ThreadSanitizer:InvalidLock \n" " fun:pthread_mutex_unlock \n" " fun:_dyld_register_func_for_*_image \n" "} \n" "{ \n" " Benign reports in __NSOperationInternal when using workqueue threads \n" " ThreadSanitizer:Race \n" " fun:__+[__NSOperationInternal _observeValueForKeyPath:ofObject:changeKind:oldValue:newValue:indexes:context:]_block_invoke_*\n" " fun:_dispatch_call_block_and_release \n" "} \n" "{ \n" " Benign race in GCD when using workqueue threads. \n" " ThreadSanitizer:Race \n" " fun:____startOperations_block_invoke_* \n" " ... \n" " fun:_dispatch_call_block_and_release \n" "} \n" "{ \n" " Benign race in NSOQSchedule when using workqueue threads. \n" " ThreadSanitizer:Race \n" " fun:__doStart* \n" " ... \n" " fun:_dispatch_call_block_and_release \n" "} \n" #endif #ifndef _MSC_VER "{ \n" " False reports on std::string internals. See TSan issue #40. \n" " ThreadSanitizer:Race \n" " ... \n" " fun:*~basic_string* \n" "} \n" "{ \n" " False reports on std::string internals. See TSan issue #40. \n" " ThreadSanitizer:Race \n" " ... \n" " fun:*basic_string*_M_destroy \n" "} \n" #else "{ \n" " False lock report inside ntdll.dll \n" " ThreadSanitizer:InvalidLock \n" " fun:* \n" " obj:*ntdll.dll \n" "} \n" "{ \n" " False report due to lack of debug symbols in ntdll.dll (a) \n" " ThreadSanitizer:InvalidLock \n" " fun:*SRWLock* \n" "} \n" "{ \n" " False report due to lack of debug symbols in ntdll.dll (b) \n" " ThreadSanitizer:UnlockForeign \n" " fun:*SRWLock* \n" "} \n" "{ \n" " False report due to lack of debug symbols in ntdll.dll (c) \n" " ThreadSanitizer:UnlockNonLocked \n" " fun:*SRWLock* \n" "} \n" "{ \n" " False reports on std::string internals (2). See TSan issue #40. \n" " ThreadSanitizer:Race \n" " ... \n" " fun:*basic_string*scalar deleting destructor* \n" "} \n" #endif #ifdef TS_PIN "{ \n" " Suppression for issue 54 (PIN lacks support for IFUNC) \n" " ThreadSanitizer:Race \n" " ... \n" " fun:*NegativeTests_Strlen::Worker* \n" "} \n" #endif ; // -------- Report Storage --------------------- {{{1 class ReportStorage { public: ReportStorage() : n_reports(0), n_race_reports(0), program_finished_(0), unwind_cb_(0) { if (G_flags->generate_suppressions) { Report("INFO: generate_suppressions = true\n"); } // Read default suppressions int n = suppressions_.ReadFromString(default_suppressions); if (n == -1) { Report("Error reading default suppressions at line %d: %s\n", suppressions_.GetErrorLineNo(), suppressions_.GetErrorString().c_str()); exit(1); } // Read user-supplied suppressions. for (size_t i = 0; i < G_flags->suppressions.size(); i++) { const string &supp_path = G_flags->suppressions[i]; Report("INFO: reading suppressions file %s\n", supp_path.c_str()); int n = suppressions_.ReadFromString(ReadFileToString(supp_path, true)); if (n == -1) { Report("Error at line %d: %s\n", suppressions_.GetErrorLineNo(), suppressions_.GetErrorString().c_str()); exit(1); } Report("INFO: %6d suppression(s) read from file %s\n", n, supp_path.c_str()); } } bool NOINLINE AddReport(TSanThread *thr, uintptr_t pc, bool is_w, uintptr_t addr, int size, ShadowValue old_sval, ShadowValue new_sval, bool is_published) { { // Check this isn't a "_ZNSs4_Rep20_S_empty_rep_storageE" report. uintptr_t offset; string symbol_descr; if (GetNameAndOffsetOfGlobalObject(addr, &symbol_descr, &offset)) { if (StringMatch("*empty_rep_storage*", symbol_descr)) return false; if (StringMatch("_IO_stdfile_*_lock", symbol_descr)) return false; if (StringMatch("_IO_*_stdout_", symbol_descr)) return false; if (StringMatch("_IO_*_stderr_", symbol_descr)) return false; } } bool is_expected = false; ExpectedRace *expected_race = G_expected_races_map->GetInfo(addr); if (debug_expected_races) { Printf("Checking expected race for %lx; exp_race=%p\n", addr, expected_race); if (expected_race) { Printf(" FOUND\n"); } } if (expected_race) { if (G_flags->nacl_untrusted != expected_race->is_nacl_untrusted) { Report("WARNING: this race is only expected in NaCl %strusted mode\n", expected_race->is_nacl_untrusted ? "un" : ""); } else { is_expected = true; expected_race->count++; } } if (g_expecting_races) { is_expected = true; g_found_races_since_EXPECT_RACE_BEGIN++; } if (is_expected && !G_flags->show_expected_races) return false; StackTrace *stack_trace = thr->CreateStackTrace(pc); if (unwind_cb_) { int const maxcnt = 256; uintptr_t cur_stack [maxcnt]; int cnt = unwind_cb_(cur_stack, maxcnt, pc); if (cnt > 0 && cnt <= maxcnt) { cnt = min<int>(cnt, stack_trace->capacity()); stack_trace->set_size(cnt); for (int i = 0; i < cnt; i++) stack_trace->Set(i, cur_stack[i]); } } int n_reports_for_this_context = reported_stacks_[stack_trace]++; if (n_reports_for_this_context > 0) { // we already reported a race here. StackTrace::Delete(stack_trace); return false; } ThreadSanitizerDataRaceReport *race_report = new ThreadSanitizerDataRaceReport; race_report->type = ThreadSanitizerReport::DATA_RACE; race_report->new_sval = new_sval; race_report->old_sval = old_sval; race_report->is_expected = is_expected; race_report->last_access_is_w = is_w; race_report->racey_addr = addr; race_report->racey_addr_description = DescribeMemory(addr); race_report->last_access_tid = thr->tid(); race_report->last_access_sid = thr->sid(); race_report->last_access_size = size; race_report->stack_trace = stack_trace; race_report->racey_addr_was_published = is_published; race_report->last_acces_lsid[false] = thr->lsid(false); race_report->last_acces_lsid[true] = thr->lsid(true); Segment *seg = Segment::Get(thr->sid()); (void)seg; CHECK(thr->lsid(false) == seg->lsid(false)); CHECK(thr->lsid(true) == seg->lsid(true)); return ThreadSanitizerPrintReport(race_report); } void AnnounceThreadsInSegmentSet(SSID ssid) { if (ssid.IsEmpty()) return; for (int s = 0; s < SegmentSet::Size(ssid); s++) { Segment *seg = SegmentSet::GetSegmentForNonSingleton(ssid, s, __LINE__); TSanThread::Get(seg->tid())->Announce(); } } void PrintConcurrentSegmentSet(SSID ssid, TID tid, SID sid, LSID lsid, bool is_w, const char *descr, set<LID> *locks, set<SID>* concurrent_sids) { if (ssid.IsEmpty()) return; bool printed_header = false; TSanThread *thr1 = TSanThread::Get(tid); for (int s = 0; s < SegmentSet::Size(ssid); s++) { SID concurrent_sid = SegmentSet::GetSID(ssid, s, __LINE__); Segment *seg = Segment::Get(concurrent_sid); if (Segment::HappensBeforeOrSameThread(concurrent_sid, sid)) continue; if (!LockSet::IntersectionIsEmpty(lsid, seg->lsid(is_w))) continue; if (concurrent_sids) { concurrent_sids->insert(concurrent_sid); } TSanThread *thr2 = TSanThread::Get(seg->tid()); if (!printed_header) { Report(" %sConcurrent %s happened at (OR AFTER) these points:%s\n", c_magenta, descr, c_default); printed_header = true; } Report(" %s (%s):\n", thr2->ThreadName().c_str(), TwoLockSetsToString(seg->lsid(false), seg->lsid(true)).c_str()); if (G_flags->show_states) { Report(" S%d\n", concurrent_sid.raw()); } LockSet::AddLocksToSet(seg->lsid(false), locks); LockSet::AddLocksToSet(seg->lsid(true), locks); Report("%s", Segment::StackTraceString(concurrent_sid).c_str()); if (!G_flags->pure_happens_before && G_flags->suggest_happens_before_arcs) { set<LID> message_locks; // Report("Locks in T%d\n", thr1->tid().raw()); // thr1->lock_history().PrintLocks(); // Report("Unlocks in T%d\n", thr2->tid().raw()); // thr2->lock_history().PrintUnlocks(); if (LockHistory::Intersect(thr1->lock_history(), thr2->lock_history(), seg->lock_era(), &message_locks)) { Report(" Note: these locks were recently released by T%d" " and later acquired by T%d: {%s}\n" " See http://code.google.com/p/data-race-test/wiki/" "PureHappensBeforeVsHybrid\n", thr2->tid().raw(), thr1->tid().raw(), SetOfLocksToString(message_locks).c_str()); locks->insert(message_locks.begin(), message_locks.end()); } } } } void SetProgramFinished() { CHECK(!program_finished_); program_finished_ = true; } string RaceInfoString(uintptr_t pc, set<SID>& concurrent_sids) { string s; char buf[100]; snprintf(buf, 100, "Race verifier data: %p", (void*)pc); s += buf; for (set<SID>::iterator it = concurrent_sids.begin(); it != concurrent_sids.end(); ++it) { // Take the first pc of the concurrent stack trace. uintptr_t concurrent_pc = *Segment::embedded_stack_trace(*it); snprintf(buf, 100, ",%p", (void*)concurrent_pc); s += buf; } s += "\n"; return s; } void PrintRaceReport(ThreadSanitizerDataRaceReport *race) { bool short_report = program_finished_; if (!short_report) { AnnounceThreadsInSegmentSet(race->new_sval.rd_ssid()); AnnounceThreadsInSegmentSet(race->new_sval.wr_ssid()); } bool is_w = race->last_access_is_w; TID tid = race->last_access_tid; TSanThread *thr = TSanThread::Get(tid); SID sid = race->last_access_sid; LSID lsid = race->last_acces_lsid[is_w]; set<LID> all_locks; n_race_reports++; if (G_flags->html) { Report("<b id=race%d>Race report #%d; </b>" "<a href=\"#race%d\">Next;</a> " "<a href=\"#race%d\">Prev;</a>\n", n_race_reports, n_race_reports, n_race_reports+1, n_race_reports-1); } // Note the {{{ and }}}. These are for vim folds. Report("%sWARNING: %s data race during %s of size %d at %p: {{{%s\n", c_red, race->is_expected ? "Expected" : "Possible", is_w ? "write" : "read", race->last_access_size, race->racey_addr, c_default); if (!short_report) { LockSet::AddLocksToSet(race->last_acces_lsid[false], &all_locks); LockSet::AddLocksToSet(race->last_acces_lsid[true], &all_locks); Report(" %s (%s):\n", thr->ThreadName().c_str(), TwoLockSetsToString(race->last_acces_lsid[false], race->last_acces_lsid[true]).c_str()); } CHECK(race->stack_trace); Report("%s", race->stack_trace->ToString().c_str()); if (short_report) { Report(" See the full version of this report above.\n"); Report("}%s\n", "}}"); return; } // Report(" sid=%d; vts=%s\n", thr->sid().raw(), // thr->vts()->ToString().c_str()); if (G_flags->show_states) { Report(" old state: %s\n", race->old_sval.ToString().c_str()); Report(" new state: %s\n", race->new_sval.ToString().c_str()); } set<SID> concurrent_sids; if (G_flags->keep_history) { PrintConcurrentSegmentSet(race->new_sval.wr_ssid(), tid, sid, lsid, true, "write(s)", &all_locks, &concurrent_sids); if (is_w) { PrintConcurrentSegmentSet(race->new_sval.rd_ssid(), tid, sid, lsid, false, "read(s)", &all_locks, &concurrent_sids); } } else { Report(" %sAccess history is disabled. " "Consider running with --keep-history=1 for better reports.%s\n", c_cyan, c_default); } if (race->racey_addr_was_published) { Report(" This memory was published\n"); } if (race->racey_addr_description.size() > 0) { Report("%s", race->racey_addr_description.c_str()); } if (race->is_expected) { ExpectedRace *expected_race = G_expected_races_map->GetInfo(race->racey_addr); if (expected_race) { CHECK(expected_race->description); Report(" Description: \"%s\"\n", expected_race->description); } } set<LID> locks_reported; if (!all_locks.empty()) { Report(" %sLocks involved in this report " "(reporting last lock sites):%s {%s}\n", c_green, c_default, SetOfLocksToString(all_locks).c_str()); for (set<LID>::iterator it = all_locks.begin(); it != all_locks.end(); ++it) { LID lid = *it; Lock::ReportLockWithOrWithoutContext(lid, true); } } string raceInfoString = RaceInfoString(race->stack_trace->Get(0), concurrent_sids); Report(" %s", raceInfoString.c_str()); Report("}}}\n"); } bool PrintReport(ThreadSanitizerReport *report) { CHECK(report); // Check if we have a suppression. vector<string> funcs_mangled; vector<string> funcs_demangled; vector<string> objects; CHECK(!g_race_verifier_active); CHECK(report->stack_trace); CHECK(report->stack_trace->size()); for (size_t i = 0; i < report->stack_trace->size(); i++) { uintptr_t pc = report->stack_trace->Get(i); string img, rtn, file; int line; PcToStrings(pc, false, &img, &rtn, &file, &line); if (rtn == "(below main)" || rtn == "ThreadSanitizerStartThread") break; funcs_mangled.push_back(rtn); funcs_demangled.push_back(NormalizeFunctionName(PcToRtnName(pc, true))); objects.push_back(img); if (rtn == "main") break; } string suppression_name; if (suppressions_.StackTraceSuppressed("ThreadSanitizer", report->ReportName(), funcs_mangled, funcs_demangled, objects, &suppression_name)) { used_suppressions_[suppression_name]++; return false; } // Actually print it. if (report->type == ThreadSanitizerReport::UNLOCK_FOREIGN) { ThreadSanitizerBadUnlockReport *bad_unlock = reinterpret_cast<ThreadSanitizerBadUnlockReport*>(report); Report("WARNING: Lock %s was released by thread T%d" " which did not acquire this lock: {{{\n%s}}}\n", Lock::ToString(bad_unlock->lid).c_str(), bad_unlock->tid.raw(), bad_unlock->stack_trace->ToString().c_str()); } else if (report->type == ThreadSanitizerReport::UNLOCK_NONLOCKED) { ThreadSanitizerBadUnlockReport *bad_unlock = reinterpret_cast<ThreadSanitizerBadUnlockReport*>(report); Report("WARNING: Unlocking a non-locked lock %s in thread T%d: " "{{{\n%s}}}\n", Lock::ToString(bad_unlock->lid).c_str(), bad_unlock->tid.raw(), bad_unlock->stack_trace->ToString().c_str()); } else if (report->type == ThreadSanitizerReport::INVALID_LOCK) { ThreadSanitizerInvalidLockReport *invalid_lock = reinterpret_cast<ThreadSanitizerInvalidLockReport*>(report); Report("WARNING: accessing an invalid lock %p in thread T%d: " "{{{\n%s}}}\n", invalid_lock->lock_addr, invalid_lock->tid.raw(), invalid_lock->stack_trace->ToString().c_str()); } else if (report->type == ThreadSanitizerReport::ATOMICITY_VIOLATION) { ThreadSanitizerAtomicityViolationReport *av = reinterpret_cast<ThreadSanitizerAtomicityViolationReport*>(report); Report("WARNING: Suspected atomicity violation {{{\n"); av->r1->Print(); av->r2->Print(); av->r3->Print(); Report("}}}\n"); } else { CHECK(report->type == ThreadSanitizerReport::DATA_RACE); ThreadSanitizerDataRaceReport *race = reinterpret_cast<ThreadSanitizerDataRaceReport*>(report); PrintRaceReport(race); } n_reports++; SetNumberOfFoundErrors(n_reports); if (!G_flags->summary_file.empty()) { char buff[100]; snprintf(buff, sizeof(buff), "ThreadSanitizer: %d warning(s) reported\n", n_reports); // We overwrite the contents of this file with the new summary. // We don't do that at the end because even if we crash later // we will already have the summary. OpenFileWriteStringAndClose(G_flags->summary_file, buff); } // Generate a suppression. if (G_flags->generate_suppressions) { string supp = "{\n"; supp += " <Put your suppression name here>\n"; supp += string(" ThreadSanitizer:") + report->ReportName() + "\n"; for (size_t i = 0; i < funcs_mangled.size(); i++) { const string &func = funcs_demangled[i]; if (func.size() == 0 || func == "(no symbols") { supp += " obj:" + objects[i] + "\n"; } else { supp += " fun:" + funcs_demangled[i] + "\n"; } if (StackTrace::CutStackBelowFunc(funcs_demangled[i])) { break; } } supp += "}"; Printf("------- suppression -------\n%s\n------- end suppression -------\n", supp.c_str()); } return true; } void PrintUsedSuppression() { for (map<string, int>::iterator it = used_suppressions_.begin(); it != used_suppressions_.end(); ++it) { Report("used_suppression: %d %s\n", it->second, it->first.c_str()); } } void PrintSummary() { Report("ThreadSanitizer summary: reported %d warning(s) (%d race(s))\n", n_reports, n_race_reports); } string DescribeMemory(uintptr_t a) { const int kBufLen = 1023; char buff[kBufLen+1]; // Is this stack? for (int i = 0; i < TSanThread::NumberOfThreads(); i++) { TSanThread *t = TSanThread::Get(TID(i)); if (!t || !t->is_running()) continue; if (t->MemoryIsInStack(a)) { snprintf(buff, sizeof(buff), " %sLocation %p is %ld bytes inside T%d's stack [%p,%p]%s\n", c_blue, reinterpret_cast<void*>(a), static_cast<long>(t->max_sp() - a), i, reinterpret_cast<void*>(t->min_sp()), reinterpret_cast<void*>(t->max_sp()), c_default ); return buff; } } HeapInfo *heap_info = G_heap_map->GetInfo(a); if (heap_info) { snprintf(buff, sizeof(buff), " %sLocation %p is %ld bytes inside a block starting at %p" " of size %ld allocated by T%d from heap:%s\n", c_blue, reinterpret_cast<void*>(a), static_cast<long>(a - heap_info->ptr), reinterpret_cast<void*>(heap_info->ptr), static_cast<long>(heap_info->size), heap_info->tid().raw(), c_default); return string(buff) + heap_info->StackTraceString().c_str(); } // Is it a global object? uintptr_t offset; string symbol_descr; if (GetNameAndOffsetOfGlobalObject(a, &symbol_descr, &offset)) { snprintf(buff, sizeof(buff), " %sAddress %p is %d bytes inside data symbol \"", c_blue, reinterpret_cast<void*>(a), static_cast<int>(offset)); return buff + symbol_descr + "\"" + c_default + "\n"; } if (G_flags->debug_level >= 2) { string res; // Is this near stack? for (int i = 0; i < TSanThread::NumberOfThreads(); i++) { TSanThread *t = TSanThread::Get(TID(i)); const uintptr_t kMaxStackDiff = 1024 * 16; uintptr_t diff1 = a - t->max_sp(); uintptr_t diff2 = t->min_sp() - a; if (diff1 < kMaxStackDiff || diff2 < kMaxStackDiff || t->MemoryIsInStack(a)) { uintptr_t diff = t->MemoryIsInStack(a) ? 0 : (diff1 < kMaxStackDiff ? diff1 : diff2); snprintf(buff, sizeof(buff), " %sLocation %p is within %d bytes outside T%d's stack [%p,%p]%s\n", c_blue, reinterpret_cast<void*>(a), static_cast<int>(diff), i, reinterpret_cast<void*>(t->min_sp()), reinterpret_cast<void*>(t->max_sp()), c_default ); res += buff; } } if (res.size() > 0) { return res + " This report _may_ indicate that valgrind incorrectly " "computed the stack boundaries\n"; } } return ""; } void SetUnwindCallback(ThreadSanitizerUnwindCallback cb) { unwind_cb_ = cb; } private: map<StackTrace *, int, StackTrace::Less> reported_stacks_; int n_reports; int n_race_reports; bool program_finished_; Suppressions suppressions_; map<string, int> used_suppressions_; ThreadSanitizerUnwindCallback unwind_cb_; }; // -------- Event Sampling ---------------- {{{1 // This class samples (profiles) events. // Instances of this class should all be static. class EventSampler { public: // Sample one event void Sample(TSanThread *thr, const char *event_name, bool need_locking) { CHECK_NE(G_flags->sample_events, 0); (counter_)++; if ((counter_ & ((1 << G_flags->sample_events) - 1)) != 0) return; TIL til(ts_lock, 8, need_locking); string pos = thr->CallStackToStringRtnOnly(G_flags->sample_events_depth); (*samples_)[event_name][pos]++; total_samples_++; if (total_samples_ >= print_after_this_number_of_samples_) { print_after_this_number_of_samples_ += print_after_this_number_of_samples_ / 2; ShowSamples(); } } // Show existing samples static void ShowSamples() { if (G_flags->sample_events == 0) return; Printf("ShowSamples: (all samples: %lld)\n", total_samples_); for (SampleMapMap::iterator it1 = samples_->begin(); it1 != samples_->end(); ++it1) { string name = it1->first; SampleMap &m = it1->second; int total = 0; for (SampleMap::iterator it2 = m.begin(); it2 != m.end(); it2++) { total += it2->second; } map<int, string> reverted_map; for (SampleMap::iterator it2 = m.begin(); it2 != m.end(); it2++) { int n_samples = it2->second; if (n_samples * 1000 < total) continue; reverted_map[n_samples] = it2->first; } Printf("%s: total samples %'d (~%'lld events)\n", name.c_str(), total, (int64_t)total << G_flags->sample_events); for (map<int, string>::iterator it = reverted_map.begin(); it != reverted_map.end(); ++it) { Printf("%s: %d samples (~%d%%) %s\n", name.c_str(), it->first, (it->first * 100) / total, it->second.c_str()); } Printf("\n"); } } static void InitClassMembers() { samples_ = new SampleMapMap; total_samples_ = 0; print_after_this_number_of_samples_ = 1000; } private: int counter_; typedef map<string, int> SampleMap; typedef map<string, SampleMap> SampleMapMap; static SampleMapMap *samples_; static int64_t total_samples_; static int64_t print_after_this_number_of_samples_; }; EventSampler::SampleMapMap *EventSampler::samples_; int64_t EventSampler::total_samples_; int64_t EventSampler::print_after_this_number_of_samples_; // -------- Detector ---------------------- {{{1 // Collection of event handlers. class Detector { public: void INLINE HandleTraceLoop(TSanThread *thr, uintptr_t pc, MopInfo *mops, uintptr_t *tleb, size_t n, int expensive_bits, bool need_locking) { bool has_expensive_flags = (expensive_bits & 4) != 0; size_t i = 0; uintptr_t sblock_pc = pc; size_t n_locks = 0; do { uintptr_t addr = tleb[i]; if (addr == 0) continue; // This mop was not executed. MopInfo *mop = &mops[i]; tleb[i] = 0; // we've consumed this mop, clear it. DCHECK(mop->size() != 0); DCHECK(mop->pc() != 0); if ((expensive_bits & 1) && mop->is_write() == false) continue; if ((expensive_bits & 2) && mop->is_write() == true) continue; n_locks += HandleMemoryAccessInternal(thr, &sblock_pc, addr, mop, has_expensive_flags, need_locking); } while (++i < n); if (has_expensive_flags) { const size_t mop_stat_size = TS_ARRAY_SIZE(thr->stats.mops_per_trace); thr->stats.mops_per_trace[min(n, mop_stat_size - 1)]++; const size_t stat_size = TS_ARRAY_SIZE(thr->stats.locks_per_trace); thr->stats.locks_per_trace[min(n_locks, stat_size - 1)]++; } } #ifdef _MSC_VER NOINLINE // With MSVC, INLINE would cause the compilation to be insanely slow. #else INLINE #endif void HandleTrace(TSanThread *thr, MopInfo *mops, size_t n, uintptr_t pc, uintptr_t *tleb, bool need_locking) { DCHECK(n); // 0 bit - ignore reads, 1 bit -- ignore writes, // 2 bit - has_expensive_flags. int expensive_bits = thr->expensive_bits(); if (expensive_bits == 0) { HandleTraceLoop(thr, pc, mops, tleb, n, 0, need_locking); } else { if ((expensive_bits & 3) == 3) { // everything is ignored, just clear the tleb. for (size_t i = 0; i < n; i++) tleb[i] = 0; } else { HandleTraceLoop(thr, pc, mops, tleb, n, expensive_bits, need_locking); } } // At the end, the tleb must be cleared. for (size_t i = 0; i < n; i++) DCHECK(tleb[i] == 0); } // Special case of a trace with just one mop and no sblock. void INLINE HandleMemoryAccess(TSanThread *thr, uintptr_t pc, uintptr_t addr, uintptr_t size, bool is_w, bool need_locking) { CHECK(size); MopInfo mop(pc, size, is_w, false); HandleTrace(thr, &mop, 1, 0/*no sblock*/, &addr, need_locking); } void ShowUnfreedHeap() { // check if there is not deleted memory // (for debugging free() interceptors, not for leak detection) if (DEBUG_MODE && G_flags->debug_level >= 1) { for (HeapMap<HeapInfo>::iterator it = G_heap_map->begin(); it != G_heap_map->end(); ++it) { HeapInfo &info = it->second; Printf("Not free()-ed memory: %p [%p, %p)\n%s\n", info.size, info.ptr, info.ptr + info.size, info.StackTraceString().c_str()); } } } void FlushExpectedRaces(bool print_summary) { // Report("ThreadSanitizerValgrind: done\n"); // check if we found all expected races (for unit tests only). static int total_missing = 0; int this_flush_missing = 0; for (ExpectedRacesMap::iterator it = G_expected_races_map->begin(); it != G_expected_races_map->end(); ++it) { ExpectedRace race = it->second; if (debug_expected_races) { Printf("Checking if expected race fired: %p\n", race.ptr); } if (race.count == 0 && !(g_race_verifier_active && !race.is_verifiable) && (G_flags->nacl_untrusted == race.is_nacl_untrusted)) { ++this_flush_missing; Printf("Missing an expected race on %p: %s (annotated at %s)\n", it->first, race.description, PcToRtnNameAndFilePos(race.pc).c_str()); } } if (this_flush_missing) { int n_errs = GetNumberOfFoundErrors(); SetNumberOfFoundErrors(n_errs + this_flush_missing); total_missing += this_flush_missing; } G_expected_races_map->Clear(); if (print_summary && total_missing > 0) Report("WARNING: %d expected race(s) NOT detected!\n", total_missing); } void HandleProgramEnd() { FlushExpectedRaces(true); // ShowUnfreedHeap(); EventSampler::ShowSamples(); ShowStats(); TraceInfo::PrintTraceProfile(); ShowProcSelfStatus(); reports_.PrintUsedSuppression(); reports_.PrintSummary(); // Report("ThreadSanitizerValgrind: exiting\n"); } void FlushIfOutOfMem(TSanThread *thr) { static int max_vm_size; static int soft_limit; const int hard_limit = G_flags->max_mem_in_mb; const int minimal_soft_limit = (hard_limit * 13) / 16; const int print_info_limit = (hard_limit * 12) / 16; CHECK(hard_limit > 0); int vm_size_in_mb = GetVmSizeInMb(); if (max_vm_size < vm_size_in_mb) { max_vm_size = vm_size_in_mb; if (max_vm_size > print_info_limit) { Report("INFO: ThreadSanitizer's VmSize: %dM\n", (int)max_vm_size); } } if (soft_limit == 0) { soft_limit = minimal_soft_limit; } if (vm_size_in_mb > soft_limit) { ForgetAllStateAndStartOver(thr, "ThreadSanitizer is running close to its memory limit"); soft_limit = vm_size_in_mb + 1; } } // Force state flushing. void FlushState(TID tid) { ForgetAllStateAndStartOver(TSanThread::Get(tid), "State flushing requested by client"); } void FlushIfNeeded(TSanThread *thr) { // Are we out of segment IDs? #ifdef TS_VALGRIND // GetVmSizeInMb() works only with valgrind any way. static int counter; counter++; // ATTENTION: don't do this in multi-threaded code -- too slow. CHECK(TS_SERIALIZED == 1); // Are we out of memory? if (G_flags->max_mem_in_mb > 0) { const int kFreq = 1014 * 32; if ((counter % kFreq) == 0) { // Don't do it too often. // TODO(kcc): find a way to check memory limit more frequently. TIL til(ts_lock, 7); AssertTILHeld(); FlushIfOutOfMem(thr); } } #if 0 if ((counter % (1024 * 1024 * 64)) == 0 || counter == (1024 * 1024)) { // ShowStats(); EventSampler::ShowSamples(); TraceInfo::PrintTraceProfile(); } #endif #endif #if 0 // do we still need it? Hope not.. size_t flush_period = G_flags->flush_period * 1000; // milliseconds. if (flush_period && (counter % (1024 * 4)) == 0) { size_t cur_time = TimeInMilliSeconds(); if (cur_time - g_last_flush_time > flush_period) { TIL til(ts_lock, 7); ForgetAllStateAndStartOver( "Doing periodic flush (period is set by --flush_period=n_seconds)"); } } #endif } void HandleRtnCall(TID tid, uintptr_t call_pc, uintptr_t target_pc, IGNORE_BELOW_RTN ignore_below) { TSanThread *thr = TSanThread::Get(tid); thr->HandleRtnCall(call_pc, target_pc, ignore_below); FlushIfNeeded(thr); } void INLINE HandleOneEvent(Event *e) { ScopedMallocCostCenter malloc_cc("HandleOneEvent"); DCHECK(e); EventType type = e->type(); DCHECK(type != NOOP); TSanThread *thr = NULL; if (type != THR_START) { thr = TSanThread::Get(TID(e->tid())); DCHECK(thr); thr->SetTopPc(e->pc()); thr->stats.events[type]++; } switch (type) { case READ: HandleMemoryAccess(thr, e->pc(), e->a(), e->info(), false, true); return; case WRITE: HandleMemoryAccess(thr, e->pc(), e->a(), e->info(), true, true); return; case RTN_CALL: HandleRtnCall(TID(e->tid()), e->pc(), e->a(), IGNORE_BELOW_RTN_UNKNOWN); return; case RTN_EXIT: thr->HandleRtnExit(); return; default: break; } // Everything else is under a lock. TIL til(ts_lock, 0); AssertTILHeld(); if (UNLIKELY(type == THR_START)) { HandleThreadStart(TID(e->tid()), TID(e->info()), (CallStack*)e->pc()); TSanThread::Get(TID(e->tid()))->stats.events[type]++; return; } FlushStateIfOutOfSegments(thr); // Since we have the lock, get some fresh SIDs. thr->GetSomeFreshSids(); switch (type) { case THR_START : CHECK(0); break; break; case SBLOCK_ENTER: if (thr->ignore_reads() && thr->ignore_writes()) break; thr->HandleSblockEnter(e->pc(), /*allow_slow_path=*/true); break; case THR_CREATE_BEFORE: thr->HandleThreadCreateBefore(TID(e->tid()), e->pc()); break; case THR_CREATE_AFTER: thr->HandleThreadCreateAfter(TID(e->tid()), TID(e->info())); break; case THR_FIRST_INSN: HandleThreadFirstInsn(TID(e->tid())); break; case THR_JOIN_AFTER : HandleThreadJoinAfter(e); break; case THR_STACK_TOP : HandleThreadStackTop(e); break; case THR_END : HandleThreadEnd(TID(e->tid())); break; case MALLOC : HandleMalloc(e, false); break; case FREE : HandleFree(e); break; case MMAP : HandleMalloc(e, true); break; // same as MALLOC case MUNMAP : HandleMunmap(e); break; case WRITER_LOCK : thr->HandleLock(e->a(), true); break; case READER_LOCK : thr->HandleLock(e->a(), false); break; case UNLOCK : thr->HandleUnlock(e->a()); break; case UNLOCK_OR_INIT : HandleUnlockOrInit(e); break; case LOCK_CREATE: case LOCK_DESTROY: HandleLockCreateOrDestroy(e); break; case SIGNAL : thr->HandleSignal(e->a()); break; case WAIT : thr->HandleWait(e->a()); break; case CYCLIC_BARRIER_INIT: thr->HandleBarrierInit(e->a(), e->info()); break; case CYCLIC_BARRIER_WAIT_BEFORE : thr->HandleBarrierWaitBefore(e->a()); break; case CYCLIC_BARRIER_WAIT_AFTER : thr->HandleBarrierWaitAfter(e->a()); break; case PCQ_CREATE : HandlePcqCreate(e); break; case PCQ_DESTROY : HandlePcqDestroy(e); break; case PCQ_PUT : HandlePcqPut(e); break; case PCQ_GET : HandlePcqGet(e); break; case EXPECT_RACE : HandleExpectRace(e->a(), (const char*)e->pc(), TID(e->tid())); break; case BENIGN_RACE : HandleBenignRace(e->a(), e->info(), (const char*)e->pc(), TID(e->tid())); break; case FLUSH_EXPECTED_RACES: FlushExpectedRaces(false); break; case EXPECT_RACE_BEGIN: CHECK(g_expecting_races == false); g_expecting_races = true; g_found_races_since_EXPECT_RACE_BEGIN = 0; break; case EXPECT_RACE_END: CHECK(g_expecting_races == true); g_expecting_races = false; if (g_found_races_since_EXPECT_RACE_BEGIN == 0) { int n_errs = GetNumberOfFoundErrors(); SetNumberOfFoundErrors(n_errs + 1); Printf("WARNING: expected race not found.\n"); } break; case HB_LOCK : HandleHBLock(e); break; case NON_HB_LOCK : HandleNonHBLock(e); break; case IGNORE_READS_BEG: HandleIgnore(e, false, true); break; case IGNORE_READS_END: HandleIgnore(e, false, false); break; case IGNORE_WRITES_BEG: HandleIgnore(e, true, true); break; case IGNORE_WRITES_END: HandleIgnore(e, true, false); break; case SET_THREAD_NAME: thr->set_thread_name((const char*)e->a()); break; case SET_LOCK_NAME: { uintptr_t lock_addr = e->a(); const char *name = reinterpret_cast<const char *>(e->info()); Lock *lock = Lock::LookupOrCreate(lock_addr); lock->set_name(name); } break; case PUBLISH_RANGE : HandlePublishRange(e); break; case UNPUBLISH_RANGE : Report("WARNING: ANNOTATE_UNPUBLISH_MEMORY_RANGE is deprecated\n"); break; case TRACE_MEM : HandleTraceMem(e); break; case STACK_TRACE : HandleStackTrace(e); break; case NOOP : CHECK(0); break; // can't happen. case VERBOSITY : e->Print(); G_flags->verbosity = e->info(); break; case FLUSH_STATE : FlushState(TID(e->tid())); break; default : CHECK(0); break; } } private: void ShowProcSelfStatus() { if (G_flags->show_proc_self_status) { string str = ReadFileToString("/proc/self/status", false); if (!str.empty()) { Printf("%s", str.c_str()); } } } void ShowStats() { if (G_flags->show_stats) { G_stats->PrintStats(); G_cache->PrintStorageStats(); } } // PCQ_CREATE, PCQ_DESTROY, PCQ_PUT, PCQ_GET void HandlePcqCreate(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } PCQ pcq; pcq.pcq_addr = e->a(); CHECK(!g_pcq_map->count(e->a())); (*g_pcq_map)[e->a()] = pcq; } void HandlePcqDestroy(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } CHECK(g_pcq_map->count(e->a())); g_pcq_map->erase(e->a()); } void HandlePcqPut(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } PCQ &pcq = (*g_pcq_map)[e->a()]; CHECK(pcq.pcq_addr == e->a()); TSanThread *thread = TSanThread::Get(TID(e->tid())); VTS *vts = thread->segment()->vts()->Clone(); pcq.putters.push_back(vts); thread->NewSegmentForSignal(); } void HandlePcqGet(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } PCQ &pcq = (*g_pcq_map)[e->a()]; CHECK(pcq.pcq_addr == e->a()); CHECK(!pcq.putters.empty()); VTS *putter = pcq.putters.front(); pcq.putters.pop_front(); CHECK(putter); TSanThread *thread = TSanThread::Get(TID(e->tid())); thread->NewSegmentForWait(putter); VTS::Unref(putter); } // PUBLISH_RANGE void HandlePublishRange(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } static int reported_deprecation; reported_deprecation++; if (reported_deprecation < 20) { Report("WARNING: ANNOTATE_PUBLISH_MEMORY_RANGE is deprecated and will not" " be supported in future versions of ThreadSanitizer.\n"); } uintptr_t mem = e->a(); uintptr_t size = e->info(); TID tid(e->tid()); TSanThread *thread = TSanThread::Get(tid); VTS *vts = thread->segment()->vts(); PublishRange(thread, mem, mem + size, vts); thread->NewSegmentForSignal(); // Printf("Publish: [%p, %p)\n", mem, mem+size); } void HandleIgnore(Event *e, bool is_w, bool on) { if (G_flags->verbosity >= 2) { e->Print(); } TSanThread *thread = TSanThread::Get(TID(e->tid())); thread->set_ignore_accesses(is_w, on); } // BENIGN_RACE void HandleBenignRace(uintptr_t ptr, uintptr_t size, const char *descr, TID tid) { TSanThread *thr = TSanThread::Get(tid); if (debug_benign_races) { Printf("T%d: BENIGN_RACE: ptr=%p size=%ld descr='%s'\n", tid.raw(), ptr, size, descr); } // Simply set all 'racey' bits in the shadow state of [ptr, ptr+size). for (uintptr_t p = ptr; p < ptr + size; p++) { CacheLine *line = G_cache->GetLineOrCreateNew(thr, p, __LINE__); CHECK(line); line->racey().Set(CacheLine::ComputeOffset(p)); G_cache->ReleaseLine(thr, p, line, __LINE__); } } // EXPECT_RACE void HandleExpectRace(uintptr_t ptr, const char *descr, TID tid) { ExpectedRace expected_race; expected_race.ptr = ptr; expected_race.size = 1; expected_race.count = 0; expected_race.is_verifiable = !descr || (string(descr).find("UNVERIFIABLE") == string::npos); expected_race.is_nacl_untrusted = !descr || (string(descr).find("NACL_UNTRUSTED") != string::npos); // copy descr (may not have strdup) CHECK(descr); size_t descr_len = strlen(descr); char *d = new char [descr_len + 1]; memcpy(d, descr, descr_len); d[descr_len] = 0; expected_race.description = d; TSanThread *thread = TSanThread::Get(tid); expected_race.pc = thread->GetCallstackEntry(1); G_expected_races_map->InsertInfo(ptr, expected_race); // Flush 'racey' flag for the address CacheLine *cache_line = G_cache->GetLineIfExists(thread, ptr, __LINE__); if (cache_line != NULL) { uintptr_t offset = CacheLine::ComputeOffset(ptr); cache_line->racey().ClearRange(offset, offset + 1); G_cache->ReleaseLine(thread, ptr, cache_line, __LINE__); } if (debug_expected_races) { Printf("T%d: EXPECT_RACE: ptr=%p descr='%s'\n", tid.raw(), ptr, descr); thread->ReportStackTrace(ptr); int i = 0; for (ExpectedRacesMap::iterator it = G_expected_races_map->begin(); it != G_expected_races_map->end(); ++it) { ExpectedRace &x = it->second; Printf(" [%d] %p [0x%lx]\n", i, &x, x.ptr); i++; } } } void HandleStackTrace(Event *e) { TSanThread *thread = TSanThread::Get(TID(e->tid())); e->Print(); thread->ReportStackTrace(); } // HB_LOCK void HandleHBLock(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } Lock *lock = Lock::LookupOrCreate(e->a()); CHECK(lock); lock->set_is_pure_happens_before(true); } // NON_HB_LOCK void HandleNonHBLock(Event *e) { if (G_flags->verbosity >= 2) { e->Print(); } Lock *lock = Lock::LookupOrCreate(e->a()); CHECK(lock); lock->set_is_pure_happens_before(false); } // UNLOCK_OR_INIT // This is a hack to handle posix pthread_spin_unlock which is sometimes // the same symbol as pthread_spin_init. We need to handle unlock as init // if the lock was not seen before or if it is currently unlocked. // TODO(kcc): is there a way to distinguish pthread_spin_init // and pthread_spin_unlock? void HandleUnlockOrInit(Event *e) { TSanThread *thread = TSanThread::Get(TID(e->tid())); if (G_flags->verbosity >= 2) { e->Print(); thread->ReportStackTrace(); } uintptr_t lock_addr = e->a(); Lock *lock = Lock::Lookup(lock_addr); if (lock && lock->wr_held()) { // We know this lock and it is locked. Just unlock it. thread->HandleUnlock(lock_addr); } else { // Never seen this lock or it is currently unlocked. Init it. Lock::Create(lock_addr); } } void HandleLockCreateOrDestroy(Event *e) { TSanThread *thread = TSanThread::Get(TID(e->tid())); uintptr_t lock_addr = e->a(); if (debug_lock) { e->Print(); } if (e->type() == LOCK_CREATE) { Lock::Create(lock_addr); } else { CHECK(e->type() == LOCK_DESTROY); // A locked pthread_mutex_t can not be destroyed but other lock types can. // When destroying a lock, we must unlock it. // If there is a bug in a program when someone attempts to unlock // a destoyed lock, we are likely to fail in an assert. // // We do not unlock-on-destroy after main() has exited. // This is because global Mutex objects may be desctructed while threads // holding them are still running. Urgh... Lock *lock = Lock::Lookup(lock_addr); // If the lock is not found, report an error. if (lock == NULL) { ThreadSanitizerInvalidLockReport *report = new ThreadSanitizerInvalidLockReport; report->type = ThreadSanitizerReport::INVALID_LOCK; report->tid = TID(e->tid()); report->lock_addr = lock_addr; report->stack_trace = thread->CreateStackTrace(); ThreadSanitizerPrintReport(report); return; } if (lock->wr_held() || lock->rd_held()) { if (G_flags->unlock_on_mutex_destroy && !g_has_exited_main) { thread->HandleUnlock(lock_addr); } } thread->HandleForgetSignaller(lock_addr); Lock::Destroy(lock_addr); } } void HandleTraceMem(Event *e) { if (G_flags->trace_level == 0) return; TID tid(e->tid()); TSanThread *thr = TSanThread::Get(TID(e->tid())); uintptr_t a = e->a(); CacheLine *line = G_cache->GetLineOrCreateNew(thr, a, __LINE__); uintptr_t offset = CacheLine::ComputeOffset(a); line->traced().Set(offset); G_cache->ReleaseLine(thr, a, line, __LINE__); if (G_flags->verbosity >= 2) e->Print(); } INLINE void RefAndUnrefTwoSegSetPairsIfDifferent(SSID new_ssid1, SSID old_ssid1, SSID new_ssid2, SSID old_ssid2) { bool recycle_1 = new_ssid1 != old_ssid1, recycle_2 = new_ssid2 != old_ssid2; if (recycle_1 && !new_ssid1.IsEmpty()) { SegmentSet::Ref(new_ssid1, "RefAndUnrefTwoSegSetPairsIfDifferent"); } if (recycle_2 && !new_ssid2.IsEmpty()) { SegmentSet::Ref(new_ssid2, "RefAndUnrefTwoSegSetPairsIfDifferent"); } if (recycle_1 && !old_ssid1.IsEmpty()) { SegmentSet::Unref(old_ssid1, "RefAndUnrefTwoSegSetPairsIfDifferent"); } if (recycle_2 && !old_ssid2.IsEmpty()) { SegmentSet::Unref(old_ssid2, "RefAndUnrefTwoSegSetPairsIfDifferent"); } } // return true if the current pair of read/write segment sets // describes a race. bool NOINLINE CheckIfRace(SSID rd_ssid, SSID wr_ssid) { int wr_ss_size = SegmentSet::Size(wr_ssid); int rd_ss_size = SegmentSet::Size(rd_ssid); DCHECK(wr_ss_size >= 2 || (wr_ss_size >= 1 && rd_ss_size >= 1)); // check all write-write pairs for (int w1 = 0; w1 < wr_ss_size; w1++) { SID w1_sid = SegmentSet::GetSID(wr_ssid, w1, __LINE__); Segment *w1_seg = Segment::Get(w1_sid); LSID w1_ls = w1_seg->lsid(true); for (int w2 = w1 + 1; w2 < wr_ss_size; w2++) { DCHECK(wr_ssid.IsTuple()); SegmentSet *ss = SegmentSet::Get(wr_ssid); LSID w2_ls = Segment::Get(ss->GetSID(w2))->lsid(true); if (LockSet::IntersectionIsEmpty(w1_ls, w2_ls)) { return true; } else { // May happen only if the locks in the intersection are hybrid locks. DCHECK(LockSet::HasNonPhbLocks(w1_ls) && LockSet::HasNonPhbLocks(w2_ls)); } } // check all write-read pairs for (int r = 0; r < rd_ss_size; r++) { SID r_sid = SegmentSet::GetSID(rd_ssid, r, __LINE__); Segment *r_seg = Segment::Get(r_sid); LSID r_ls = r_seg->lsid(false); if (Segment::HappensBeforeOrSameThread(w1_sid, r_sid)) continue; if (LockSet::IntersectionIsEmpty(w1_ls, r_ls)) { return true; } else { // May happen only if the locks in the intersection are hybrid locks. DCHECK(LockSet::HasNonPhbLocks(w1_ls) && LockSet::HasNonPhbLocks(r_ls)); } } } return false; } // New experimental state machine. // Set *res to the new state. // Return true if the new state is race. bool INLINE MemoryStateMachine(ShadowValue old_sval, TSanThread *thr, bool is_w, ShadowValue *res) { ShadowValue new_sval; SID cur_sid = thr->sid(); DCHECK(cur_sid.valid()); if (UNLIKELY(old_sval.IsNew())) { // We see this memory for the first time. DCHECK(cur_sid.valid()); if (is_w) { new_sval.set(SSID(0), SSID(cur_sid)); } else { new_sval.set(SSID(cur_sid), SSID(0)); } *res = new_sval; return false; } SSID old_rd_ssid = old_sval.rd_ssid(); SSID old_wr_ssid = old_sval.wr_ssid(); SSID new_rd_ssid(0); SSID new_wr_ssid(0); if (is_w) { new_rd_ssid = SegmentSet::RemoveSegmentFromSS(old_rd_ssid, cur_sid); new_wr_ssid = SegmentSet::AddSegmentToSS(old_wr_ssid, cur_sid); } else { if (SegmentSet::Contains(old_wr_ssid, cur_sid)) { // cur_sid is already in old_wr_ssid, no change to SSrd is required. new_rd_ssid = old_rd_ssid; } else { new_rd_ssid = SegmentSet::AddSegmentToSS(old_rd_ssid, cur_sid); } new_wr_ssid = old_wr_ssid; } if (UNLIKELY(G_flags->sample_events > 0)) { if (new_rd_ssid.IsTuple() || new_wr_ssid.IsTuple()) { static EventSampler sampler; sampler.Sample(thr, "HasTupleSS", false); } } new_sval.set(new_rd_ssid, new_wr_ssid); *res = new_sval; if (new_sval == old_sval) return false; if (new_wr_ssid.IsTuple() || (!new_wr_ssid.IsEmpty() && !new_rd_ssid.IsEmpty())) { return CheckIfRace(new_rd_ssid, new_wr_ssid); } return false; } // Fast path implementation for the case when we stay in the same thread. // In this case we don't need to call HappensBefore(), deal with // Tuple segment sets and check for race. // If this function returns true, the ShadowValue *new_sval is updated // in the same way as MemoryStateMachine() would have done it. Just faster. INLINE bool MemoryStateMachineSameThread(bool is_w, ShadowValue old_sval, TSanThread *thr, ShadowValue *new_sval) { #define MSM_STAT(i) do { if (DEBUG_MODE) \ thr->stats.msm_branch_count[i]++; } while ((void)0, 0) SSID rd_ssid = old_sval.rd_ssid(); SSID wr_ssid = old_sval.wr_ssid(); SID cur_sid = thr->sid(); TID tid = thr->tid(); if (rd_ssid.IsEmpty()) { if (wr_ssid.IsSingleton()) { // *** CASE 01 ***: rd_ssid == 0, wr_ssid == singleton SID wr_sid = wr_ssid.GetSingleton(); if (wr_sid == cur_sid) { // --- w/r: {0, cur} => {0, cur} MSM_STAT(1); // no op return true; } if (tid == Segment::Get(wr_sid)->tid()) { // same thread, but the segments are different. DCHECK(cur_sid != wr_sid); if (is_w) { // -------------- w: {0, wr} => {0, cur} MSM_STAT(2); new_sval->set(SSID(0), SSID(cur_sid)); thr->AddDeadSid(wr_sid, "FastPath01"); } else { // -------------- r: {0, wr} => {cur, wr} MSM_STAT(3); new_sval->set(SSID(cur_sid), wr_ssid); } Segment::Ref(cur_sid, "FastPath01"); return true; } } else if (wr_ssid.IsEmpty()) { // *** CASE 00 ***: rd_ssid == 0, wr_ssid == 0 if (is_w) { // -------------- w: {0, 0} => {0, cur} MSM_STAT(4); new_sval->set(SSID(0), SSID(cur_sid)); } else { // -------------- r: {0, 0} => {cur, 0} MSM_STAT(5); new_sval->set(SSID(cur_sid), SSID(0)); } Segment::Ref(cur_sid, "FastPath00"); return true; } } else if (rd_ssid.IsSingleton()) { SID rd_sid = rd_ssid.GetSingleton(); if (wr_ssid.IsEmpty()) { // *** CASE 10 ***: rd_ssid == singleton, wr_ssid == 0 if (rd_sid == cur_sid) { // same segment. if (is_w) { // -------------- w: {cur, 0} => {0, cur} MSM_STAT(6); new_sval->set(SSID(0), SSID(cur_sid)); } else { // -------------- r: {cur, 0} => {cur, 0} MSM_STAT(7); // no op } return true; } if (tid == Segment::Get(rd_sid)->tid()) { // same thread, but the segments are different. DCHECK(cur_sid != rd_sid); if (is_w) { // -------------- w: {rd, 0} => {0, cur} MSM_STAT(8); new_sval->set(SSID(0), SSID(cur_sid)); } else { // -------------- r: {rd, 0} => {cur, 0} MSM_STAT(9); new_sval->set(SSID(cur_sid), SSID(0)); } Segment::Ref(cur_sid, "FastPath10"); thr->AddDeadSid(rd_sid, "FastPath10"); return true; } } else if (wr_ssid.IsSingleton()){ // *** CASE 11 ***: rd_ssid == singleton, wr_ssid == singleton DCHECK(rd_ssid.IsSingleton()); SID wr_sid = wr_ssid.GetSingleton(); DCHECK(wr_sid != rd_sid); // By definition of ShadowValue. if (cur_sid == rd_sid) { if (tid == Segment::Get(wr_sid)->tid()) { if (is_w) { // -------------- w: {cur, wr} => {0, cur} MSM_STAT(10); new_sval->set(SSID(0), SSID(cur_sid)); thr->AddDeadSid(wr_sid, "FastPath11"); } else { // -------------- r: {cur, wr} => {cur, wr} MSM_STAT(11); // no op } return true; } } else if (cur_sid == wr_sid){ if (tid == Segment::Get(rd_sid)->tid()) { if (is_w) { // -------------- w: {rd, cur} => {rd, cur} MSM_STAT(12); // no op } else { // -------------- r: {rd, cur} => {0, cur} MSM_STAT(13); new_sval->set(SSID(0), SSID(cur_sid)); thr->AddDeadSid(rd_sid, "FastPath11"); } return true; } } else if (tid == Segment::Get(rd_sid)->tid() && tid == Segment::Get(wr_sid)->tid()) { if (is_w) { // -------------- w: {rd, wr} => {0, cur} MSM_STAT(14); new_sval->set(SSID(0), SSID(cur_sid)); thr->AddDeadSid(wr_sid, "FastPath11"); } else { // -------------- r: {rd, wr} => {cur, wr} MSM_STAT(15); new_sval->set(SSID(cur_sid), wr_ssid); } thr->AddDeadSid(rd_sid, "FastPath11"); Segment::Ref(cur_sid, "FastPath11"); return true; } } } MSM_STAT(0); return false; #undef MSM_STAT } // return false if we were not able to complete the task (fast_path_only). INLINE bool HandleMemoryAccessHelper(bool is_w, CacheLine *cache_line, uintptr_t addr, uintptr_t size, uintptr_t pc, TSanThread *thr, bool fast_path_only) { DCHECK((addr & (size - 1)) == 0); // size-aligned. uintptr_t offset = CacheLine::ComputeOffset(addr); ShadowValue old_sval; ShadowValue *sval_p = NULL; if (UNLIKELY(!cache_line->has_shadow_value().Get(offset))) { sval_p = cache_line->AddNewSvalAtOffset(offset); DCHECK(sval_p->IsNew()); } else { sval_p = cache_line->GetValuePointer(offset); } old_sval = *sval_p; bool res = false; bool fast_path_ok = MemoryStateMachineSameThread( is_w, old_sval, thr, sval_p); if (fast_path_ok) { res = true; } else if (fast_path_only) { res = false; } else { bool is_published = cache_line->published().Get(offset); // We check only the first bit for publishing, oh well. if (UNLIKELY(is_published)) { const VTS *signaller_vts = GetPublisherVTS(addr); CHECK(signaller_vts); thr->NewSegmentForWait(signaller_vts); } bool is_race = MemoryStateMachine(old_sval, thr, is_w, sval_p); // Check for race. if (UNLIKELY(is_race)) { if (thr->ShouldReportRaces()) { if (G_flags->report_races && !cache_line->racey().Get(offset)) { reports_.AddReport(thr, pc, is_w, addr, size, old_sval, *sval_p, is_published); } cache_line->racey().SetRange(offset, offset + size); } } // Ref/Unref segments RefAndUnrefTwoSegSetPairsIfDifferent(sval_p->rd_ssid(), old_sval.rd_ssid(), sval_p->wr_ssid(), old_sval.wr_ssid()); res = true; } if (DEBUG_MODE && !fast_path_only) { // check that the SSIDs/SIDs in the new sval have sane ref counters. CHECK(!sval_p->wr_ssid().IsEmpty() || !sval_p->rd_ssid().IsEmpty()); for (int i = 0; i < 2; i++) { SSID ssid = i ? sval_p->rd_ssid() : sval_p->wr_ssid(); if (ssid.IsEmpty()) continue; if (ssid.IsSingleton()) { // singleton segment should have ref count > 0. SID sid = ssid.GetSingleton(); Segment *seg = Segment::Get(sid); (void)seg; CHECK(seg->ref_count() > 0); if (sid == thr->sid()) { // if this is the current seg, ref count should be > 1. CHECK(seg->ref_count() > 1); } } else { SegmentSet *sset = SegmentSet::Get(ssid); (void)sset; CHECK(sset->ref_count() > 0); } } } return res; } // return false if we were not able to complete the task (fast_path_only). INLINE bool HandleAccessGranularityAndExecuteHelper( CacheLine *cache_line, TSanThread *thr, uintptr_t addr, MopInfo *mop, bool has_expensive_flags, bool fast_path_only) { size_t size = mop->size(); uintptr_t pc = mop->pc(); bool is_w = mop->is_write(); uintptr_t a = addr; uintptr_t b = 0; uintptr_t off = CacheLine::ComputeOffset(a); uint16_t *granularity_mask = cache_line->granularity_mask(off); uint16_t gr = *granularity_mask; // Can't do split/join on the fast path, bacause it involves segment set // reference count manipulation that is not thread-safe. if (size == 8 && (off & 7) == 0) { if (!gr) { *granularity_mask = gr = 1; // 0000000000000001 } if (GranularityIs8(off, gr)) { if (has_expensive_flags) thr->stats.n_fast_access8++; cache_line->DebugTrace(off, __FUNCTION__, __LINE__); goto one_call; } else { if (fast_path_only) return false; if (has_expensive_flags) thr->stats.n_slow_access8++; cache_line->Join_1_to_2(off); cache_line->Join_1_to_2(off + 2); cache_line->Join_1_to_2(off + 4); cache_line->Join_1_to_2(off + 6); cache_line->Join_2_to_4(off); cache_line->Join_2_to_4(off + 4); cache_line->Join_4_to_8(off); goto slow_path; } } else if (size == 4 && (off & 3) == 0) { if (!gr) { *granularity_mask = gr = 3 << 1; // 0000000000000110 } if (GranularityIs4(off, gr)) { if (has_expensive_flags) thr->stats.n_fast_access4++; cache_line->DebugTrace(off, __FUNCTION__, __LINE__); goto one_call; } else { if (fast_path_only) return false; if (has_expensive_flags) thr->stats.n_slow_access4++; cache_line->Split_8_to_4(off); cache_line->Join_1_to_2(off); cache_line->Join_1_to_2(off + 2); cache_line->Join_2_to_4(off); goto slow_path; } } else if (size == 2 && (off & 1) == 0) { if (!gr) { *granularity_mask = gr = 15 << 3; // 0000000001111000 } if (GranularityIs2(off, gr)) { if (has_expensive_flags) thr->stats.n_fast_access2++; cache_line->DebugTrace(off, __FUNCTION__, __LINE__); goto one_call; } else { if (fast_path_only) return false; if (has_expensive_flags) thr->stats.n_slow_access2++; cache_line->Split_8_to_4(off); cache_line->Split_4_to_2(off); cache_line->Join_1_to_2(off); goto slow_path; } } else if (size == 1) { if (!gr) { *granularity_mask = gr = 255 << 7; // 0111111110000000 } if (GranularityIs1(off, gr)) { if (has_expensive_flags) thr->stats.n_fast_access1++; cache_line->DebugTrace(off, __FUNCTION__, __LINE__); goto one_call; } else { if (fast_path_only) return false; if (has_expensive_flags) thr->stats.n_slow_access1++; cache_line->Split_8_to_4(off); cache_line->Split_4_to_2(off); cache_line->Split_2_to_1(off); goto slow_path; } } else { if (fast_path_only) return false; if (has_expensive_flags) thr->stats.n_very_slow_access++; // Very slow: size is not 1,2,4,8 or address is unaligned. // Handle this access as a series of 1-byte accesses, but only // inside the current cache line. // TODO(kcc): do we want to handle the next cache line as well? b = a + mop->size(); uintptr_t max_x = min(b, CacheLine::ComputeNextTag(a)); for (uintptr_t x = a; x < max_x; x++) { off = CacheLine::ComputeOffset(x); DCHECK(CacheLine::ComputeTag(x) == cache_line->tag()); uint16_t *granularity_mask = cache_line->granularity_mask(off); if (!*granularity_mask) { *granularity_mask = 1; } cache_line->DebugTrace(off, __FUNCTION__, __LINE__); cache_line->Split_8_to_4(off); cache_line->Split_4_to_2(off); cache_line->Split_2_to_1(off); if (!HandleMemoryAccessHelper(is_w, cache_line, x, 1, pc, thr, false)) return false; } return true; } slow_path: if (fast_path_only) return false; DCHECK(cache_line); DCHECK(size == 1 || size == 2 || size == 4 || size == 8); DCHECK((addr & (size - 1)) == 0); // size-aligned. gr = *granularity_mask; CHECK(gr); // size is one of 1, 2, 4, 8; address is size-aligned, but the granularity // is different. b = a + mop->size(); for (uintptr_t x = a; x < b;) { if (has_expensive_flags) thr->stats.n_access_slow_iter++; off = CacheLine::ComputeOffset(x); cache_line->DebugTrace(off, __FUNCTION__, __LINE__); size_t s = 0; // How many bytes are we going to access? if (GranularityIs8(off, gr)) s = 8; else if(GranularityIs4(off, gr)) s = 4; else if(GranularityIs2(off, gr)) s = 2; else s = 1; if (!HandleMemoryAccessHelper(is_w, cache_line, x, s, pc, thr, false)) return false; x += s; } return true; one_call: return HandleMemoryAccessHelper(is_w, cache_line, addr, size, pc, thr, fast_path_only); } INLINE bool IsTraced(CacheLine *cache_line, uintptr_t addr, bool has_expensive_flags) { if (!has_expensive_flags) return false; if (G_flags->trace_level == 0) return false; DCHECK(cache_line); uintptr_t off = CacheLine::ComputeOffset(addr); if (cache_line->traced().Get(off)) { return true; } else if (addr == G_flags->trace_addr) { return true; } return false; } void DoTrace(TSanThread *thr, uintptr_t addr, MopInfo *mop, bool need_locking) { size_t size = mop->size(); uintptr_t pc = mop->pc(); TIL til(ts_lock, 1, need_locking); for (uintptr_t x = addr; x < addr + size; x++) { uintptr_t off = CacheLine::ComputeOffset(x); CacheLine *cache_line = G_cache->GetLineOrCreateNew(thr, x, __LINE__); ShadowValue *sval_p = cache_line->GetValuePointer(off); if (cache_line->has_shadow_value().Get(off) != 0) { bool is_published = cache_line->published().Get(off); Printf("TRACE: T%d/S%d %s[%d] addr=%p sval: %s%s; line=%p P=%s\n", raw_tid(thr), thr->sid().raw(), mop->is_write() ? "wr" : "rd", size, addr, sval_p->ToString().c_str(), is_published ? " P" : "", cache_line, cache_line->published().Empty() ? "0" : cache_line->published().ToString().c_str()); thr->ReportStackTrace(pc); } G_cache->ReleaseLine(thr, x, cache_line, __LINE__); } } #if TS_SERIALIZED == 1 INLINE // TODO(kcc): this can also be made NOINLINE later. #else NOINLINE #endif void HandleMemoryAccessSlowLocked(TSanThread *thr, uintptr_t addr, MopInfo *mop, bool has_expensive_flags, bool need_locking) { AssertTILHeld(); DCHECK(thr->lsid(false) == thr->segment()->lsid(false)); DCHECK(thr->lsid(true) == thr->segment()->lsid(true)); thr->FlushDeadSids(); if (TS_SERIALIZED == 0) { // In serialized version this is the hotspot, so grab fresh SIDs // only in non-serial variant. thr->GetSomeFreshSids(); } CacheLine *cache_line = G_cache->GetLineOrCreateNew(thr, addr, __LINE__); HandleAccessGranularityAndExecuteHelper(cache_line, thr, addr, mop, has_expensive_flags, /*fast_path_only=*/false); bool tracing = IsTraced(cache_line, addr, has_expensive_flags); G_cache->ReleaseLine(thr, addr, cache_line, __LINE__); cache_line = NULL; // just in case. if (has_expensive_flags) { if (tracing) { DoTrace(thr, addr, mop, /*need_locking=*/false); } if (G_flags->sample_events > 0) { const char *type = "SampleMemoryAccess"; static EventSampler sampler; sampler.Sample(thr, type, false); } } } INLINE bool HandleMemoryAccessInternal(TSanThread *thr, uintptr_t *sblock_pc, uintptr_t addr, MopInfo *mop, bool has_expensive_flags, bool need_locking) { # define INC_STAT(stat) \ do { if (has_expensive_flags) (stat)++; } while ((void)0, 0) if (TS_ATOMICITY && G_flags->atomicity) { HandleMemoryAccessForAtomicityViolationDetector(thr, addr, mop); return false; } DCHECK(mop->size() > 0); DCHECK(thr->is_running()); DCHECK(!thr->ignore_reads() || !thr->ignore_writes()); // We do not check and ignore stack now. // On unoptimized binaries this would give ~10% speedup if ignore_stack==true, // but if --ignore_stack==false this would cost few extra insns. // On optimized binaries ignoring stack gives nearly nothing. // if (thr->IgnoreMemoryIfInStack(addr)) return; CacheLine *cache_line = NULL; INC_STAT(thr->stats.memory_access_sizes[mop->size() <= 16 ? mop->size() : 17 ]); INC_STAT(thr->stats.events[mop->is_write() ? WRITE : READ]); if (has_expensive_flags) { thr->stats.access_to_first_1g += (addr >> 30) == 0; thr->stats.access_to_first_2g += (addr >> 31) == 0; thr->stats.access_to_first_4g += ((uint64_t)addr >> 32) == 0; } int locked_access_case = 0; if (need_locking) { // The fast (unlocked) path. if (thr->HasRoomForDeadSids()) { // Acquire a line w/o locks. cache_line = G_cache->TryAcquireLine(thr, addr, __LINE__); if (!Cache::LineIsNullOrLocked(cache_line)) { // The line is not empty or locked -- check the tag. if (cache_line->tag() == CacheLine::ComputeTag(addr)) { // The line is ours and non-empty -- fire the fast path. if (thr->HandleSblockEnter(*sblock_pc, /*allow_slow_path=*/false)) { *sblock_pc = 0; // don't do SblockEnter any more. bool res = HandleAccessGranularityAndExecuteHelper( cache_line, thr, addr, mop, has_expensive_flags, /*fast_path_only=*/true); bool traced = IsTraced(cache_line, addr, has_expensive_flags); // release the line. G_cache->ReleaseLine(thr, addr, cache_line, __LINE__); if (res && has_expensive_flags && traced) { DoTrace(thr, addr, mop, /*need_locking=*/true); } if (res) { INC_STAT(thr->stats.unlocked_access_ok); // fast path succeded, we are done. return false; } else { locked_access_case = 1; } } else { // we were not able to handle SblockEnter. G_cache->ReleaseLine(thr, addr, cache_line, __LINE__); locked_access_case = 2; } } else { locked_access_case = 3; // The line has a wrong tag. G_cache->ReleaseLine(thr, addr, cache_line, __LINE__); } } else if (cache_line == NULL) { locked_access_case = 4; // We grabbed the cache slot but it is empty, release it. G_cache->ReleaseLine(thr, addr, cache_line, __LINE__); } else { locked_access_case = 5; } } else { locked_access_case = 6; } } else { locked_access_case = 7; } if (need_locking) { INC_STAT(thr->stats.locked_access[locked_access_case]); } // Everything below goes under a lock. TIL til(ts_lock, 2, need_locking); thr->HandleSblockEnter(*sblock_pc, /*allow_slow_path=*/true); *sblock_pc = 0; // don't do SblockEnter any more. HandleMemoryAccessSlowLocked(thr, addr, mop, has_expensive_flags, need_locking); return true; #undef INC_STAT } void HandleMemoryAccessForAtomicityViolationDetector(TSanThread *thr, uintptr_t addr, MopInfo *mop) { CHECK(G_flags->atomicity); TID tid = thr->tid(); if (thr->MemoryIsInStack(addr)) return; LSID wr_lsid = thr->lsid(0); LSID rd_lsid = thr->lsid(1); if (wr_lsid.raw() == 0 && rd_lsid.raw() == 0) { thr->increment_n_mops_since_start(); return; } // uint64_t combined_lsid = wr_lsid.raw(); // combined_lsid = (combined_lsid << 32) | rd_lsid.raw(); // if (combined_lsid == 0) return; // Printf("Era=%d T%d %s a=%p pc=%p in_stack=%d %s\n", g_lock_era, // tid.raw(), is_w ? "W" : "R", addr, pc, thr->MemoryIsInStack(addr), // PcToRtnNameAndFilePos(pc).c_str()); BitSet *range_set = thr->lock_era_access_set(mop->is_write()); // Printf("era %d T%d access under lock pc=%p addr=%p size=%p w=%d\n", // g_lock_era, tid.raw(), pc, addr, size, is_w); range_set->Add(addr, addr + mop->size()); // Printf(" %s\n", range_set->ToString().c_str()); } // MALLOC void HandleMalloc(Event *e, bool is_mmap) { ScopedMallocCostCenter cc("HandleMalloc"); TID tid(e->tid()); uintptr_t a = e->a(); uintptr_t size = e->info(); if (a == 0) return; #if defined(__GNUC__) && __WORDSIZE == 64 // If we are allocating a huge piece of memory, // don't handle it because it is too slow. // TODO(kcc): this is a workaround for NaCl. May need to fix it cleaner. const uint64_t G84 = (1ULL << 32) * 21; // 84G. if (size >= G84) { return; } #endif TSanThread *thr = TSanThread::Get(tid); thr->NewSegmentForMallocEvent(); uintptr_t b = a + size; CHECK(a <= b); ClearMemoryState(thr, a, b); // update heap_map HeapInfo info; info.ptr = a; info.size = size; info.sid = thr->sid(); Segment::Ref(info.sid, __FUNCTION__); if (debug_malloc) { Printf("T%d MALLOC: %p [%p %p) %s %s\n%s\n", tid.raw(), size, a, a+size, Segment::ToString(thr->sid()).c_str(), thr->segment()->vts()->ToString().c_str(), info.StackTraceString().c_str()); } // CHECK(!G_heap_map->count(a)); // we may have two calls // to AnnotateNewMemory. G_heap_map->InsertInfo(a, info); if (is_mmap) { // Mmap may be used for thread stack, so we should keep the mmap info // when state is flushing. ThreadStackInfo ts_info; ts_info.ptr = a; ts_info.size = size; G_thread_stack_map->InsertInfo(a, ts_info); } } void ImitateWriteOnFree(TSanThread *thr, uintptr_t a, uintptr_t size, uintptr_t pc) { // Handle the memory deletion as a write, but don't touch all // the memory if there is too much of it, limit with the first 1K. if (size && G_flags->free_is_write && !global_ignore) { const uintptr_t kMaxWriteSizeOnFree = 2048; uintptr_t write_size = min(kMaxWriteSizeOnFree, size); uintptr_t step = sizeof(uintptr_t); // We simulate 4- or 8-byte accesses to make analysis faster. for (uintptr_t i = 0; i < write_size; i += step) { uintptr_t this_size = write_size - i >= step ? step : write_size - i; HandleMemoryAccess(thr, pc, a + i, this_size, /*is_w=*/true, /*need_locking*/false); } } } // FREE void HandleFree(Event *e) { TID tid(e->tid()); TSanThread *thr = TSanThread::Get(tid); uintptr_t a = e->a(); if (debug_free) { e->Print(); thr->ReportStackTrace(e->pc()); } if (a == 0) return; HeapInfo *info = G_heap_map->GetInfo(a); if (!info || info->ptr != a) return; uintptr_t size = info->size; uintptr_t pc = e->pc(); ImitateWriteOnFree(thr, a, size, pc); // update G_heap_map CHECK(info->ptr == a); Segment::Unref(info->sid, __FUNCTION__); ClearMemoryState(thr, a, a + size); G_heap_map->EraseInfo(a); // We imitate a Write event again, in case there will be use-after-free. // We also need to create a new sblock so that the previous stack trace // has free() in it. if (G_flags->keep_history && G_flags->free_is_write) { thr->HandleSblockEnter(pc, /*allow_slow_path*/true); } ImitateWriteOnFree(thr, a, size, pc); } void HandleMunmap(Event *e) { // TODO(glider): at the moment we handle only munmap()s of single mmap()ed // regions. The correct implementation should handle arbitrary munmap()s // that may carve the existing mappings or split them into two parts. // It should also be possible to munmap() several mappings at a time. uintptr_t a = e->a(); if (a == 0) return; HeapInfo *h_info = G_heap_map->GetInfo(a); uintptr_t size = e->info(); if (h_info && h_info->ptr == a && h_info->size == size) { // TODO(glider): we may want to handle memory deletion and call // Segment::Unref for all the unmapped memory. Segment::Unref(h_info->sid, __FUNCTION__); G_heap_map->EraseRange(a, a + size); } ThreadStackInfo *ts_info = G_thread_stack_map->GetInfo(a); if (ts_info && ts_info->ptr == a && ts_info->size == size) G_thread_stack_map->EraseRange(a, a + size); } void HandleThreadStart(TID child_tid, TID parent_tid, CallStack *call_stack) { // Printf("HandleThreadStart: tid=%d parent_tid=%d pc=%lx pid=%d\n", // child_tid.raw(), parent_tid.raw(), pc, getpid()); VTS *vts = NULL; StackTrace *creation_context = NULL; if (child_tid == TID(0)) { // main thread, we are done. vts = VTS::CreateSingleton(child_tid); } else if (!parent_tid.valid()) { TSanThread::StopIgnoringAccessesInT0BecauseNewThreadStarted(); Report("INFO: creating thread T%d w/o a parent\n", child_tid.raw()); vts = VTS::CreateSingleton(child_tid); } else { TSanThread::StopIgnoringAccessesInT0BecauseNewThreadStarted(); TSanThread *parent = TSanThread::Get(parent_tid); CHECK(parent); parent->HandleChildThreadStart(child_tid, &vts, &creation_context); } if (!call_stack) { call_stack = new CallStack(); } TSanThread *new_thread = new TSanThread(child_tid, parent_tid, vts, creation_context, call_stack); CHECK(new_thread == TSanThread::Get(child_tid)); if (child_tid == TID(0)) { new_thread->set_ignore_all_accesses(true); // until a new thread comes. } } // Executes before the first instruction of the thread but after the thread // has been set up (e.g. the stack is in place). void HandleThreadFirstInsn(TID tid) { // TODO(kcc): get rid of this once we find out how to get the T0's stack. if (tid == TID(0)) { uintptr_t stack_min(0), stack_max(0); GetThreadStack(tid.raw(), &stack_min, &stack_max); TSanThread *thr = TSanThread::Get(tid); thr->SetStack(stack_min, stack_max); ClearMemoryState(thr, thr->min_sp(), thr->max_sp()); } } // THR_STACK_TOP void HandleThreadStackTop(Event *e) { TID tid(e->tid()); TSanThread *thr = TSanThread::Get(tid); // Stack grows from bottom up. uintptr_t sp = e->a(); uintptr_t sp_min = 0, sp_max = 0; uintptr_t stack_size_if_known = e->info(); ThreadStackInfo *stack_info; if (stack_size_if_known) { sp_min = sp - stack_size_if_known; sp_max = sp; } else if (NULL != (stack_info = G_thread_stack_map->GetInfo(sp))) { if (debug_thread) { Printf("T%d %s: %p\n%s\n", e->tid(), __FUNCTION__, sp, reports_.DescribeMemory(sp).c_str()); } sp_min = stack_info->ptr; sp_max = stack_info->ptr + stack_info->size; } if (debug_thread) { Printf("T%d SP: %p [%p %p), size=%ldK\n", e->tid(), sp, sp_min, sp_max, (sp_max - sp_min) >> 10); } if (sp_min < sp_max) { CHECK((sp_max - sp_min) >= 8 * 1024); // stay sane. CHECK((sp_max - sp_min) < 128 * 1024 * 1024); // stay sane. ClearMemoryState(thr, sp_min, sp_max); thr->SetStack(sp_min, sp_max); } } // THR_END void HandleThreadEnd(TID tid) { TSanThread *thr = TSanThread::Get(tid); // Add the thread-local stats to global stats. G_stats->Add(thr->stats); thr->stats.Clear(); // Printf("HandleThreadEnd: %d\n", tid.raw()); if (tid != TID(0)) { TSanThread *child = TSanThread::Get(tid); child->HandleThreadEnd(); if (debug_thread) { Printf("T%d: THR_END : %s %s\n", tid.raw(), Segment::ToString(child->sid()).c_str(), child->vts()->ToString().c_str()); } ClearMemoryState(thr, child->min_sp(), child->max_sp()); } else { reports_.SetProgramFinished(); } if (g_so_far_only_one_thread == false && (thr->ignore_reads() || thr->ignore_writes())) { Report("WARNING: T%d ended while at least one 'ignore' bit is set: " "ignore_wr=%d ignore_rd=%d\n", tid.raw(), thr->ignore_reads(), thr->ignore_writes()); for (int i = 0; i < 2; i++) { StackTrace *context = thr->GetLastIgnoreContext(i); if (context) { Report("Last ignore_%s call was here: \n%s\n", i ? "wr" : "rd", context->ToString().c_str()); } } if (G_flags->save_ignore_context == false) { Report("Rerun with --save_ignore_context to see where " "IGNORE_END is missing\n"); } } ShowProcSelfStatus(); } // THR_JOIN_AFTER void HandleThreadJoinAfter(Event *e) { TID tid(e->tid()); TSanThread *parent_thr = TSanThread::Get(tid); VTS *vts_at_exit = NULL; TID child_tid = parent_thr->HandleThreadJoinAfter(&vts_at_exit, TID(e->a())); CHECK(vts_at_exit); CHECK(parent_thr->sid().valid()); Segment::AssertLive(parent_thr->sid(), __LINE__); parent_thr->NewSegmentForWait(vts_at_exit); if (debug_thread) { Printf("T%d: THR_JOIN_AFTER T%d : %s\n", tid.raw(), child_tid.raw(), parent_thr->vts()->ToString().c_str()); } } public: // TODO(kcc): merge this into Detector class. (?) ReportStorage reports_; void SetUnwindCallback(ThreadSanitizerUnwindCallback cb) { reports_.SetUnwindCallback(cb); } }; static Detector *G_detector; void TSanThread::HandleAtomicMop(uintptr_t a, uintptr_t pc, tsan_atomic_op op, tsan_memory_order mo, size_t size) { if (op == tsan_atomic_op_fence) return; bool const is_store = (op != tsan_atomic_op_load); CHECK(inside_atomic_op_ >= 0); if (mo != tsan_memory_order_natomic) inside_atomic_op_ += 1; MopInfo mop (pc, size, is_store, true); G_detector->HandleTrace(this, &mop, 1, pc, &a, false); if (mo != tsan_memory_order_natomic) inside_atomic_op_ -= 1; CHECK(inside_atomic_op_ >= 0); } // -------- Flags ------------------------- {{{1 const char *usage_str = "Usage:\n" " %s [options] program_to_test [program's options]\n" "See %s for details\n"; void ThreadSanitizerPrintUsage() { Printf(usage_str, G_flags->tsan_program_name.c_str(), G_flags->tsan_url.c_str()); } static void ReportUnknownFlagAndExit(const string &str) { Printf("Unknown flag or flag value: %s\n", str.c_str()); ThreadSanitizerPrintUsage(); exit(1); } // if arg and flag match, return true // and set 'val' to the substring of arg after '='. static bool FlagNameMatch(const string &arg, const string &flag, string *val) { string f = string("--") + flag; if (arg.size() < f.size()) return false; for (size_t i = 0; i < f.size(); i++) { // '-' must match '-' // '_' may match '_' or '-' if (f[i] == '_') { if (arg[i] != '-' && arg[i] != '_') return false; } else { if (f[i] != arg[i]) return false; } } if (arg.size() == f.size()) { *val = ""; return true; } if (arg[f.size()] != '=') return false; *val = arg.substr(f.size() + 1); return true; } static int FindBoolFlag(const char *name, bool default_val, vector<string> *args, bool *retval) { int res = 0; *retval = default_val; bool cont = false; do { cont = false; vector<string>::iterator it = args->begin(); for (; it != args->end(); ++it) { string &str = *it; string flag_value; if (!FlagNameMatch(str, name, &flag_value)) continue; if (flag_value == "") *retval = true; else if (flag_value == "1") *retval = true; else if (flag_value == "true") *retval = true; else if (flag_value == "yes") *retval = true; else if (flag_value == "0") *retval = false; else if (flag_value == "false") *retval = false; else if (flag_value == "no") *retval = false; else ReportUnknownFlagAndExit(str); res++; if (G_flags->verbosity >= 1) { Printf("%40s => %s\n", name, *retval ? "true" : "false"); } break; } if (it != args->end()) { cont = true; args->erase(it); } } while (cont); return res; } static void FindIntFlag(const char *name, intptr_t default_val, vector<string> *args, intptr_t *retval) { *retval = default_val; bool cont = false; do { cont = false; vector<string>::iterator it = args->begin(); for (; it != args->end(); ++it) { string &str = *it; string flag_value; if (!FlagNameMatch(str, name, &flag_value)) continue; char *end_ptr; const char *beg_ptr = flag_value.c_str(); intptr_t int_val = my_strtol(beg_ptr, &end_ptr, 0); if (flag_value.empty() || beg_ptr + flag_value.size() != end_ptr) ReportUnknownFlagAndExit(str); *retval = int_val; if (G_flags->verbosity >= 1) { Printf("%40s => %ld\n", name, *retval); } break; } if (it != args->end()) { cont = true; args->erase(it); } } while (cont); } static void FindUIntFlag(const char *name, intptr_t default_val, vector<string> *args, uintptr_t *retval) { intptr_t signed_int; FindIntFlag(name, default_val, args, &signed_int); CHECK_GE(signed_int, 0); *retval = signed_int; } void FindStringFlag(const char *name, vector<string> *args, vector<string> *retval) { bool cont = false; do { cont = false; vector<string>::iterator it = args->begin(); for (; it != args->end(); ++it) { string &str = *it; string flag_value; if (!FlagNameMatch(str, name, &flag_value)) continue; retval->push_back(flag_value); if (G_flags->verbosity >= 1) { Printf("%40s => %s\n", name, flag_value.c_str()); } break; } if (it != args->end()) { cont = true; args->erase(it); } } while (cont); } void FindStringFlag(const char *name, vector<string> *args, string *retval) { vector<string> tmp; FindStringFlag(name, args, &tmp); if (tmp.size() > 0) { *retval = tmp.back(); } } static size_t GetMemoryLimitInMbFromProcSelfLimits() { #ifdef VGO_linux // Parse the memory limit section of /proc/self/limits. string proc_self_limits = ReadFileToString("/proc/self/limits", false); const char *max_addr_space = "Max address space"; size_t pos = proc_self_limits.find(max_addr_space); if (pos == string::npos) return 0; pos += strlen(max_addr_space); while (proc_self_limits[pos] == ' ') pos++; if (proc_self_limits[pos] == 'u') return 0; // 'unlimited'. char *end; size_t result = my_strtol(proc_self_limits.c_str() + pos, &end, 0); result >>= 20; return result; #else return 0; #endif } static size_t GetMemoryLimitInMb() { size_t ret = -1; // Maximum possible value. #if defined(VGO_linux) && __WORDSIZE == 32 // Valgrind doesn't support more than 3G per process on 32-bit Linux. ret = 3 * 1024; #endif // Try /proc/self/limits. size_t from_proc_self = GetMemoryLimitInMbFromProcSelfLimits(); if (from_proc_self && ret > from_proc_self) { ret = from_proc_self; } // Try env. const char *from_env_str = (const char*)getenv("VALGRIND_MEMORY_LIMIT_IN_MB"); if (from_env_str) { char *end; size_t from_env_value = (size_t)my_strtol(from_env_str, &end, 0); if (ret > from_env_value) ret = from_env_value; } if (ret == (size_t)-1) return 0; return ret; } bool PhaseDebugIsOn(const char *phase_name) { CHECK(G_flags); for (size_t i = 0; i < G_flags->debug_phase.size(); i++) { if (G_flags->debug_phase[i] == phase_name) return true; } return false; } void ThreadSanitizerParseFlags(vector<string> *args) { #ifdef TS_OFFLINE string input_type_tmp; FindStringFlag("input_type", args, &input_type_tmp); if (input_type_tmp.size() > 0) { G_flags->input_type = input_type_tmp; } else { G_flags->input_type = "str"; } #endif // Check this first. FindIntFlag("v", 0, args, &G_flags->verbosity); FindBoolFlag("ignore_stack", false, args, &G_flags->ignore_stack); FindIntFlag("keep_history", 1, args, &G_flags->keep_history); FindUIntFlag("segment_set_recycle_queue_size", DEBUG_MODE ? 10 : 10000, args, &G_flags->segment_set_recycle_queue_size); FindUIntFlag("recent_segments_cache_size", 10, args, &G_flags->recent_segments_cache_size); bool fast_mode = false; FindBoolFlag("fast_mode", false, args, &fast_mode); if (fast_mode) { Printf("INFO: --fast-mode is deprecated\n"); } bool ignore_in_dtor = false; FindBoolFlag("ignore_in_dtor", false, args, &ignore_in_dtor); if (ignore_in_dtor) { Printf("INFO: --ignore-in-dtor is deprecated\n"); } int has_phb = FindBoolFlag("pure_happens_before", true, args, &G_flags->pure_happens_before); bool hybrid = false; int has_hyb = FindBoolFlag("hybrid", false, args, &hybrid); if (has_hyb && has_phb) { Printf("INFO: --hybrid and --pure-happens-before" " is mutually exclusive; ignoring the --hybrid switch\n"); } else if (has_hyb && !has_phb) { G_flags->pure_happens_before = !hybrid; } FindBoolFlag("show_expected_races", false, args, &G_flags->show_expected_races); FindBoolFlag("demangle", true, args, &G_flags->demangle); FindBoolFlag("announce_threads", false, args, &G_flags->announce_threads); FindBoolFlag("full_output", false, args, &G_flags->full_output); FindBoolFlag("show_states", false, args, &G_flags->show_states); FindBoolFlag("show_proc_self_status", false, args, &G_flags->show_proc_self_status); FindBoolFlag("show_valgrind_context", false, args, &G_flags->show_valgrind_context); FindBoolFlag("suggest_happens_before_arcs", true, args, &G_flags->suggest_happens_before_arcs); FindBoolFlag("show_pc", false, args, &G_flags->show_pc); FindBoolFlag("full_stack_frames", false, args, &G_flags->full_stack_frames); FindBoolFlag("free_is_write", true, args, &G_flags->free_is_write); FindBoolFlag("exit_after_main", false, args, &G_flags->exit_after_main); FindIntFlag("show_stats", 0, args, &G_flags->show_stats); FindBoolFlag("trace_profile", false, args, &G_flags->trace_profile); FindBoolFlag("color", false, args, &G_flags->color); FindBoolFlag("html", false, args, &G_flags->html); #ifdef TS_OFFLINE bool show_pid_default = false; #else bool show_pid_default = true; #endif FindBoolFlag("show_pid", show_pid_default, args, &G_flags->show_pid); FindBoolFlag("save_ignore_context", DEBUG_MODE ? true : false, args, &G_flags->save_ignore_context); FindIntFlag("dry_run", 0, args, &G_flags->dry_run); FindBoolFlag("report_races", true, args, &G_flags->report_races); FindIntFlag("locking_scheme", 1, args, &G_flags->locking_scheme); FindBoolFlag("unlock_on_mutex_destroy", true, args, &G_flags->unlock_on_mutex_destroy); FindIntFlag("sample_events", 0, args, &G_flags->sample_events); FindIntFlag("sample_events_depth", 2, args, &G_flags->sample_events_depth); FindIntFlag("debug_level", 1, args, &G_flags->debug_level); FindStringFlag("debug_phase", args, &G_flags->debug_phase); FindIntFlag("trace_level", 0, args, &G_flags->trace_level); FindIntFlag("literace_sampling", 0, args, &G_flags->literace_sampling); FindIntFlag("sampling", 0, args, &G_flags->literace_sampling); CHECK(G_flags->literace_sampling < 32); CHECK(G_flags->literace_sampling >= 0); FindBoolFlag("start_with_global_ignore_on", false, args, &G_flags->start_with_global_ignore_on); FindStringFlag("fullpath_after", args, &G_flags->file_prefix_to_cut); FindStringFlag("file_prefix_to_cut", args, &G_flags->file_prefix_to_cut); for (size_t i = 0; i < G_flags->file_prefix_to_cut.size(); i++) { G_flags->file_prefix_to_cut[i] = ConvertToPlatformIndependentPath(G_flags->file_prefix_to_cut[i]); } FindStringFlag("ignore", args, &G_flags->ignore); FindStringFlag("whitelist", args, &G_flags->whitelist); FindBoolFlag("ignore_unknown_pcs", false, args, &G_flags->ignore_unknown_pcs); FindBoolFlag("thread_coverage", false, args, &G_flags->thread_coverage); FindBoolFlag("atomicity", false, args, &G_flags->atomicity); if (G_flags->atomicity) { // When doing atomicity violation checking we should not // create h-b arcs between Unlocks and Locks. G_flags->pure_happens_before = false; } FindBoolFlag("call_coverage", false, args, &G_flags->call_coverage); FindStringFlag("dump_events", args, &G_flags->dump_events); FindBoolFlag("symbolize", true, args, &G_flags->symbolize); FindIntFlag("trace_addr", 0, args, reinterpret_cast<intptr_t*>(&G_flags->trace_addr)); FindIntFlag("max_mem_in_mb", 0, args, &G_flags->max_mem_in_mb); FindBoolFlag("offline", false, args, &G_flags->offline); FindBoolFlag("attach_mode", false, args, &G_flags->attach_mode); if (G_flags->max_mem_in_mb == 0) { G_flags->max_mem_in_mb = GetMemoryLimitInMb(); } vector<string> summary_file_tmp; FindStringFlag("summary_file", args, &summary_file_tmp); if (summary_file_tmp.size() > 0) { G_flags->summary_file = summary_file_tmp.back(); } vector<string> log_file_tmp; FindStringFlag("log_file", args, &log_file_tmp); if (log_file_tmp.size() > 0) { G_flags->log_file = log_file_tmp.back(); } G_flags->tsan_program_name = "valgrind --tool=tsan"; FindStringFlag("tsan_program_name", args, &G_flags->tsan_program_name); G_flags->tsan_url = "http://code.google.com/p/data-race-test"; FindStringFlag("tsan_url", args, &G_flags->tsan_url); FindStringFlag("suppressions", args, &G_flags->suppressions); FindBoolFlag("gen_suppressions", false, args, &G_flags->generate_suppressions); FindIntFlag("error_exitcode", 0, args, &G_flags->error_exitcode); FindIntFlag("flush_period", 0, args, &G_flags->flush_period); FindBoolFlag("trace_children", false, args, &G_flags->trace_children); FindIntFlag("max_sid", kMaxSID, args, &G_flags->max_sid); kMaxSID = G_flags->max_sid; if (kMaxSID <= 100000) { Printf("Error: max-sid should be at least 100000. Exiting\n"); exit(1); } FindIntFlag("max_sid_before_flush", (kMaxSID * 15) / 16, args, &G_flags->max_sid_before_flush); kMaxSIDBeforeFlush = G_flags->max_sid_before_flush; FindIntFlag("num_callers_in_history", kSizeOfHistoryStackTrace, args, &G_flags->num_callers_in_history); kSizeOfHistoryStackTrace = G_flags->num_callers_in_history; // Cut stack under the following default functions. G_flags->cut_stack_below.push_back("TSanThread*ThreadBody*"); G_flags->cut_stack_below.push_back("ThreadSanitizerStartThread"); G_flags->cut_stack_below.push_back("start_thread"); G_flags->cut_stack_below.push_back("BaseThreadInitThunk"); FindStringFlag("cut_stack_below", args, &G_flags->cut_stack_below); FindIntFlag("num_callers", 16, args, &G_flags->num_callers); G_flags->max_n_threads = 100000; if (G_flags->full_output) { G_flags->announce_threads = true; G_flags->show_pc = true; G_flags->full_stack_frames = true; G_flags->show_states = true; G_flags->file_prefix_to_cut.clear(); } FindIntFlag("race_verifier_sleep_ms", 100, args, &G_flags->race_verifier_sleep_ms); FindStringFlag("race_verifier", args, &G_flags->race_verifier); FindStringFlag("race_verifier_extra", args, &G_flags->race_verifier_extra); g_race_verifier_active = !(G_flags->race_verifier.empty() && G_flags->race_verifier_extra.empty()); if (g_race_verifier_active) { Printf("INFO: ThreadSanitizer running in Race Verifier mode.\n"); } FindBoolFlag("nacl_untrusted", false, args, &G_flags->nacl_untrusted); FindBoolFlag("threaded_analysis", false, args, &G_flags->threaded_analysis); FindBoolFlag("sched_shake", false, args, &G_flags->sched_shake); FindBoolFlag("api_ambush", false, args, &G_flags->api_ambush); FindBoolFlag("enable_atomic", false, args, &G_flags->enable_atomic); if (!args->empty()) { ReportUnknownFlagAndExit(args->front()); } debug_expected_races = PhaseDebugIsOn("expected_races"); debug_benign_races = PhaseDebugIsOn("benign_races"); debug_malloc = PhaseDebugIsOn("malloc"); debug_free = PhaseDebugIsOn("free"); debug_thread = PhaseDebugIsOn("thread"); debug_ignore = PhaseDebugIsOn("ignore"); debug_rtn = PhaseDebugIsOn("rtn"); debug_lock = PhaseDebugIsOn("lock"); debug_wrap = PhaseDebugIsOn("wrap"); debug_ins = PhaseDebugIsOn("ins"); debug_shadow_stack = PhaseDebugIsOn("shadow_stack"); debug_happens_before = PhaseDebugIsOn("happens_before"); debug_cache = PhaseDebugIsOn("cache"); debug_race_verifier = PhaseDebugIsOn("race_verifier"); debug_atomic = PhaseDebugIsOn("atomic"); } // -------- ThreadSanitizer ------------------ {{{1 // Setup the list of functions/images/files to ignore. static void SetupIgnore() { g_ignore_lists = new IgnoreLists; g_white_lists = new IgnoreLists; // Add some major ignore entries so that tsan remains sane // even w/o any ignore file. First - for all platforms. g_ignore_lists->ignores.push_back(IgnoreFun("ThreadSanitizerStartThread")); g_ignore_lists->ignores.push_back(IgnoreFun("exit")); g_ignore_lists->ignores.push_back(IgnoreFun("longjmp")); // Dangerous: recursively ignoring vfprintf hides races on printf arguments. // See PrintfTests in unittest/racecheck_unittest.cc // TODO(eugenis): Do something about this. // http://code.google.com/p/data-race-test/issues/detail?id=53 g_ignore_lists->ignores_r.push_back(IgnoreFun("vfprintf")); // do not create segments in our Replace_* functions g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_memcpy")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_memchr")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strcpy")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strchr")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strchrnul")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strrchr")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strlen")); g_ignore_lists->ignores_hist.push_back(IgnoreFun("Replace_strcmp")); // Ignore everything in our own file. g_ignore_lists->ignores.push_back(IgnoreFile("*ts_valgrind_intercepts.c")); #ifndef _MSC_VER // POSIX ignores g_ignore_lists->ignores.push_back(IgnoreObj("*/libpthread*")); g_ignore_lists->ignores.push_back(IgnoreObj("*/ld-2*.so")); g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create")); g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create@*")); g_ignore_lists->ignores.push_back(IgnoreFun("pthread_create_WRK")); g_ignore_lists->ignores.push_back(IgnoreFun("__cxa_*")); g_ignore_lists->ignores.push_back( IgnoreFun("*__gnu_cxx*__exchange_and_add*")); g_ignore_lists->ignores.push_back(IgnoreFun("__lll_mutex_*")); g_ignore_lists->ignores.push_back(IgnoreFun("__lll_*lock_*")); g_ignore_lists->ignores.push_back(IgnoreFun("__fprintf_chk")); g_ignore_lists->ignores.push_back(IgnoreFun("_IO_file_xsputn*")); // fflush internals g_ignore_lists->ignores.push_back(IgnoreFun("_IO_adjust_column")); g_ignore_lists->ignores.push_back(IgnoreFun("_IO_flush_all_lockp")); g_ignore_lists->ignores.push_back(IgnoreFun("__sigsetjmp")); g_ignore_lists->ignores.push_back(IgnoreFun("__sigjmp_save")); g_ignore_lists->ignores.push_back(IgnoreFun("_setjmp")); g_ignore_lists->ignores.push_back(IgnoreFun("_longjmp_unwind")); g_ignore_lists->ignores.push_back(IgnoreFun("__mktime_internal")); // http://code.google.com/p/data-race-test/issues/detail?id=40 g_ignore_lists->ignores_r.push_back(IgnoreFun("_ZNSsD1Ev")); g_ignore_lists->ignores_r.push_back(IgnoreFun("gaih_inet")); g_ignore_lists->ignores_r.push_back(IgnoreFun("getaddrinfo")); g_ignore_lists->ignores_r.push_back(IgnoreFun("gethostbyname2_r")); #ifdef VGO_darwin // Mac-only ignores g_ignore_lists->ignores.push_back(IgnoreObj("/usr/lib/dyld")); g_ignore_lists->ignores.push_back(IgnoreObj("/usr/lib/libobjc.A.dylib")); g_ignore_lists->ignores.push_back(IgnoreObj("*/libSystem.*.dylib")); g_ignore_lists->ignores_r.push_back(IgnoreFun("__CFDoExternRefOperation")); g_ignore_lists->ignores_r.push_back(IgnoreFun("_CFAutoreleasePoolPop")); g_ignore_lists->ignores_r.push_back(IgnoreFun("_CFAutoreleasePoolPush")); g_ignore_lists->ignores_r.push_back(IgnoreFun("OSAtomicAdd32")); g_ignore_lists->ignores_r.push_back(IgnoreTriple("_dispatch_Block_copy", "/usr/lib/libSystem.B.dylib", "*")); // pthread_lib_{enter,exit} shouldn't give us any reports since they // have IGNORE_ALL_ACCESSES_BEGIN/END but they do give the reports... g_ignore_lists->ignores_r.push_back(IgnoreFun("pthread_lib_enter")); g_ignore_lists->ignores_r.push_back(IgnoreFun("pthread_lib_exit")); #endif #else // Windows-only ignores g_ignore_lists->ignores.push_back(IgnoreObj("*ole32.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*OLEAUT32.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*MSCTF.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*ntdll.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*mswsock.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*WS2_32.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*msvcrt.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*kernel32.dll")); g_ignore_lists->ignores.push_back(IgnoreObj("*ADVAPI32.DLL")); g_ignore_lists->ignores.push_back(IgnoreFun("_EH_epilog3")); g_ignore_lists->ignores.push_back(IgnoreFun("_EH_prolog3_catch")); g_ignore_lists->ignores.push_back(IgnoreFun("unnamedImageEntryPoint")); g_ignore_lists->ignores.push_back(IgnoreFun("_Mtxunlock")); g_ignore_lists->ignores.push_back(IgnoreFun("IsNLSDefinedString")); g_ignore_lists->ignores_r.push_back(IgnoreFun("RtlDestroyQueryDebugBuffer")); g_ignore_lists->ignores_r.push_back(IgnoreFun("BCryptGenerateSymmetricKey")); g_ignore_lists->ignores_r.push_back(IgnoreFun("SHGetItemFromDataObject")); // http://code.google.com/p/data-race-test/issues/detail?id=53 g_ignore_lists->ignores_r.push_back(IgnoreFun("_stbuf")); g_ignore_lists->ignores_r.push_back(IgnoreFun("_getptd")); // TODO(timurrrr): Add support for FLS (fiber-local-storage) // http://code.google.com/p/data-race-test/issues/detail?id=55 g_ignore_lists->ignores_r.push_back(IgnoreFun("_freefls")); #endif #ifdef ANDROID // Android does not have a libpthread; pthread_* functions live in libc. // We have to ignore them one-by-one. g_ignore_lists->ignores.push_back(IgnoreFun("pthread_*")); g_ignore_lists->ignores.push_back(IgnoreFun("__init_tls")); #endif // Now read the ignore/whitelist files. for (size_t i = 0; i < G_flags->ignore.size(); i++) { string file_name = G_flags->ignore[i]; Report("INFO: Reading ignore file: %s\n", file_name.c_str()); string str = ReadFileToString(file_name, true); ReadIgnoresFromString(str, g_ignore_lists); } for (size_t i = 0; i < G_flags->whitelist.size(); i++) { string file_name = G_flags->whitelist[i]; Report("INFO: Reading whitelist file: %s\n", file_name.c_str()); string str = ReadFileToString(file_name, true); ReadIgnoresFromString(str, g_white_lists); } } void ThreadSanitizerSetUnwindCallback(ThreadSanitizerUnwindCallback cb) { G_detector->SetUnwindCallback(cb); } void ThreadSanitizerNaclUntrustedRegion(uintptr_t mem_start, uintptr_t mem_end) { g_nacl_mem_start = mem_start; g_nacl_mem_end = mem_end; } bool AddrIsInNaclUntrustedRegion(uintptr_t addr) { return addr >= g_nacl_mem_start && addr < g_nacl_mem_end; } bool ThreadSanitizerIgnoreForNacl(uintptr_t addr) { // Ignore trusted addresses if tracing untrusted code, and ignore untrusted // addresses otherwise. return G_flags->nacl_untrusted != AddrIsInNaclUntrustedRegion(addr); } bool ThreadSanitizerWantToInstrumentSblock(uintptr_t pc) { string img_name, rtn_name, file_name; int line_no; G_stats->pc_to_strings++; PcToStrings(pc, false, &img_name, &rtn_name, &file_name, &line_no); if (g_white_lists->ignores.size() > 0) { bool in_white_list = TripleVectorMatchKnown(g_white_lists->ignores, rtn_name, img_name, file_name); if (in_white_list) { if (debug_ignore) { Report("INFO: Whitelisted rtn: %s\n", rtn_name.c_str()); } } else { return false; } } if (G_flags->ignore_unknown_pcs && rtn_name == "(no symbols)") { if (debug_ignore) { Report("INFO: not instrumenting unknown function at %p\n", pc); } return false; } bool ignore = TripleVectorMatchKnown(g_ignore_lists->ignores, rtn_name, img_name, file_name) || TripleVectorMatchKnown(g_ignore_lists->ignores_r, rtn_name, img_name, file_name); if (debug_ignore) { Printf("%s: pc=%p file_name=%s img_name=%s rtn_name=%s ret=%d\n", __FUNCTION__, pc, file_name.c_str(), img_name.c_str(), rtn_name.c_str(), !ignore); } bool nacl_ignore = ThreadSanitizerIgnoreForNacl(pc); return !(ignore || nacl_ignore); } bool ThreadSanitizerWantToCreateSegmentsOnSblockEntry(uintptr_t pc) { string rtn_name; rtn_name = PcToRtnName(pc, false); if (G_flags->keep_history == 0) return false; return !(TripleVectorMatchKnown(g_ignore_lists->ignores_hist, rtn_name, "", "")); } // Returns true if function at "pc" is marked as "fun_r" in the ignore file. bool NOINLINE ThreadSanitizerIgnoreAccessesBelowFunction(uintptr_t pc) { ScopedMallocCostCenter cc(__FUNCTION__); typedef unordered_map<uintptr_t, bool> Cache; static Cache *cache = NULL; { TIL ignore_below_lock(ts_ignore_below_lock, 18); if (!cache) cache = new Cache; // Fast path - check if we already know the answer. Cache::iterator i = cache->find(pc); if (i != cache->end()) return i->second; } string rtn_name = PcToRtnName(pc, false); bool ret = TripleVectorMatchKnown(g_ignore_lists->ignores_r, rtn_name, "", ""); if (DEBUG_MODE) { // Heavy test for NormalizeFunctionName: test on all possible inputs in // debug mode. TODO(timurrrr): Remove when tested. NormalizeFunctionName(PcToRtnName(pc, true)); } // Grab the lock again TIL ignore_below_lock(ts_ignore_below_lock, 19); if (ret && debug_ignore) { Report("INFO: ignoring all accesses below the function '%s' (%p)\n", PcToRtnNameAndFilePos(pc).c_str(), pc); } return ((*cache)[pc] = ret); } // We intercept a user function with this name // and answer the user query with a non-NULL string. extern "C" const char *ThreadSanitizerQuery(const char *query) { const char *ret = "0"; string str(query); if (str == "pure_happens_before" && G_flags->pure_happens_before == true) { ret = "1"; } if (str == "hybrid_full" && G_flags->pure_happens_before == false) { ret = "1"; } if (str == "race_verifier" && g_race_verifier_active == true) { ret = "1"; } if (DEBUG_MODE && G_flags->debug_level >= 2) { Printf("ThreadSanitizerQuery(\"%s\") = \"%s\"\n", query, ret); } if (str == "trace-level=0") { Report("INFO: trace-level=0\n"); G_flags->trace_level = 0; debug_happens_before = false; } if (str == "trace-level=1") { Report("INFO: trace-level=1\n"); G_flags->trace_level = 1; debug_happens_before = true; } return ret; } extern void ThreadSanitizerInit() { ScopedMallocCostCenter cc("ThreadSanitizerInit"); ts_lock = new TSLock; ts_ignore_below_lock = new TSLock; g_so_far_only_one_thread = true; ANNOTATE_BENIGN_RACE(&g_so_far_only_one_thread, "real benign race"); CHECK_EQ(sizeof(ShadowValue), 8); CHECK(G_flags); G_stats = new Stats; SetupIgnore(); G_detector = new Detector; G_cache = new Cache; G_expected_races_map = new ExpectedRacesMap; G_heap_map = new HeapMap<HeapInfo>; G_thread_stack_map = new HeapMap<ThreadStackInfo>; { ScopedMallocCostCenter cc1("Segment::InitClassMembers"); Segment::InitClassMembers(); } SegmentSet::InitClassMembers(); CacheLine::InitClassMembers(); TSanThread::InitClassMembers(); Lock::InitClassMembers(); LockSet::InitClassMembers(); EventSampler::InitClassMembers(); VTS::InitClassMembers(); // TODO(timurrrr): make sure *::InitClassMembers() are called only once for // each class g_publish_info_map = new PublishInfoMap; g_stack_trace_free_list = new StackTraceFreeList; g_pcq_map = new PCQMap; g_atomicCore = new TsanAtomicCore(); if (G_flags->html) { c_bold = "<font ><b>"; c_red = "<font color=red><b>"; c_green = "<font color=green><b>"; c_magenta = "<font color=magenta><b>"; c_cyan = "<font color=cyan><b>"; c_blue = "<font color=blue><b>"; c_yellow = "<font color=yellow><b>"; c_default = "</b></font>"; } else if (G_flags->color) { // Enable ANSI colors. c_bold = "\033[1m"; c_red = "\033[31m"; c_green = "\033[32m"; c_yellow = "\033[33m"; c_blue = "\033[34m"; c_magenta = "\033[35m"; c_cyan = "\033[36m"; c_default = "\033[0m"; } if (G_flags->verbosity >= 1) { Report("INFO: Started pid %d\n", getpid()); } if (G_flags->start_with_global_ignore_on) { global_ignore = true; Report("INFO: STARTING WITH GLOBAL IGNORE ON\n"); } ANNOTATE_BENIGN_RACE(&g_lock_era, "g_lock_era may be incremented in a racey way"); } extern void ThreadSanitizerFini() { G_detector->HandleProgramEnd(); } extern void ThreadSanitizerDumpAllStacks() { // first, print running threads. for (int i = 0; i < TSanThread::NumberOfThreads(); i++) { TSanThread *t = TSanThread::Get(TID(i)); if (!t || !t->is_running()) continue; Report("T%d\n", i); t->ReportStackTrace(); } // now print all dead threds. for (int i = 0; i < TSanThread::NumberOfThreads(); i++) { TSanThread *t = TSanThread::Get(TID(i)); if (!t || t->is_running()) continue; Report("T%d (not running)\n", i); t->ReportStackTrace(); } } extern void ThreadSanitizerHandleOneEvent(Event *e) { // Lock is inside on some paths. G_detector->HandleOneEvent(e); } TSanThread *ThreadSanitizerGetThreadByTid(int32_t tid) { return TSanThread::Get(TID(tid)); } extern NOINLINE void ThreadSanitizerHandleTrace(int32_t tid, TraceInfo *trace_info, uintptr_t *tleb) { ThreadSanitizerHandleTrace(TSanThread::Get(TID(tid)), trace_info, tleb); } extern NOINLINE void ThreadSanitizerHandleTrace(TSanThread *thr, TraceInfo *trace_info, uintptr_t *tleb) { DCHECK(thr); // The lock is taken inside on the slow path. G_detector->HandleTrace(thr, trace_info->mops(), trace_info->n_mops(), trace_info->pc(), tleb, /*need_locking=*/true); } extern NOINLINE void ThreadSanitizerHandleOneMemoryAccess(TSanThread *thr, MopInfo mop, uintptr_t addr) { DCHECK(thr); G_detector->HandleTrace(thr, &mop, 1, mop.create_sblock() ? mop.pc() : 0, &addr, /*need_locking=*/true); } void NOINLINE ThreadSanitizerHandleRtnCall(int32_t tid, uintptr_t call_pc, uintptr_t target_pc, IGNORE_BELOW_RTN ignore_below) { // This does locking on a cold path. Hot path in thread-local. G_detector->HandleRtnCall(TID(tid), call_pc, target_pc, ignore_below); if (G_flags->sample_events) { static EventSampler sampler; TSanThread *thr = TSanThread::Get(TID(tid)); sampler.Sample(thr, "RTN_CALL", true); } } void NOINLINE ThreadSanitizerHandleRtnExit(int32_t tid) { // This is a thread-local operation, no need for locking. TSanThread::Get(TID(tid))->HandleRtnExit(); } static bool ThreadSanitizerPrintReport(ThreadSanitizerReport *report) { return G_detector->reports_.PrintReport(report); } // -------- TsanAtomicImplementation ------------------ {{{1 // Atomic operation handler. // The idea of atomic handling is as simple as follows. // * First, we handle it as normal memory access, // however with race reporting suppressed. That is, we won't produce any // race reports during atomic access, but we can produce race reports // later during normal memory accesses that race with the access. // * Then, we do the actual atomic memory access. // It's executed in an atomic fashion, because there can be simultaneous // atomic accesses from non-instrumented code (FUTEX_OP is a notable // example). // * Finally, we update simulated memory model state according to // the access type and associated memory order as follows. // For writes and RMWs we create a new entry in the modification order // of the variable. For reads we scan the modification order starting // from the latest entry and going back in time, during the scan we decide // what entry the read returns. A separate VTS (happens-before edges) // is associated with each entry in the modification order, so that a load // acquires memory visibility from the exact release-sequence associated // with the loaded value. // For details of memory modelling refer to sections 1.10 and 29 // of C++0x standard: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf uint64_t ThreadSanitizerHandleAtomicOp(int32_t tid, uintptr_t pc, tsan_atomic_op op, tsan_memory_order mo, tsan_memory_order fail_mo, size_t size, void volatile* a, uint64_t v, uint64_t cmp) { if (G_flags->enable_atomic == false) { uint64_t newv = 0; uint64_t prev = 0; return tsan_atomic_do_op(op, mo, fail_mo, size, a, v, cmp, &newv, &prev); } else { uint64_t rv = 0; TSanThread* thr = TSanThread::Get(TID(tid)); // Just a verification of the parameters. tsan_atomic_verify(op, mo, fail_mo, size, a); { TIL til(ts_lock, 0); uint64_t newv = 0; uint64_t prev = 0; // Handle it as a plain mop. Race reports are temporally suppressed,though. thr->HandleAtomicMop((uintptr_t)a, pc, op, mo, size); // Do the actual atomic operation. It's executed in an atomic fashion, // because there can be simultaneous atomic accesses // from non-instrumented code. rv = tsan_atomic_do_op(op, mo, fail_mo, size, a, v, cmp, &newv, &prev); PrintfIf(debug_atomic, "rv=%llu, newv=%llu, prev=%llu\n", (unsigned long long)rv, (unsigned long long)newv, (unsigned long long)prev); if (op != tsan_atomic_op_fence) { if (op == tsan_atomic_op_load) { // For reads it replaces the return value with a random value // from visible sequence of side-effects in the modification order // of the variable. rv = g_atomicCore->HandleRead(thr, (uintptr_t)a, rv, tsan_atomic_is_acquire(mo)); } else if ((op == tsan_atomic_op_compare_exchange_weak || op == tsan_atomic_op_compare_exchange_strong) && cmp != rv) { // Failed compare_exchange is handled as read, because, well, // it's indeed just a read (at least logically). g_atomicCore->HandleRead(thr, (uintptr_t)a, rv, tsan_atomic_is_acquire(fail_mo)); } else { // For writes and RMW operations it updates modification order // of the atomic variable. g_atomicCore->HandleWrite(thr, (uintptr_t)a, newv, prev, tsan_atomic_is_acquire(mo), tsan_atomic_is_release(mo), tsan_atomic_is_rmw(op)); } } } PrintfIf(debug_atomic, "ATOMIC: %s-%s %p (%llu,%llu)=%llu\n", tsan_atomic_to_str(op), tsan_atomic_to_str(mo), a, (unsigned long long)v, (unsigned long long)cmp, (unsigned long long)rv); return rv; } } TsanAtomicCore::TsanAtomicCore() { } void TsanAtomicCore::HandleWrite(TSanThread* thr, uintptr_t a, uint64_t v, uint64_t prev, bool const is_acquire, bool const is_release, bool const is_rmw) { PrintfIf(debug_atomic, "HIST(%p): store acquire=%u, release=%u, rmw=%u\n", (void*)a, is_acquire, is_release, is_rmw); Atomic* atomic = &atomic_map_[a]; // Fix modification history if there were untracked accesses. AtomicFixHist(atomic, prev); AtomicHistoryEntry& hprv = atomic->hist [(atomic->hist_pos - 1) % Atomic::kHistSize]; AtomicHistoryEntry& hist = atomic->hist [atomic->hist_pos % Atomic::kHistSize]; // Fill in new entry in the modification history. hist.val = v; hist.tid = thr->tid(); hist.clk = thr->vts()->clk(thr->tid()); if (hist.vts != 0) { VTS::Unref(hist.vts); hist.vts = 0; } atomic->hist_pos += 1; // Update VTS according to memory access type and memory ordering. if (is_rmw) { if (is_release) { if (hprv.vts != 0) { hist.vts = VTS::Join(hprv.vts, thr->vts()); } else { hist.vts = thr->vts()->Clone(); } } else if (hprv.vts != 0) { hist.vts = hprv.vts->Clone(); } if (is_acquire && hprv.vts != 0) { thr->NewSegmentForWait(hprv.vts); } } else { DCHECK(is_acquire == false); if (is_release) { hist.vts = thr->vts()->Clone(); } } // Update the thread's VTS if it's relese memory access. if (is_release) { thr->NewSegmentForSignal(); if (debug_happens_before) { Printf("T%d: Signal: %p:\n %s %s\n %s\n", thr->tid().raw(), a, thr->vts()->ToString().c_str(), Segment::ToString(thr->sid()).c_str(), hist.vts->ToString().c_str()); if (G_flags->debug_level >= 1) { thr->ReportStackTrace(); } } } } uint64_t TsanAtomicCore::HandleRead(TSanThread* thr, uintptr_t a, uint64_t v, bool is_acquire) { PrintfIf(debug_atomic, "HIST(%p): {\n", (void*)a); Atomic* atomic = &atomic_map_[a]; // Fix modification history if there were untracked accesses. AtomicFixHist(atomic, v); AtomicHistoryEntry* hist0 = 0; int32_t seen_seq = 0; int32_t const seen_seq0 = atomic->last_seen.clock(thr->tid()); // Scan modification order of the variable from the latest entry // back in time. For each side-effect (write) we determine as to // whether we have to yield the value or we can go back in time further. for (int32_t i = 0; i != Atomic::kHistSize; i += 1) { int32_t const idx = (atomic->hist_pos - i - 1); CHECK(idx >= 0); AtomicHistoryEntry& hist = atomic->hist[idx % Atomic::kHistSize]; PrintfIf(debug_atomic, "HIST(%p): #%u (tid=%u, clk=%u," " val=%llu) vts=%u\n", (void*)a, (unsigned)i, (unsigned)hist.tid.raw(), (unsigned)hist.clk, (unsigned long long)hist.val, (unsigned)thr->vts()->clk(hist.tid)); if (hist.tid.raw() == TID::kInvalidTID) { // We hit an uninialized entry, that is, it's an access to an unitialized // variable (potentially due to "race"). // Unfortunately, it should not happen as of now. // TODO(dvyukov): how can we detect and report unitialized atomic reads?. // . hist0 = 0; break; } else if (i == Atomic::kHistSize - 1) { // It's the last entry so we have to return it // because we have to return something. PrintfIf(debug_atomic, "HIST(%p): replaced: last\n", (void*)a); hist0 = &hist; break; } else if (seen_seq0 >= idx) { // The thread had already seen the entry so we have to return // at least it. PrintfIf(debug_atomic, "HIST(%p): replaced: stability\n", (void*)a); hist0 = &hist; break; } else if (thr->vts()->clk(hist.tid) >= hist.clk) { // The write happened-before the read, so we have to return it. PrintfIf(debug_atomic, "HIST(%p): replaced: ordering\n", (void*)a); hist0 = &hist; break; } else if (thr->random() % 2) { // We are not obliged to return the entry but we can (and decided to do). PrintfIf(debug_atomic, "HIST(%p): replaced: coherence\n", (void*)a); seen_seq = idx; hist0 = &hist; break; } else { // Move on to the next (older) entry. PrintfIf(debug_atomic, "HIST(%p): can be replaced but not\n", (void*)a); } } if (hist0 != 0) { v = hist0->val; // Acquire mamory visibility is needed. if (is_acquire) { if (hist0->vts != 0) { thr->NewSegmentForWait(hist0->vts); } if (debug_happens_before) { Printf("T%d: Wait: %p:\n %s %s\n", thr->tid().raw(), a, thr->vts()->ToString().c_str(), Segment::ToString(thr->sid()).c_str()); if (G_flags->debug_level >= 1) { thr->ReportStackTrace(); } } } if (seen_seq != 0) { // Mark the entry as seen so we won't return any older entry later. atomic->last_seen.update(thr->tid(), seen_seq); } } else { CHECK("should never happen as of now" == 0); PrintfIf(debug_atomic, "HIST(%p): UNITIALIZED LOAD\n", (void*)a); v = thr->random(); } PrintfIf(debug_atomic, "HIST(%p): } -> %llu\n", (void*)a, (unsigned long long)v); return v; } void TsanAtomicCore::ClearMemoryState(uintptr_t a, uintptr_t b) { DCHECK(a <= b); DCHECK(G_flags->enable_atomic || atomic_map_.empty()); AtomicMap::iterator begin (atomic_map_.lower_bound(a)); AtomicMap::iterator pos (begin); for (; pos != atomic_map_.end() && pos->first <= b; ++pos) { pos->second.reset(); } atomic_map_.erase(begin, pos); } void TsanAtomicCore::AtomicFixHist(Atomic* atomic, uint64_t prev) { AtomicHistoryEntry& hprv = atomic->hist [(atomic->hist_pos - 1) % Atomic::kHistSize]; // In case we had missed an atomic access (that is, an access from // non-instrumented code), reset whole history and initialize it // with a single entry that happened "before world creation". if (prev != hprv.val) { PrintfIf(debug_atomic, "HIST RESET\n"); atomic->reset(); AtomicHistoryEntry& hist = atomic->hist [atomic->hist_pos % Atomic::kHistSize]; hist.val = prev; hist.tid = TID(0); hist.clk = 0; atomic->hist_pos += 1; } } TsanAtomicCore::Atomic::Atomic() { reset(true); } void TsanAtomicCore::Atomic::reset(bool init) { hist_pos = sizeof(hist)/sizeof(hist[0]) + 1; for (size_t i = 0; i != sizeof(hist)/sizeof(hist[0]); i += 1) { hist[i].val = 0xBCEBC041; hist[i].tid = TID(TID::kInvalidTID); hist[i].clk = -1; if (init == false && hist[i].vts != 0) VTS::Unref(hist[i].vts); hist[i].vts = 0; } last_seen.reset(); } // -------- TODO -------------------------- {{{1 // - Support configurable aliases for function names (is it doable in valgrind)? // - Correctly support atomic operations (not just ignore). // - Handle INC as just one write // - same for memset, etc // - Implement correct handling of memory accesses with different sizes. // - Do not create HB arcs between RdUnlock and RdLock // - Compress cache lines // - Optimize the case where a threads signals twice in a row on the same // address. // - Fix --ignore-in-dtor if --demangle=no. // - Use cpplint (http://code.google.com/p/google-styleguide) // - Get rid of annoying casts in printfs. // - Compress stack traces (64-bit only. may save up to 36 bytes per segment). // end. {{{1 // vim:shiftwidth=2:softtabstop=2:expandtab:tw=80