diff options
author | Kostya Serebryany <kcc@google.com> | 2013-01-10 12:44:08 +0000 |
---|---|---|
committer | Kostya Serebryany <kcc@gcc.gnu.org> | 2013-01-10 12:44:08 +0000 |
commit | e9772e16b39885fb70f6e3651a0b98d6de8655c3 (patch) | |
tree | 23cebf7ab15836f70e055aee309f853c0c377de6 /libsanitizer | |
parent | e1f674e4c21be4834cfad53666b5b7a9492cf0a5 (diff) | |
download | gcc-e9772e16b39885fb70f6e3651a0b98d6de8655c3.zip gcc-e9772e16b39885fb70f6e3651a0b98d6de8655c3.tar.gz gcc-e9772e16b39885fb70f6e3651a0b98d6de8655c3.tar.bz2 |
libsanitizer mege from upstream r171973
From-SVN: r195083
Diffstat (limited to 'libsanitizer')
89 files changed, 4166 insertions, 864 deletions
diff --git a/libsanitizer/ChangeLog b/libsanitizer/ChangeLog index ec9176f..e0e39c3 100644 --- a/libsanitizer/ChangeLog +++ b/libsanitizer/ChangeLog @@ -1,3 +1,13 @@ +2013-01-10 Kostya Serebryany <kcc@google.com> + + * All source files: Merge from upstream r171973. + * sanitizer_common/Makefile.am: Added new files. + * asan/Makefile.am: Likewise. + * tsan/Makefile.am: Likewise. + * sanitizer_common/Makefile.in: Regenerated. + * asan/Makefile.in: Likewise. + * tsan/Makefile.in: Likewise. + 2013-01-07 H.J. Lu <hongjiu.lu@intel.com> * asan/Makefile.am (libasan_la_LIBADD): Replace diff --git a/libsanitizer/MERGE b/libsanitizer/MERGE index 071edae..ff637c2 100644 --- a/libsanitizer/MERGE +++ b/libsanitizer/MERGE @@ -1,4 +1,4 @@ -169392 +171973 The first line of this file holds the svn revision number of the last merge done from the master library sources. diff --git a/libsanitizer/asan/Makefile.am b/libsanitizer/asan/Makefile.am index 04a621f..7d67500 100644 --- a/libsanitizer/asan/Makefile.am +++ b/libsanitizer/asan/Makefile.am @@ -15,6 +15,7 @@ toolexeclib_LTLIBRARIES = libasan.la asan_files = \ asan_allocator.cc \ + asan_allocator2.cc \ asan_interceptors.cc \ asan_mac.cc \ asan_malloc_mac.cc \ @@ -23,6 +24,7 @@ asan_files = \ asan_rtl.cc \ asan_stats.cc \ asan_thread_registry.cc \ + asan_fake_stack.cc \ asan_globals.cc \ asan_linux.cc \ asan_malloc_linux.cc \ diff --git a/libsanitizer/asan/Makefile.in b/libsanitizer/asan/Makefile.in index 7c8e321..4578391 100644 --- a/libsanitizer/asan/Makefile.in +++ b/libsanitizer/asan/Makefile.in @@ -84,19 +84,20 @@ am__DEPENDENCIES_1 = @USING_MAC_INTERPOSE_FALSE@ $(am__DEPENDENCIES_1) @USING_MAC_INTERPOSE_TRUE@libasan_la_DEPENDENCIES = $(top_builddir)/sanitizer_common/libsanitizer_common.la \ @USING_MAC_INTERPOSE_TRUE@ $(am__DEPENDENCIES_1) -am__libasan_la_SOURCES_DIST = asan_allocator.cc asan_interceptors.cc \ - asan_mac.cc asan_malloc_mac.cc asan_new_delete.cc \ - asan_posix.cc asan_rtl.cc asan_stats.cc \ - asan_thread_registry.cc asan_globals.cc asan_linux.cc \ - asan_malloc_linux.cc asan_malloc_win.cc asan_poisoning.cc \ - asan_report.cc asan_stack.cc asan_thread.cc asan_win.cc \ - dynamic/asan_interceptors_dynamic.cc -am__objects_1 = asan_allocator.lo asan_interceptors.lo asan_mac.lo \ - asan_malloc_mac.lo asan_new_delete.lo asan_posix.lo \ - asan_rtl.lo asan_stats.lo asan_thread_registry.lo \ - asan_globals.lo asan_linux.lo asan_malloc_linux.lo \ - asan_malloc_win.lo asan_poisoning.lo asan_report.lo \ - asan_stack.lo asan_thread.lo asan_win.lo +am__libasan_la_SOURCES_DIST = asan_allocator.cc asan_allocator2.cc \ + asan_interceptors.cc asan_mac.cc asan_malloc_mac.cc \ + asan_new_delete.cc asan_posix.cc asan_rtl.cc asan_stats.cc \ + asan_thread_registry.cc asan_fake_stack.cc asan_globals.cc \ + asan_linux.cc asan_malloc_linux.cc asan_malloc_win.cc \ + asan_poisoning.cc asan_report.cc asan_stack.cc asan_thread.cc \ + asan_win.cc dynamic/asan_interceptors_dynamic.cc +am__objects_1 = asan_allocator.lo asan_allocator2.lo \ + asan_interceptors.lo asan_mac.lo asan_malloc_mac.lo \ + asan_new_delete.lo asan_posix.lo asan_rtl.lo asan_stats.lo \ + asan_thread_registry.lo asan_fake_stack.lo asan_globals.lo \ + asan_linux.lo asan_malloc_linux.lo asan_malloc_win.lo \ + asan_poisoning.lo asan_report.lo asan_stack.lo asan_thread.lo \ + asan_win.lo @USING_MAC_INTERPOSE_TRUE@am__objects_2 = \ @USING_MAC_INTERPOSE_TRUE@ asan_interceptors_dynamic.lo am_libasan_la_OBJECTS = $(am__objects_1) $(am__objects_2) @@ -269,6 +270,7 @@ ACLOCAL_AMFLAGS = -I $(top_srcdir) -I $(top_srcdir)/config toolexeclib_LTLIBRARIES = libasan.la asan_files = \ asan_allocator.cc \ + asan_allocator2.cc \ asan_interceptors.cc \ asan_mac.cc \ asan_malloc_mac.cc \ @@ -277,6 +279,7 @@ asan_files = \ asan_rtl.cc \ asan_stats.cc \ asan_thread_registry.cc \ + asan_fake_stack.cc \ asan_globals.cc \ asan_linux.cc \ asan_malloc_linux.cc \ @@ -409,6 +412,8 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_allocator.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_allocator2.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_fake_stack.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_globals.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_interceptors.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asan_interceptors_dynamic.Plo@am__quote@ diff --git a/libsanitizer/asan/asan_allocator.cc b/libsanitizer/asan/asan_allocator.cc index 03d5bbd..b170fe7 100644 --- a/libsanitizer/asan/asan_allocator.cc +++ b/libsanitizer/asan/asan_allocator.cc @@ -22,8 +22,9 @@ // Once freed, the body of the chunk contains the stack trace of the free call. // //===----------------------------------------------------------------------===// - #include "asan_allocator.h" + +#if ASAN_ALLOCATOR_VERSION == 1 #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_lock.h" @@ -35,10 +36,6 @@ #include "sanitizer/asan_interface.h" #include "sanitizer_common/sanitizer_atomic.h" -#if defined(_WIN32) && !defined(__clang__) -#include <intrin.h> -#endif - namespace __asan { #define REDZONE ((uptr)(flags()->redzone)) @@ -58,42 +55,6 @@ static const uptr kMallocSizeClassStep = 1UL << kMallocSizeClassStepLog; static const uptr kMaxAllowedMallocSize = (SANITIZER_WORDSIZE == 32) ? 3UL << 30 : 8UL << 30; -static inline bool IsAligned(uptr a, uptr alignment) { - return (a & (alignment - 1)) == 0; -} - -static inline uptr Log2(uptr x) { - CHECK(IsPowerOfTwo(x)); -#if !defined(_WIN32) || defined(__clang__) - return __builtin_ctzl(x); -#elif defined(_WIN64) - unsigned long ret; // NOLINT - _BitScanForward64(&ret, x); - return ret; -#else - unsigned long ret; // NOLINT - _BitScanForward(&ret, x); - return ret; -#endif -} - -static inline uptr RoundUpToPowerOfTwo(uptr size) { - CHECK(size); - if (IsPowerOfTwo(size)) return size; - - unsigned long up; // NOLINT -#if !defined(_WIN32) || defined(__clang__) - up = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size); -#elif defined(_WIN64) - _BitScanReverse64(&up, size); -#else - _BitScanReverse(&up, size); -#endif - CHECK(size < (1ULL << (up + 1))); - CHECK(size > (1ULL << up)); - return 1UL << (up + 1); -} - static inline uptr SizeClassToSize(u8 size_class) { CHECK(size_class < kNumberOfSizeClasses); if (size_class <= kMallocSizeClassStepLog) { @@ -165,7 +126,8 @@ struct ChunkBase { // Second 8 bytes. uptr alignment_log : 8; - uptr used_size : FIRST_32_SECOND_64(32, 56); // Size requested by the user. + uptr alloc_type : 2; + uptr used_size : FIRST_32_SECOND_64(32, 54); // Size requested by the user. // This field may overlap with the user area and thus should not // be used while the chunk is in CHUNK_ALLOCATED state. @@ -215,33 +177,6 @@ void AsanChunkView::GetFreeStack(StackTrace *stack) { chunk_->compressed_free_stack_size()); } -bool AsanChunkView::AddrIsInside(uptr addr, uptr access_size, uptr *offset) { - if (addr >= Beg() && (addr + access_size) <= End()) { - *offset = addr - Beg(); - return true; - } - return false; -} - -bool AsanChunkView::AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) { - if (addr < Beg()) { - *offset = Beg() - addr; - return true; - } - return false; -} - -bool AsanChunkView::AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) { - if (addr + access_size >= End()) { - if (addr <= End()) - *offset = 0; - else - *offset = addr - End(); - return true; - } - return false; -} - static AsanChunk *PtrToChunk(uptr ptr) { AsanChunk *m = (AsanChunk*)(ptr - REDZONE); if (m->chunk_state == CHUNK_MEMALIGN) { @@ -252,34 +187,13 @@ static AsanChunk *PtrToChunk(uptr ptr) { void AsanChunkFifoList::PushList(AsanChunkFifoList *q) { CHECK(q->size() > 0); - if (last_) { - CHECK(first_); - CHECK(!last_->next); - last_->next = q->first_; - last_ = q->last_; - } else { - CHECK(!first_); - last_ = q->last_; - first_ = q->first_; - CHECK(first_); - } - CHECK(last_); - CHECK(!last_->next); size_ += q->size(); + append_back(q); q->clear(); } void AsanChunkFifoList::Push(AsanChunk *n) { - CHECK(n->next == 0); - if (last_) { - CHECK(first_); - CHECK(!last_->next); - last_->next = n; - last_ = n; - } else { - CHECK(!first_); - last_ = first_ = n; - } + push_back(n); size_ += n->Size(); } @@ -288,15 +202,9 @@ void AsanChunkFifoList::Push(AsanChunk *n) { // ago. Not sure if we can or want to do anything with this. AsanChunk *AsanChunkFifoList::Pop() { CHECK(first_); - AsanChunk *res = first_; - first_ = first_->next; - if (first_ == 0) - last_ = 0; - CHECK(size_ >= res->Size()); + AsanChunk *res = front(); size_ -= res->Size(); - if (last_) { - CHECK(!last_->next); - } + pop_front(); return res; } @@ -588,7 +496,8 @@ AsanChunkView FindHeapChunkByAddress(uptr address) { return AsanChunkView(malloc_info.FindChunkByAddr(address)); } -static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) { +static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { __asan_init(); CHECK(stack); if (size == 0) { @@ -645,6 +554,7 @@ static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) { CHECK(m); CHECK(m->chunk_state == CHUNK_AVAILABLE); m->chunk_state = CHUNK_ALLOCATED; + m->alloc_type = alloc_type; m->next = 0; CHECK(m->Size() == size_to_allocate); uptr addr = (uptr)m + REDZONE; @@ -679,7 +589,7 @@ static u8 *Allocate(uptr alignment, uptr size, StackTrace *stack) { return (u8*)addr; } -static void Deallocate(u8 *ptr, StackTrace *stack) { +static void Deallocate(u8 *ptr, StackTrace *stack, AllocType alloc_type) { if (!ptr) return; CHECK(stack); @@ -700,6 +610,9 @@ static void Deallocate(u8 *ptr, StackTrace *stack) { ReportFreeNotMalloced((uptr)ptr, stack); } CHECK(old_chunk_state == CHUNK_ALLOCATED); + if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch) + ReportAllocTypeMismatch((uptr)ptr, stack, + (AllocType)m->alloc_type, (AllocType)alloc_type); // With REDZONE==16 m->next is in the user area, otherwise it should be 0. CHECK(REDZONE <= 16 || !m->next); CHECK(m->free_tid == kInvalidTid); @@ -744,18 +657,19 @@ static u8 *Reallocate(u8 *old_ptr, uptr new_size, CHECK(m->chunk_state == CHUNK_ALLOCATED); uptr old_size = m->used_size; uptr memcpy_size = Min(new_size, old_size); - u8 *new_ptr = Allocate(0, new_size, stack); + u8 *new_ptr = Allocate(0, new_size, stack, FROM_MALLOC); if (new_ptr) { CHECK(REAL(memcpy) != 0); REAL(memcpy)(new_ptr, old_ptr, memcpy_size); - Deallocate(old_ptr, stack); + Deallocate(old_ptr, stack, FROM_MALLOC); } return new_ptr; } } // namespace __asan -// Default (no-op) implementation of malloc hooks. +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +// Provide default (no-op) implementation of malloc hooks. extern "C" { SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE void __asan_malloc_hook(void *ptr, uptr size) { @@ -767,53 +681,58 @@ void __asan_free_hook(void *ptr) { (void)ptr; } } // extern "C" +#endif namespace __asan { +void PrintInternalAllocatorStats() { +} + SANITIZER_INTERFACE_ATTRIBUTE -void *asan_memalign(uptr alignment, uptr size, StackTrace *stack) { - void *ptr = (void*)Allocate(alignment, size, stack); - __asan_malloc_hook(ptr, size); +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { + void *ptr = (void*)Allocate(alignment, size, stack, alloc_type); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } SANITIZER_INTERFACE_ATTRIBUTE -void asan_free(void *ptr, StackTrace *stack) { - __asan_free_hook(ptr); - Deallocate((u8*)ptr, stack); +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) { + ASAN_FREE_HOOK(ptr); + Deallocate((u8*)ptr, stack, alloc_type); } SANITIZER_INTERFACE_ATTRIBUTE void *asan_malloc(uptr size, StackTrace *stack) { - void *ptr = (void*)Allocate(0, size, stack); - __asan_malloc_hook(ptr, size); + void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) { - void *ptr = (void*)Allocate(0, nmemb * size, stack); + void *ptr = (void*)Allocate(0, nmemb * size, stack, FROM_MALLOC); if (ptr) REAL(memset)(ptr, 0, nmemb * size); - __asan_malloc_hook(ptr, nmemb * size); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } void *asan_realloc(void *p, uptr size, StackTrace *stack) { if (p == 0) { - void *ptr = (void*)Allocate(0, size, stack); - __asan_malloc_hook(ptr, size); + void *ptr = (void*)Allocate(0, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } else if (size == 0) { - __asan_free_hook(p); - Deallocate((u8*)p, stack); + ASAN_FREE_HOOK(p); + Deallocate((u8*)p, stack, FROM_MALLOC); return 0; } return Reallocate((u8*)p, size, stack); } void *asan_valloc(uptr size, StackTrace *stack) { - void *ptr = (void*)Allocate(GetPageSizeCached(), size, stack); - __asan_malloc_hook(ptr, size); + void *ptr = (void*)Allocate(GetPageSizeCached(), size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } @@ -824,16 +743,16 @@ void *asan_pvalloc(uptr size, StackTrace *stack) { // pvalloc(0) should allocate one page. size = PageSize; } - void *ptr = (void*)Allocate(PageSize, size, stack); - __asan_malloc_hook(ptr, size); + void *ptr = (void*)Allocate(PageSize, size, stack, FROM_MALLOC); + ASAN_MALLOC_HOOK(ptr, size); return ptr; } int asan_posix_memalign(void **memptr, uptr alignment, uptr size, StackTrace *stack) { - void *ptr = Allocate(alignment, size, stack); + void *ptr = Allocate(alignment, size, stack, FROM_MALLOC); CHECK(IsAligned((uptr)ptr, alignment)); - __asan_malloc_hook(ptr, size); + ASAN_MALLOC_HOOK(ptr, size); *memptr = ptr; return 0; } @@ -860,170 +779,11 @@ void asan_mz_force_unlock() { malloc_info.ForceUnlock(); } -// ---------------------- Fake stack-------------------- {{{1 -FakeStack::FakeStack() { - CHECK(REAL(memset) != 0); - REAL(memset)(this, 0, sizeof(*this)); -} - -bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) { - uptr mem = allocated_size_classes_[size_class]; - uptr size = ClassMmapSize(size_class); - bool res = mem && addr >= mem && addr < mem + size; - return res; -} - -uptr FakeStack::AddrIsInFakeStack(uptr addr) { - for (uptr i = 0; i < kNumberOfSizeClasses; i++) { - if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i]; - } - return 0; -} - -// We may want to compute this during compilation. -inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) { - uptr rounded_size = RoundUpToPowerOfTwo(alloc_size); - uptr log = Log2(rounded_size); - CHECK(alloc_size <= (1UL << log)); - if (!(alloc_size > (1UL << (log-1)))) { - Printf("alloc_size %zu log %zu\n", alloc_size, log); - } - CHECK(alloc_size > (1UL << (log-1))); - uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog; - CHECK(res < kNumberOfSizeClasses); - CHECK(ClassSize(res) >= rounded_size); - return res; -} - -void FakeFrameFifo::FifoPush(FakeFrame *node) { - CHECK(node); - node->next = 0; - if (first_ == 0 && last_ == 0) { - first_ = last_ = node; - } else { - CHECK(first_); - CHECK(last_); - last_->next = node; - last_ = node; - } -} - -FakeFrame *FakeFrameFifo::FifoPop() { - CHECK(first_ && last_ && "Exhausted fake stack"); - FakeFrame *res = 0; - if (first_ == last_) { - res = first_; - first_ = last_ = 0; - } else { - res = first_; - first_ = first_->next; - } - return res; -} - -void FakeStack::Init(uptr stack_size) { - stack_size_ = stack_size; - alive_ = true; -} - -void FakeStack::Cleanup() { - alive_ = false; - for (uptr i = 0; i < kNumberOfSizeClasses; i++) { - uptr mem = allocated_size_classes_[i]; - if (mem) { - PoisonShadow(mem, ClassMmapSize(i), 0); - allocated_size_classes_[i] = 0; - UnmapOrDie((void*)mem, ClassMmapSize(i)); - } - } -} - -uptr FakeStack::ClassMmapSize(uptr size_class) { - return RoundUpToPowerOfTwo(stack_size_); -} - -void FakeStack::AllocateOneSizeClass(uptr size_class) { - CHECK(ClassMmapSize(size_class) >= GetPageSizeCached()); - uptr new_mem = (uptr)MmapOrDie( - ClassMmapSize(size_class), __FUNCTION__); - // Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n", - // asanThreadRegistry().GetCurrent()->tid(), - // size_class, new_mem, new_mem + ClassMmapSize(size_class), - // ClassMmapSize(size_class)); - uptr i; - for (i = 0; i < ClassMmapSize(size_class); - i += ClassSize(size_class)) { - size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i)); - } - CHECK(i == ClassMmapSize(size_class)); - allocated_size_classes_[size_class] = new_mem; -} - -uptr FakeStack::AllocateStack(uptr size, uptr real_stack) { - if (!alive_) return real_stack; - CHECK(size <= kMaxStackMallocSize && size > 1); - uptr size_class = ComputeSizeClass(size); - if (!allocated_size_classes_[size_class]) { - AllocateOneSizeClass(size_class); - } - FakeFrame *fake_frame = size_classes_[size_class].FifoPop(); - CHECK(fake_frame); - fake_frame->size_minus_one = size - 1; - fake_frame->real_stack = real_stack; - while (FakeFrame *top = call_stack_.top()) { - if (top->real_stack > real_stack) break; - call_stack_.LifoPop(); - DeallocateFrame(top); - } - call_stack_.LifoPush(fake_frame); - uptr ptr = (uptr)fake_frame; - PoisonShadow(ptr, size, 0); - return ptr; -} - -void FakeStack::DeallocateFrame(FakeFrame *fake_frame) { - CHECK(alive_); - uptr size = fake_frame->size_minus_one + 1; - uptr size_class = ComputeSizeClass(size); - CHECK(allocated_size_classes_[size_class]); - uptr ptr = (uptr)fake_frame; - CHECK(AddrIsInSizeClass(ptr, size_class)); - CHECK(AddrIsInSizeClass(ptr + size - 1, size_class)); - size_classes_[size_class].FifoPush(fake_frame); -} - -void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) { - FakeFrame *fake_frame = (FakeFrame*)ptr; - CHECK(fake_frame->magic = kRetiredStackFrameMagic); - CHECK(fake_frame->descr != 0); - CHECK(fake_frame->size_minus_one == size - 1); - PoisonShadow(ptr, size, kAsanStackAfterReturnMagic); -} - } // namespace __asan // ---------------------- Interface ---------------- {{{1 using namespace __asan; // NOLINT -uptr __asan_stack_malloc(uptr size, uptr real_stack) { - if (!flags()->use_fake_stack) return real_stack; - AsanThread *t = asanThreadRegistry().GetCurrent(); - if (!t) { - // TSD is gone, use the real stack. - return real_stack; - } - uptr ptr = t->fake_stack().AllocateStack(size, real_stack); - // Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack); - return ptr; -} - -void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) { - if (!flags()->use_fake_stack) return; - if (ptr != real_stack) { - FakeStack::OnFree(ptr, size, real_stack); - } -} - // ASan allocator doesn't reserve extra bytes, so normally we would // just return "size". uptr __asan_get_estimated_allocated_size(uptr size) { @@ -1040,8 +800,9 @@ uptr __asan_get_allocated_size(const void *p) { uptr allocated_size = malloc_info.AllocationSize((uptr)p); // Die if p is not malloced or if it is already freed. if (allocated_size == 0) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_FATAL_HERE; ReportAsanGetAllocatedSizeNotOwned((uptr)p, &stack); } return allocated_size; } +#endif // ASAN_ALLOCATOR_VERSION diff --git a/libsanitizer/asan/asan_allocator.h b/libsanitizer/asan/asan_allocator.h index 372ca06..4ade352 100644 --- a/libsanitizer/asan/asan_allocator.h +++ b/libsanitizer/asan/asan_allocator.h @@ -15,9 +15,22 @@ #include "asan_internal.h" #include "asan_interceptors.h" +#include "sanitizer_common/sanitizer_list.h" + +// We are in the process of transitioning from the old allocator (version 1) +// to a new one (version 2). The change is quite intrusive so both allocators +// will co-exist in the source base for a while. The actual allocator is chosen +// at build time by redefining this macrozz. +#define ASAN_ALLOCATOR_VERSION 1 namespace __asan { +enum AllocType { + FROM_MALLOC = 1, // Memory block came from malloc, calloc, realloc, etc. + FROM_NEW = 2, // Memory block came from operator new. + FROM_NEW_BR = 3 // Memory block came from operator new [ ] +}; + static const uptr kNumberOfSizeClasses = 255; struct AsanChunk; @@ -32,16 +45,40 @@ class AsanChunkView { uptr FreeTid(); void GetAllocStack(StackTrace *stack); void GetFreeStack(StackTrace *stack); - bool AddrIsInside(uptr addr, uptr access_size, uptr *offset); - bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset); - bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset); + bool AddrIsInside(uptr addr, uptr access_size, uptr *offset) { + if (addr >= Beg() && (addr + access_size) <= End()) { + *offset = addr - Beg(); + return true; + } + return false; + } + bool AddrIsAtLeft(uptr addr, uptr access_size, uptr *offset) { + (void)access_size; + if (addr < Beg()) { + *offset = Beg() - addr; + return true; + } + return false; + } + bool AddrIsAtRight(uptr addr, uptr access_size, uptr *offset) { + if (addr + access_size >= End()) { + if (addr <= End()) + *offset = 0; + else + *offset = addr - End(); + return true; + } + return false; + } + private: AsanChunk *const chunk_; }; AsanChunkView FindHeapChunkByAddress(uptr address); -class AsanChunkFifoList { +// List of AsanChunks with total size. +class AsanChunkFifoList: public IntrusiveList<AsanChunk> { public: explicit AsanChunkFifoList(LinkerInitialized) { } AsanChunkFifoList() { clear(); } @@ -50,12 +87,10 @@ class AsanChunkFifoList { AsanChunk *Pop(); uptr size() { return size_; } void clear() { - first_ = last_ = 0; + IntrusiveList<AsanChunk>::clear(); size_ = 0; } private: - AsanChunk *first_; - AsanChunk *last_; uptr size_; }; @@ -68,7 +103,11 @@ struct AsanThreadLocalMallocStorage { } AsanChunkFifoList quarantine_; +#if ASAN_ALLOCATOR_VERSION == 1 AsanChunk *free_lists_[kNumberOfSizeClasses]; +#else + uptr allocator2_cache[1024]; // Opaque. +#endif void CommitBack(); }; @@ -156,8 +195,9 @@ class FakeStack { FakeFrameLifo call_stack_; }; -void *asan_memalign(uptr alignment, uptr size, StackTrace *stack); -void asan_free(void *ptr, StackTrace *stack); +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type); +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type); void *asan_malloc(uptr size, StackTrace *stack); void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack); @@ -173,5 +213,52 @@ uptr asan_mz_size(const void *ptr); void asan_mz_force_lock(); void asan_mz_force_unlock(); +void PrintInternalAllocatorStats(); + +// Log2 and RoundUpToPowerOfTwo should be inlined for performance. +#if defined(_WIN32) && !defined(__clang__) +extern "C" { +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); // NOLINT +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); // NOLINT +#if defined(_WIN64) +unsigned char _BitScanForward64(unsigned long *index, unsigned __int64 mask); // NOLINT +unsigned char _BitScanReverse64(unsigned long *index, unsigned __int64 mask); // NOLINT +#endif +} +#endif + +static inline uptr Log2(uptr x) { + CHECK(IsPowerOfTwo(x)); +#if !defined(_WIN32) || defined(__clang__) + return __builtin_ctzl(x); +#elif defined(_WIN64) + unsigned long ret; // NOLINT + _BitScanForward64(&ret, x); + return ret; +#else + unsigned long ret; // NOLINT + _BitScanForward(&ret, x); + return ret; +#endif +} + +static inline uptr RoundUpToPowerOfTwo(uptr size) { + CHECK(size); + if (IsPowerOfTwo(size)) return size; + + unsigned long up; // NOLINT +#if !defined(_WIN32) || defined(__clang__) + up = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size); +#elif defined(_WIN64) + _BitScanReverse64(&up, size); +#else + _BitScanReverse(&up, size); +#endif + CHECK(size < (1ULL << (up + 1))); + CHECK(size > (1ULL << up)); + return 1UL << (up + 1); +} + + } // namespace __asan #endif // ASAN_ALLOCATOR_H diff --git a/libsanitizer/asan/asan_allocator2.cc b/libsanitizer/asan/asan_allocator2.cc new file mode 100644 index 0000000..d12ccb7 --- /dev/null +++ b/libsanitizer/asan/asan_allocator2.cc @@ -0,0 +1,714 @@ +//===-- asan_allocator2.cc ------------------------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of AddressSanitizer, an address sanity checker. +// +// Implementation of ASan's memory allocator, 2-nd version. +// This variant uses the allocator from sanitizer_common, i.e. the one shared +// with ThreadSanitizer and MemorySanitizer. +// +// Status: under development, not enabled by default yet. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#if ASAN_ALLOCATOR_VERSION == 2 + +#include "asan_mapping.h" +#include "asan_report.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_list.h" +#include "sanitizer_common/sanitizer_stackdepot.h" + +namespace __asan { + +struct AsanMapUnmapCallback { + void OnMap(uptr p, uptr size) const { + PoisonShadow(p, size, kAsanHeapLeftRedzoneMagic); + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.mmaps++; + thread_stats.mmaped += size; + } + void OnUnmap(uptr p, uptr size) const { + PoisonShadow(p, size, 0); + // We are about to unmap a chunk of user memory. + // Mark the corresponding shadow memory as not needed. + // Since asan's mapping is compacting, the shadow chunk may be + // not page-aligned, so we only flush the page-aligned portion. + uptr page_size = GetPageSizeCached(); + uptr shadow_beg = RoundUpTo(MemToShadow(p), page_size); + uptr shadow_end = RoundDownTo(MemToShadow(p + size), page_size); + FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg); + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.munmaps++; + thread_stats.munmaped += size; + } +}; + +#if SANITIZER_WORDSIZE == 64 +const uptr kAllocatorSpace = 0x600000000000ULL; +const uptr kAllocatorSize = 0x10000000000ULL; // 1T. +typedef DefaultSizeClassMap SizeClassMap; +typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, 0 /*metadata*/, + SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator; +#elif SANITIZER_WORDSIZE == 32 +static const u64 kAddressSpaceSize = 1ULL << 32; +typedef CompactSizeClassMap SizeClassMap; +typedef SizeClassAllocator32<0, kAddressSpaceSize, 16, + SizeClassMap, AsanMapUnmapCallback> PrimaryAllocator; +#endif + +typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; +typedef LargeMmapAllocator<AsanMapUnmapCallback> SecondaryAllocator; +typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, + SecondaryAllocator> Allocator; + +// We can not use THREADLOCAL because it is not supported on some of the +// platforms we care about (OSX 10.6, Android). +// static THREADLOCAL AllocatorCache cache; +AllocatorCache *GetAllocatorCache(AsanThreadLocalMallocStorage *ms) { + CHECK(ms); + CHECK_LE(sizeof(AllocatorCache), sizeof(ms->allocator2_cache)); + return reinterpret_cast<AllocatorCache *>(ms->allocator2_cache); +} + +static Allocator allocator; + +static const uptr kMaxAllowedMallocSize = + FIRST_32_SECOND_64(3UL << 30, 8UL << 30); + +static const uptr kMaxThreadLocalQuarantine = + FIRST_32_SECOND_64(1 << 18, 1 << 20); + +static const uptr kReturnOnZeroMalloc = 2048; // Zero page is protected. + +static int inited = 0; + +static void Init() { + if (inited) return; + __asan_init(); + inited = true; // this must happen before any threads are created. + allocator.Init(); +} + +// Every chunk of memory allocated by this allocator can be in one of 3 states: +// CHUNK_AVAILABLE: the chunk is in the free list and ready to be allocated. +// CHUNK_ALLOCATED: the chunk is allocated and not yet freed. +// CHUNK_QUARANTINE: the chunk was freed and put into quarantine zone. +enum { + CHUNK_AVAILABLE = 0, // 0 is the default value even if we didn't set it. + CHUNK_ALLOCATED = 2, + CHUNK_QUARANTINE = 3 +}; + +// Valid redzone sizes are 16, 32, 64, ... 2048, so we encode them in 3 bits. +// We use adaptive redzones: for larger allocation larger redzones are used. +static u32 RZLog2Size(u32 rz_log) { + CHECK_LT(rz_log, 8); + return 16 << rz_log; +} + +static u32 RZSize2Log(u32 rz_size) { + CHECK_GE(rz_size, 16); + CHECK_LE(rz_size, 2048); + CHECK(IsPowerOfTwo(rz_size)); + u32 res = __builtin_ctz(rz_size) - 4; + CHECK_EQ(rz_size, RZLog2Size(res)); + return res; +} + +static uptr ComputeRZLog(uptr user_requested_size) { + u32 rz_log = + user_requested_size <= 64 - 16 ? 0 : + user_requested_size <= 128 - 32 ? 1 : + user_requested_size <= 512 - 64 ? 2 : + user_requested_size <= 4096 - 128 ? 3 : + user_requested_size <= (1 << 14) - 256 ? 4 : + user_requested_size <= (1 << 15) - 512 ? 5 : + user_requested_size <= (1 << 16) - 1024 ? 6 : 7; + return Max(rz_log, RZSize2Log(flags()->redzone)); +} + +// The memory chunk allocated from the underlying allocator looks like this: +// L L L L L L H H U U U U U U R R +// L -- left redzone words (0 or more bytes) +// H -- ChunkHeader (16 bytes), which is also a part of the left redzone. +// U -- user memory. +// R -- right redzone (0 or more bytes) +// ChunkBase consists of ChunkHeader and other bytes that overlap with user +// memory. + +// If a memory chunk is allocated by memalign and we had to increase the +// allocation size to achieve the proper alignment, then we store this magic +// value in the first uptr word of the memory block and store the address of +// ChunkBase in the next uptr. +// M B ? ? ? L L L L L L H H U U U U U U +// M -- magic value kMemalignMagic +// B -- address of ChunkHeader pointing to the first 'H' +static const uptr kMemalignMagic = 0xCC6E96B9; + +struct ChunkHeader { + // 1-st 8 bytes. + u32 chunk_state : 8; // Must be first. + u32 alloc_tid : 24; + + u32 free_tid : 24; + u32 from_memalign : 1; + u32 alloc_type : 2; + u32 rz_log : 3; + // 2-nd 8 bytes + // This field is used for small sizes. For large sizes it is equal to + // SizeClassMap::kMaxSize and the actual size is stored in the + // SecondaryAllocator's metadata. + u32 user_requested_size; + u32 alloc_context_id; +}; + +struct ChunkBase : ChunkHeader { + // Header2, intersects with user memory. + AsanChunk *next; + u32 free_context_id; +}; + +static const uptr kChunkHeaderSize = sizeof(ChunkHeader); +static const uptr kChunkHeader2Size = sizeof(ChunkBase) - kChunkHeaderSize; +COMPILER_CHECK(kChunkHeaderSize == 16); +COMPILER_CHECK(kChunkHeader2Size <= 16); + +struct AsanChunk: ChunkBase { + uptr Beg() { return reinterpret_cast<uptr>(this) + kChunkHeaderSize; } + uptr UsedSize() { + if (user_requested_size != SizeClassMap::kMaxSize) + return user_requested_size; + return *reinterpret_cast<uptr *>(allocator.GetMetaData(AllocBeg())); + } + void *AllocBeg() { + if (from_memalign) + return allocator.GetBlockBegin(reinterpret_cast<void *>(this)); + return reinterpret_cast<void*>(Beg() - RZLog2Size(rz_log)); + } + // We store the alloc/free stack traces in the chunk itself. + u32 *AllocStackBeg() { + return (u32*)(Beg() - RZLog2Size(rz_log)); + } + uptr AllocStackSize() { + CHECK_LE(RZLog2Size(rz_log), kChunkHeaderSize); + return (RZLog2Size(rz_log) - kChunkHeaderSize) / sizeof(u32); + } + u32 *FreeStackBeg() { + return (u32*)(Beg() + kChunkHeader2Size); + } + uptr FreeStackSize() { + if (user_requested_size < kChunkHeader2Size) return 0; + uptr available = RoundUpTo(user_requested_size, SHADOW_GRANULARITY); + return (available - kChunkHeader2Size) / sizeof(u32); + } +}; + +uptr AsanChunkView::Beg() { return chunk_->Beg(); } +uptr AsanChunkView::End() { return Beg() + UsedSize(); } +uptr AsanChunkView::UsedSize() { return chunk_->UsedSize(); } +uptr AsanChunkView::AllocTid() { return chunk_->alloc_tid; } +uptr AsanChunkView::FreeTid() { return chunk_->free_tid; } + +static void GetStackTraceFromId(u32 id, StackTrace *stack) { + CHECK(id); + uptr size = 0; + const uptr *trace = StackDepotGet(id, &size); + CHECK_LT(size, kStackTraceMax); + internal_memcpy(stack->trace, trace, sizeof(uptr) * size); + stack->size = size; +} + +void AsanChunkView::GetAllocStack(StackTrace *stack) { + if (flags()->use_stack_depot) + GetStackTraceFromId(chunk_->alloc_context_id, stack); + else + StackTrace::UncompressStack(stack, chunk_->AllocStackBeg(), + chunk_->AllocStackSize()); +} + +void AsanChunkView::GetFreeStack(StackTrace *stack) { + if (flags()->use_stack_depot) + GetStackTraceFromId(chunk_->free_context_id, stack); + else + StackTrace::UncompressStack(stack, chunk_->FreeStackBeg(), + chunk_->FreeStackSize()); +} + +class Quarantine: public AsanChunkFifoList { + public: + void SwallowThreadLocalQuarantine(AsanThreadLocalMallocStorage *ms) { + AsanChunkFifoList *q = &ms->quarantine_; + if (!q->size()) return; + SpinMutexLock l(&mutex_); + PushList(q); + PopAndDeallocateLoop(ms); + } + + void BypassThreadLocalQuarantine(AsanChunk *m) { + SpinMutexLock l(&mutex_); + Push(m); + } + + private: + void PopAndDeallocateLoop(AsanThreadLocalMallocStorage *ms) { + while (size() > (uptr)flags()->quarantine_size) { + PopAndDeallocate(ms); + } + } + void PopAndDeallocate(AsanThreadLocalMallocStorage *ms) { + CHECK_GT(size(), 0); + AsanChunk *m = Pop(); + CHECK(m); + CHECK(m->chunk_state == CHUNK_QUARANTINE); + m->chunk_state = CHUNK_AVAILABLE; + CHECK_NE(m->alloc_tid, kInvalidTid); + CHECK_NE(m->free_tid, kInvalidTid); + PoisonShadow(m->Beg(), + RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY), + kAsanHeapLeftRedzoneMagic); + void *p = reinterpret_cast<void *>(m->AllocBeg()); + if (m->from_memalign) { + uptr *memalign_magic = reinterpret_cast<uptr *>(p); + CHECK_EQ(memalign_magic[0], kMemalignMagic); + CHECK_EQ(memalign_magic[1], reinterpret_cast<uptr>(m)); + } + + // Statistics. + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.real_frees++; + thread_stats.really_freed += m->UsedSize(); + + allocator.Deallocate(GetAllocatorCache(ms), p); + } + SpinMutex mutex_; +}; + +static Quarantine quarantine; + +void AsanChunkFifoList::PushList(AsanChunkFifoList *q) { + CHECK(q->size() > 0); + size_ += q->size(); + append_back(q); + q->clear(); +} + +void AsanChunkFifoList::Push(AsanChunk *n) { + push_back(n); + size_ += n->UsedSize(); +} + +// Interesting performance observation: this function takes up to 15% of overal +// allocator time. That's because *first_ has been evicted from cache long time +// ago. Not sure if we can or want to do anything with this. +AsanChunk *AsanChunkFifoList::Pop() { + CHECK(first_); + AsanChunk *res = front(); + size_ -= res->UsedSize(); + pop_front(); + return res; +} + +static void *Allocate(uptr size, uptr alignment, StackTrace *stack, + AllocType alloc_type) { + Init(); + CHECK(stack); + const uptr min_alignment = SHADOW_GRANULARITY; + if (alignment < min_alignment) + alignment = min_alignment; + if (size == 0) { + if (alignment <= kReturnOnZeroMalloc) + return reinterpret_cast<void *>(kReturnOnZeroMalloc); + else + return 0; // 0 bytes with large alignment requested. Just return 0. + } + CHECK(IsPowerOfTwo(alignment)); + uptr rz_log = ComputeRZLog(size); + uptr rz_size = RZLog2Size(rz_log); + uptr rounded_size = RoundUpTo(size, alignment); + if (rounded_size < kChunkHeader2Size) + rounded_size = kChunkHeader2Size; + uptr needed_size = rounded_size + rz_size; + if (alignment > min_alignment) + needed_size += alignment; + bool using_primary_allocator = true; + // If we are allocating from the secondary allocator, there will be no + // automatic right redzone, so add the right redzone manually. + if (!PrimaryAllocator::CanAllocate(needed_size, alignment)) { + needed_size += rz_size; + using_primary_allocator = false; + } + CHECK(IsAligned(needed_size, min_alignment)); + if (size > kMaxAllowedMallocSize || needed_size > kMaxAllowedMallocSize) { + Report("WARNING: AddressSanitizer failed to allocate %p bytes\n", + (void*)size); + return 0; + } + + AsanThread *t = asanThreadRegistry().GetCurrent(); + AllocatorCache *cache = t ? GetAllocatorCache(&t->malloc_storage()) : 0; + void *allocated = allocator.Allocate(cache, needed_size, 8, false); + uptr alloc_beg = reinterpret_cast<uptr>(allocated); + uptr alloc_end = alloc_beg + needed_size; + uptr beg_plus_redzone = alloc_beg + rz_size; + uptr user_beg = beg_plus_redzone; + if (!IsAligned(user_beg, alignment)) + user_beg = RoundUpTo(user_beg, alignment); + uptr user_end = user_beg + size; + CHECK_LE(user_end, alloc_end); + uptr chunk_beg = user_beg - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + m->chunk_state = CHUNK_ALLOCATED; + m->alloc_type = alloc_type; + m->rz_log = rz_log; + u32 alloc_tid = t ? t->tid() : 0; + m->alloc_tid = alloc_tid; + CHECK_EQ(alloc_tid, m->alloc_tid); // Does alloc_tid fit into the bitfield? + m->free_tid = kInvalidTid; + m->from_memalign = user_beg != beg_plus_redzone; + if (m->from_memalign) { + CHECK_LE(beg_plus_redzone + 2 * sizeof(uptr), user_beg); + uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg); + memalign_magic[0] = kMemalignMagic; + memalign_magic[1] = chunk_beg; + } + if (using_primary_allocator) { + CHECK(size); + m->user_requested_size = size; + CHECK(allocator.FromPrimary(allocated)); + } else { + CHECK(!allocator.FromPrimary(allocated)); + m->user_requested_size = SizeClassMap::kMaxSize; + uptr *meta = reinterpret_cast<uptr *>(allocator.GetMetaData(allocated)); + meta[0] = size; + meta[1] = chunk_beg; + } + + if (flags()->use_stack_depot) { + m->alloc_context_id = StackDepotPut(stack->trace, stack->size); + } else { + m->alloc_context_id = 0; + StackTrace::CompressStack(stack, m->AllocStackBeg(), m->AllocStackSize()); + } + + uptr size_rounded_down_to_granularity = RoundDownTo(size, SHADOW_GRANULARITY); + // Unpoison the bulk of the memory region. + if (size_rounded_down_to_granularity) + PoisonShadow(user_beg, size_rounded_down_to_granularity, 0); + // Deal with the end of the region if size is not aligned to granularity. + if (size != size_rounded_down_to_granularity && flags()->poison_heap) { + u8 *shadow = (u8*)MemToShadow(user_beg + size_rounded_down_to_granularity); + *shadow = size & (SHADOW_GRANULARITY - 1); + } + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.mallocs++; + thread_stats.malloced += size; + thread_stats.malloced_redzones += needed_size - size; + uptr class_id = Min(kNumberOfSizeClasses, SizeClassMap::ClassID(needed_size)); + thread_stats.malloced_by_size[class_id]++; + if (needed_size > SizeClassMap::kMaxSize) + thread_stats.malloc_large++; + + void *res = reinterpret_cast<void *>(user_beg); + ASAN_MALLOC_HOOK(res, size); + return res; +} + +static void Deallocate(void *ptr, StackTrace *stack, AllocType alloc_type) { + uptr p = reinterpret_cast<uptr>(ptr); + if (p == 0 || p == kReturnOnZeroMalloc) return; + uptr chunk_beg = p - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + + // Flip the chunk_state atomically to avoid race on double-free. + u8 old_chunk_state = atomic_exchange((atomic_uint8_t*)m, CHUNK_QUARANTINE, + memory_order_acq_rel); + + if (old_chunk_state == CHUNK_QUARANTINE) + ReportDoubleFree((uptr)ptr, stack); + else if (old_chunk_state != CHUNK_ALLOCATED) + ReportFreeNotMalloced((uptr)ptr, stack); + CHECK(old_chunk_state == CHUNK_ALLOCATED); + if (m->alloc_type != alloc_type && flags()->alloc_dealloc_mismatch) + ReportAllocTypeMismatch((uptr)ptr, stack, + (AllocType)m->alloc_type, (AllocType)alloc_type); + + CHECK_GE(m->alloc_tid, 0); + if (SANITIZER_WORDSIZE == 64) // On 32-bits this resides in user area. + CHECK_EQ(m->free_tid, kInvalidTid); + AsanThread *t = asanThreadRegistry().GetCurrent(); + m->free_tid = t ? t->tid() : 0; + if (flags()->use_stack_depot) { + m->free_context_id = StackDepotPut(stack->trace, stack->size); + } else { + m->free_context_id = 0; + StackTrace::CompressStack(stack, m->FreeStackBeg(), m->FreeStackSize()); + } + CHECK(m->chunk_state == CHUNK_QUARANTINE); + // Poison the region. + PoisonShadow(m->Beg(), + RoundUpTo(m->UsedSize(), SHADOW_GRANULARITY), + kAsanHeapFreeMagic); + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.frees++; + thread_stats.freed += m->UsedSize(); + + // Push into quarantine. + if (t) { + AsanChunkFifoList &q = t->malloc_storage().quarantine_; + q.Push(m); + + if (q.size() > kMaxThreadLocalQuarantine) + quarantine.SwallowThreadLocalQuarantine(&t->malloc_storage()); + } else { + quarantine.BypassThreadLocalQuarantine(m); + } + + ASAN_FREE_HOOK(ptr); +} + +static void *Reallocate(void *old_ptr, uptr new_size, StackTrace *stack) { + CHECK(old_ptr && new_size); + uptr p = reinterpret_cast<uptr>(old_ptr); + uptr chunk_beg = p - kChunkHeaderSize; + AsanChunk *m = reinterpret_cast<AsanChunk *>(chunk_beg); + + AsanStats &thread_stats = asanThreadRegistry().GetCurrentThreadStats(); + thread_stats.reallocs++; + thread_stats.realloced += new_size; + + CHECK(m->chunk_state == CHUNK_ALLOCATED); + uptr old_size = m->UsedSize(); + uptr memcpy_size = Min(new_size, old_size); + void *new_ptr = Allocate(new_size, 8, stack, FROM_MALLOC); + if (new_ptr) { + CHECK(REAL(memcpy) != 0); + REAL(memcpy)(new_ptr, old_ptr, memcpy_size); + Deallocate(old_ptr, stack, FROM_MALLOC); + } + return new_ptr; +} + +static AsanChunk *GetAsanChunkByAddr(uptr p) { + void *ptr = reinterpret_cast<void *>(p); + uptr alloc_beg = reinterpret_cast<uptr>(allocator.GetBlockBegin(ptr)); + if (!alloc_beg) return 0; + uptr *memalign_magic = reinterpret_cast<uptr *>(alloc_beg); + if (memalign_magic[0] == kMemalignMagic) { + AsanChunk *m = reinterpret_cast<AsanChunk *>(memalign_magic[1]); + CHECK(m->from_memalign); + return m; + } + if (!allocator.FromPrimary(ptr)) { + uptr *meta = reinterpret_cast<uptr *>( + allocator.GetMetaData(reinterpret_cast<void *>(alloc_beg))); + AsanChunk *m = reinterpret_cast<AsanChunk *>(meta[1]); + return m; + } + uptr actual_size = allocator.GetActuallyAllocatedSize(ptr); + CHECK_LE(actual_size, SizeClassMap::kMaxSize); + // We know the actually allocted size, but we don't know the redzone size. + // Just try all possible redzone sizes. + for (u32 rz_log = 0; rz_log < 8; rz_log++) { + u32 rz_size = RZLog2Size(rz_log); + uptr max_possible_size = actual_size - rz_size; + if (ComputeRZLog(max_possible_size) != rz_log) + continue; + return reinterpret_cast<AsanChunk *>( + alloc_beg + rz_size - kChunkHeaderSize); + } + return 0; +} + +static uptr AllocationSize(uptr p) { + AsanChunk *m = GetAsanChunkByAddr(p); + if (!m) return 0; + if (m->chunk_state != CHUNK_ALLOCATED) return 0; + if (m->Beg() != p) return 0; + return m->UsedSize(); +} + +// We have an address between two chunks, and we want to report just one. +AsanChunk *ChooseChunk(uptr addr, + AsanChunk *left_chunk, AsanChunk *right_chunk) { + // Prefer an allocated chunk over freed chunk and freed chunk + // over available chunk. + if (left_chunk->chunk_state != right_chunk->chunk_state) { + if (left_chunk->chunk_state == CHUNK_ALLOCATED) + return left_chunk; + if (right_chunk->chunk_state == CHUNK_ALLOCATED) + return right_chunk; + if (left_chunk->chunk_state == CHUNK_QUARANTINE) + return left_chunk; + if (right_chunk->chunk_state == CHUNK_QUARANTINE) + return right_chunk; + } + // Same chunk_state: choose based on offset. + uptr l_offset = 0, r_offset = 0; + CHECK(AsanChunkView(left_chunk).AddrIsAtRight(addr, 1, &l_offset)); + CHECK(AsanChunkView(right_chunk).AddrIsAtLeft(addr, 1, &r_offset)); + if (l_offset < r_offset) + return left_chunk; + return right_chunk; +} + +AsanChunkView FindHeapChunkByAddress(uptr addr) { + AsanChunk *m1 = GetAsanChunkByAddr(addr); + if (!m1) return AsanChunkView(m1); + uptr offset = 0; + if (AsanChunkView(m1).AddrIsAtLeft(addr, 1, &offset)) { + // The address is in the chunk's left redzone, so maybe it is actually + // a right buffer overflow from the other chunk to the left. + // Search a bit to the left to see if there is another chunk. + AsanChunk *m2 = 0; + for (uptr l = 1; l < GetPageSizeCached(); l++) { + m2 = GetAsanChunkByAddr(addr - l); + if (m2 == m1) continue; // Still the same chunk. + break; + } + if (m2 && AsanChunkView(m2).AddrIsAtRight(addr, 1, &offset)) + m1 = ChooseChunk(addr, m2, m1); + } + return AsanChunkView(m1); +} + +void AsanThreadLocalMallocStorage::CommitBack() { + quarantine.SwallowThreadLocalQuarantine(this); + allocator.SwallowCache(GetAllocatorCache(this)); +} + +void PrintInternalAllocatorStats() { + allocator.PrintStats(); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_memalign(uptr alignment, uptr size, StackTrace *stack, + AllocType alloc_type) { + return Allocate(size, alignment, stack, alloc_type); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void asan_free(void *ptr, StackTrace *stack, AllocType alloc_type) { + Deallocate(ptr, stack, alloc_type); +} + +SANITIZER_INTERFACE_ATTRIBUTE +void *asan_malloc(uptr size, StackTrace *stack) { + return Allocate(size, 8, stack, FROM_MALLOC); +} + +void *asan_calloc(uptr nmemb, uptr size, StackTrace *stack) { + void *ptr = Allocate(nmemb * size, 8, stack, FROM_MALLOC); + if (ptr) + REAL(memset)(ptr, 0, nmemb * size); + return ptr; +} + +void *asan_realloc(void *p, uptr size, StackTrace *stack) { + if (p == 0) + return Allocate(size, 8, stack, FROM_MALLOC); + if (size == 0) { + Deallocate(p, stack, FROM_MALLOC); + return 0; + } + return Reallocate(p, size, stack); +} + +void *asan_valloc(uptr size, StackTrace *stack) { + return Allocate(size, GetPageSizeCached(), stack, FROM_MALLOC); +} + +void *asan_pvalloc(uptr size, StackTrace *stack) { + uptr PageSize = GetPageSizeCached(); + size = RoundUpTo(size, PageSize); + if (size == 0) { + // pvalloc(0) should allocate one page. + size = PageSize; + } + return Allocate(size, PageSize, stack, FROM_MALLOC); +} + +int asan_posix_memalign(void **memptr, uptr alignment, uptr size, + StackTrace *stack) { + void *ptr = Allocate(size, alignment, stack, FROM_MALLOC); + CHECK(IsAligned((uptr)ptr, alignment)); + *memptr = ptr; + return 0; +} + +uptr asan_malloc_usable_size(void *ptr, StackTrace *stack) { + CHECK(stack); + if (ptr == 0) return 0; + uptr usable_size = AllocationSize(reinterpret_cast<uptr>(ptr)); + if (flags()->check_malloc_usable_size && (usable_size == 0)) + ReportMallocUsableSizeNotOwned((uptr)ptr, stack); + return usable_size; +} + +uptr asan_mz_size(const void *ptr) { + UNIMPLEMENTED(); + return 0; +} + +void asan_mz_force_lock() { + UNIMPLEMENTED(); +} + +void asan_mz_force_unlock() { + UNIMPLEMENTED(); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +// ASan allocator doesn't reserve extra bytes, so normally we would +// just return "size". We don't want to expose our redzone sizes, etc here. +uptr __asan_get_estimated_allocated_size(uptr size) { + return size; +} + +bool __asan_get_ownership(const void *p) { + return AllocationSize(reinterpret_cast<uptr>(p)) > 0; +} + +uptr __asan_get_allocated_size(const void *p) { + if (p == 0) return 0; + uptr allocated_size = AllocationSize(reinterpret_cast<uptr>(p)); + // Die if p is not malloced or if it is already freed. + if (allocated_size == 0) { + GET_STACK_TRACE_FATAL_HERE; + ReportAsanGetAllocatedSizeNotOwned(reinterpret_cast<uptr>(p), &stack); + } + return allocated_size; +} + +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +// Provide default (no-op) implementation of malloc hooks. +extern "C" { +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_malloc_hook(void *ptr, uptr size) { + (void)ptr; + (void)size; +} +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +void __asan_free_hook(void *ptr) { + (void)ptr; +} +} // extern "C" +#endif + + +#endif // ASAN_ALLOCATOR_VERSION diff --git a/libsanitizer/asan/asan_fake_stack.cc b/libsanitizer/asan/asan_fake_stack.cc new file mode 100644 index 0000000..e8d1e78 --- /dev/null +++ b/libsanitizer/asan/asan_fake_stack.cc @@ -0,0 +1,180 @@ +//===-- asan_fake_stack.cc ------------------------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of AddressSanitizer, an address sanity checker. +// +// FakeStack is used to detect use-after-return bugs. +//===----------------------------------------------------------------------===// +#include "asan_allocator.h" +#include "asan_thread.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" + +namespace __asan { + +FakeStack::FakeStack() { + CHECK(REAL(memset) != 0); + REAL(memset)(this, 0, sizeof(*this)); +} + +bool FakeStack::AddrIsInSizeClass(uptr addr, uptr size_class) { + uptr mem = allocated_size_classes_[size_class]; + uptr size = ClassMmapSize(size_class); + bool res = mem && addr >= mem && addr < mem + size; + return res; +} + +uptr FakeStack::AddrIsInFakeStack(uptr addr) { + for (uptr i = 0; i < kNumberOfSizeClasses; i++) { + if (AddrIsInSizeClass(addr, i)) return allocated_size_classes_[i]; + } + return 0; +} + +// We may want to compute this during compilation. +inline uptr FakeStack::ComputeSizeClass(uptr alloc_size) { + uptr rounded_size = RoundUpToPowerOfTwo(alloc_size); + uptr log = Log2(rounded_size); + CHECK(alloc_size <= (1UL << log)); + if (!(alloc_size > (1UL << (log-1)))) { + Printf("alloc_size %zu log %zu\n", alloc_size, log); + } + CHECK(alloc_size > (1UL << (log-1))); + uptr res = log < kMinStackFrameSizeLog ? 0 : log - kMinStackFrameSizeLog; + CHECK(res < kNumberOfSizeClasses); + CHECK(ClassSize(res) >= rounded_size); + return res; +} + +void FakeFrameFifo::FifoPush(FakeFrame *node) { + CHECK(node); + node->next = 0; + if (first_ == 0 && last_ == 0) { + first_ = last_ = node; + } else { + CHECK(first_); + CHECK(last_); + last_->next = node; + last_ = node; + } +} + +FakeFrame *FakeFrameFifo::FifoPop() { + CHECK(first_ && last_ && "Exhausted fake stack"); + FakeFrame *res = 0; + if (first_ == last_) { + res = first_; + first_ = last_ = 0; + } else { + res = first_; + first_ = first_->next; + } + return res; +} + +void FakeStack::Init(uptr stack_size) { + stack_size_ = stack_size; + alive_ = true; +} + +void FakeStack::Cleanup() { + alive_ = false; + for (uptr i = 0; i < kNumberOfSizeClasses; i++) { + uptr mem = allocated_size_classes_[i]; + if (mem) { + PoisonShadow(mem, ClassMmapSize(i), 0); + allocated_size_classes_[i] = 0; + UnmapOrDie((void*)mem, ClassMmapSize(i)); + } + } +} + +uptr FakeStack::ClassMmapSize(uptr size_class) { + return RoundUpToPowerOfTwo(stack_size_); +} + +void FakeStack::AllocateOneSizeClass(uptr size_class) { + CHECK(ClassMmapSize(size_class) >= GetPageSizeCached()); + uptr new_mem = (uptr)MmapOrDie( + ClassMmapSize(size_class), __FUNCTION__); + // Printf("T%d new_mem[%zu]: %p-%p mmap %zu\n", + // asanThreadRegistry().GetCurrent()->tid(), + // size_class, new_mem, new_mem + ClassMmapSize(size_class), + // ClassMmapSize(size_class)); + uptr i; + for (i = 0; i < ClassMmapSize(size_class); + i += ClassSize(size_class)) { + size_classes_[size_class].FifoPush((FakeFrame*)(new_mem + i)); + } + CHECK(i == ClassMmapSize(size_class)); + allocated_size_classes_[size_class] = new_mem; +} + +uptr FakeStack::AllocateStack(uptr size, uptr real_stack) { + if (!alive_) return real_stack; + CHECK(size <= kMaxStackMallocSize && size > 1); + uptr size_class = ComputeSizeClass(size); + if (!allocated_size_classes_[size_class]) { + AllocateOneSizeClass(size_class); + } + FakeFrame *fake_frame = size_classes_[size_class].FifoPop(); + CHECK(fake_frame); + fake_frame->size_minus_one = size - 1; + fake_frame->real_stack = real_stack; + while (FakeFrame *top = call_stack_.top()) { + if (top->real_stack > real_stack) break; + call_stack_.LifoPop(); + DeallocateFrame(top); + } + call_stack_.LifoPush(fake_frame); + uptr ptr = (uptr)fake_frame; + PoisonShadow(ptr, size, 0); + return ptr; +} + +void FakeStack::DeallocateFrame(FakeFrame *fake_frame) { + CHECK(alive_); + uptr size = fake_frame->size_minus_one + 1; + uptr size_class = ComputeSizeClass(size); + CHECK(allocated_size_classes_[size_class]); + uptr ptr = (uptr)fake_frame; + CHECK(AddrIsInSizeClass(ptr, size_class)); + CHECK(AddrIsInSizeClass(ptr + size - 1, size_class)); + size_classes_[size_class].FifoPush(fake_frame); +} + +void FakeStack::OnFree(uptr ptr, uptr size, uptr real_stack) { + FakeFrame *fake_frame = (FakeFrame*)ptr; + CHECK(fake_frame->magic = kRetiredStackFrameMagic); + CHECK(fake_frame->descr != 0); + CHECK(fake_frame->size_minus_one == size - 1); + PoisonShadow(ptr, size, kAsanStackAfterReturnMagic); +} + +} // namespace __asan + +// ---------------------- Interface ---------------- {{{1 +using namespace __asan; // NOLINT + +uptr __asan_stack_malloc(uptr size, uptr real_stack) { + if (!flags()->use_fake_stack) return real_stack; + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (!t) { + // TSD is gone, use the real stack. + return real_stack; + } + uptr ptr = t->fake_stack().AllocateStack(size, real_stack); + // Printf("__asan_stack_malloc %p %zu %p\n", ptr, size, real_stack); + return ptr; +} + +void __asan_stack_free(uptr ptr, uptr size, uptr real_stack) { + if (!flags()->use_fake_stack) return; + if (ptr != real_stack) { + FakeStack::OnFree(ptr, size, real_stack); + } +} diff --git a/libsanitizer/asan/asan_flags.h b/libsanitizer/asan/asan_flags.h index a0dcf3e..49a9073 100644 --- a/libsanitizer/asan/asan_flags.h +++ b/libsanitizer/asan/asan_flags.h @@ -43,7 +43,7 @@ struct Flags { int report_globals; // If set, attempts to catch initialization order issues. bool check_initialization_order; - // Max number of stack frames kept for each allocation. + // Max number of stack frames kept for each allocation/deallocation. int malloc_context_size; // If set, uses custom wrappers and replacements for libc string functions // to find more errors. @@ -93,6 +93,17 @@ struct Flags { bool print_full_thread_history; // ASan will write logs to "log_path.pid" instead of stderr. const char *log_path; + // Use fast (frame-pointer-based) unwinder on fatal errors (if available). + bool fast_unwind_on_fatal; + // Use fast (frame-pointer-based) unwinder on malloc/free (if available). + bool fast_unwind_on_malloc; + // Poison (or not) the heap memory on [de]allocation. Zero value is useful + // for benchmarking the allocator or instrumentator. + bool poison_heap; + // Report errors on malloc/delete, new/free, new/delete[], etc. + bool alloc_dealloc_mismatch; + // Use stack depot instead of storing stacks in the redzones. + bool use_stack_depot; }; Flags *flags(); diff --git a/libsanitizer/asan/asan_intercepted_functions.h b/libsanitizer/asan/asan_intercepted_functions.h index 89f91ad..8341bc6 100644 --- a/libsanitizer/asan/asan_intercepted_functions.h +++ b/libsanitizer/asan/asan_intercepted_functions.h @@ -14,6 +14,7 @@ #include "asan_internal.h" #include "interception/interception.h" +#include "sanitizer_common/sanitizer_platform_interceptors.h" using __sanitizer::uptr; @@ -39,8 +40,10 @@ using __sanitizer::uptr; #if defined(__linux__) # define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 1 +# define ASAN_INTERCEPT_PRCTL 1 #else # define ASAN_USE_ALIAS_ATTRIBUTE_FOR_INDEX 0 +# define ASAN_INTERCEPT_PRCTL 0 #endif #if !defined(__APPLE__) @@ -149,10 +152,23 @@ DECLARE_FUNCTION_AND_WRAPPER(long long, atoll, const char *nptr); // NOLINT DECLARE_FUNCTION_AND_WRAPPER(long long, strtoll, const char *nptr, char **endptr, int base); // NOLINT # endif +// unistd.h +# if SANITIZER_INTERCEPT_READ +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, read, int fd, void *buf, SIZE_T count); +# endif +# if SANITIZER_INTERCEPT_PREAD +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread, int fd, void *buf, + SIZE_T count, OFF_T offset); +# endif +# if SANITIZER_INTERCEPT_PREAD64 +DECLARE_FUNCTION_AND_WRAPPER(SSIZE_T, pread64, int fd, void *buf, + SIZE_T count, OFF64_T offset); +# endif + # if ASAN_INTERCEPT_MLOCKX // mlock/munlock -DECLARE_FUNCTION_AND_WRAPPER(int, mlock, const void *addr, size_t len); -DECLARE_FUNCTION_AND_WRAPPER(int, munlock, const void *addr, size_t len); +DECLARE_FUNCTION_AND_WRAPPER(int, mlock, const void *addr, SIZE_T len); +DECLARE_FUNCTION_AND_WRAPPER(int, munlock, const void *addr, SIZE_T len); DECLARE_FUNCTION_AND_WRAPPER(int, mlockall, int flags); DECLARE_FUNCTION_AND_WRAPPER(int, munlockall, void); # endif diff --git a/libsanitizer/asan/asan_interceptors.cc b/libsanitizer/asan/asan_interceptors.cc index 5b544c8..26daee1 100644 --- a/libsanitizer/asan/asan_interceptors.cc +++ b/libsanitizer/asan/asan_interceptors.cc @@ -25,38 +25,20 @@ namespace __asan { -// Instruments read/write access to a single byte in memory. -// On error calls __asan_report_error, which aborts the program. -#define ACCESS_ADDRESS(address, isWrite) do { \ - if (!AddrIsInMem(address) || AddressIsPoisoned(address)) { \ - GET_CURRENT_PC_BP_SP; \ - __asan_report_error(pc, bp, sp, address, isWrite, /* access_size */ 1); \ - } \ -} while (0) - // We implement ACCESS_MEMORY_RANGE, ASAN_READ_RANGE, // and ASAN_WRITE_RANGE as macro instead of function so // that no extra frames are created, and stack trace contains // relevant information only. - -// Instruments read/write access to a memory range. -// More complex implementation is possible, for now just -// checking the first and the last byte of a range. -#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \ - if (size > 0) { \ - uptr ptr = (uptr)(offset); \ - ACCESS_ADDRESS(ptr, isWrite); \ - ACCESS_ADDRESS(ptr + (size) - 1, isWrite); \ - } \ +// We check all shadow bytes. +#define ACCESS_MEMORY_RANGE(offset, size, isWrite) do { \ + if (uptr __ptr = __asan_region_is_poisoned((uptr)(offset), size)) { \ + GET_CURRENT_PC_BP_SP; \ + __asan_report_error(pc, bp, sp, __ptr, isWrite, /* access_size */1); \ + } \ } while (0) -#define ASAN_READ_RANGE(offset, size) do { \ - ACCESS_MEMORY_RANGE(offset, size, false); \ -} while (0) - -#define ASAN_WRITE_RANGE(offset, size) do { \ - ACCESS_MEMORY_RANGE(offset, size, true); \ -} while (0) +#define ASAN_READ_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, false) +#define ASAN_WRITE_RANGE(offset, size) ACCESS_MEMORY_RANGE(offset, size, true); // Behavior of functions like "memcpy" or "strcpy" is undefined // if memory intervals overlap. We report error in this case. @@ -69,7 +51,7 @@ static inline bool RangesOverlap(const char *offset1, uptr length1, const char *offset1 = (const char*)_offset1; \ const char *offset2 = (const char*)_offset2; \ if (RangesOverlap(offset1, length1, offset2, length2)) { \ - GET_STACK_TRACE_HERE(kStackTraceMax); \ + GET_STACK_TRACE_FATAL_HERE; \ ReportStringFunctionMemoryRangesOverlap(name, offset1, length1, \ offset2, length2, &stack); \ } \ @@ -96,6 +78,11 @@ static inline uptr MaybeRealStrnlen(const char *s, uptr maxlen) { // ---------------------- Wrappers ---------------- {{{1 using namespace __asan; // NOLINT +#define COMMON_INTERCEPTOR_WRITE_RANGE(ptr, size) ASAN_WRITE_RANGE(ptr, size) +#define COMMON_INTERCEPTOR_READ_RANGE(ptr, size) ASAN_READ_RANGE(ptr, size) +#define COMMON_INTERCEPTOR_ENTER(func, ...) ENSURE_ASAN_INITED() +#include "sanitizer_common/sanitizer_common_interceptors.h" + static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { AsanThread *t = (AsanThread*)arg; asanThreadRegistry().SetCurrent(t); @@ -105,7 +92,7 @@ static thread_return_t THREAD_CALLING_CONV asan_thread_start(void *arg) { #if ASAN_INTERCEPT_PTHREAD_CREATE INTERCEPTOR(int, pthread_create, void *thread, void *attr, void *(*start_routine)(void*), void *arg) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack); asanThreadRegistry().RegisterThread(t); @@ -175,6 +162,25 @@ INTERCEPTOR(void, siglongjmp, void *env, int val) { } #endif +#if ASAN_INTERCEPT_PRCTL +#define PR_SET_NAME 15 +INTERCEPTOR(int, prctl, int option, + unsigned long arg2, unsigned long arg3, // NOLINT + unsigned long arg4, unsigned long arg5) { // NOLINT + int res = REAL(prctl(option, arg2, arg3, arg4, arg5)); + if (option == PR_SET_NAME) { + AsanThread *t = asanThreadRegistry().GetCurrent(); + if (t) { + char buff[17]; + internal_strncpy(buff, (char*)arg2, 16); + buff[16] = 0; + t->summary()->set_name(buff); + } + } + return res; +} +#endif + #if ASAN_INTERCEPT___CXA_THROW INTERCEPTOR(void, __cxa_throw, void *a, void *b, void *c) { CHECK(REAL(__cxa_throw)); @@ -256,8 +262,8 @@ INTERCEPTOR(void*, memcpy, void *to, const void *from, uptr size) { // See http://llvm.org/bugs/show_bug.cgi?id=11763. CHECK_RANGES_OVERLAP("memcpy", to, size, from, size); } - ASAN_WRITE_RANGE(from, size); - ASAN_READ_RANGE(to, size); + ASAN_READ_RANGE(from, size); + ASAN_WRITE_RANGE(to, size); } #if MAC_INTERPOSE_FUNCTIONS // Interposing of resolver functions is broken on Mac OS 10.7 and 10.8. @@ -275,8 +281,8 @@ INTERCEPTOR(void*, memmove, void *to, const void *from, uptr size) { } ENSURE_ASAN_INITED(); if (flags()->replace_intrin) { - ASAN_WRITE_RANGE(from, size); - ASAN_READ_RANGE(to, size); + ASAN_READ_RANGE(from, size); + ASAN_WRITE_RANGE(to, size); } #if MAC_INTERPOSE_FUNCTIONS // Interposing of resolver functions is broken on Mac OS 10.7 and 10.8. @@ -621,7 +627,7 @@ INTERCEPTOR_WINAPI(DWORD, CreateThread, void* security, uptr stack_size, DWORD (__stdcall *start_routine)(void*), void* arg, DWORD flags, void* tid) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; u32 current_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); AsanThread *t = AsanThread::Create(current_tid, start_routine, arg, &stack); asanThreadRegistry().RegisterThread(t); @@ -646,6 +652,9 @@ void InitializeAsanInterceptors() { #if MAC_INTERPOSE_FUNCTIONS return; #endif + + SANITIZER_COMMON_INTERCEPTORS_INIT; + // Intercept mem* functions. ASAN_INTERCEPT_FUNC(memcmp); ASAN_INTERCEPT_FUNC(memmove); @@ -718,6 +727,9 @@ void InitializeAsanInterceptors() { #if ASAN_INTERCEPT_SIGLONGJMP ASAN_INTERCEPT_FUNC(siglongjmp); #endif +#if ASAN_INTERCEPT_PRCTL + ASAN_INTERCEPT_FUNC(prctl); +#endif // Intercept exception handling functions. #if ASAN_INTERCEPT___CXA_THROW diff --git a/libsanitizer/asan/asan_internal.h b/libsanitizer/asan/asan_internal.h index ac1ee7d..1717fce 100644 --- a/libsanitizer/asan/asan_internal.h +++ b/libsanitizer/asan/asan_internal.h @@ -81,9 +81,9 @@ // If set, values like allocator chunk size, as well as defaults for some flags // will be changed towards less memory overhead. #ifndef ASAN_LOW_MEMORY -# ifdef ASAN_ANDROID +#if SANITIZER_WORDSIZE == 32 # define ASAN_LOW_MEMORY 1 -# else +#else # define ASAN_LOW_MEMORY 0 # endif #endif @@ -143,6 +143,15 @@ bool PlatformHasDifferentMemcpyAndMemmove(); # define PLATFORM_HAS_DIFFERENT_MEMCPY_AND_MEMMOVE true #endif // __APPLE__ +// Add convenient macro for interface functions that may be represented as +// weak hooks. +#define ASAN_MALLOC_HOOK(ptr, size) \ + if (&__asan_malloc_hook) __asan_malloc_hook(ptr, size) +#define ASAN_FREE_HOOK(ptr) \ + if (&__asan_free_hook) __asan_free_hook(ptr) +#define ASAN_ON_ERROR() \ + if (&__asan_on_error) __asan_on_error() + extern int asan_inited; // Used to avoid infinite recursion in __asan_init(). extern bool asan_init_is_running; diff --git a/libsanitizer/asan/asan_linux.cc b/libsanitizer/asan/asan_linux.cc index 4479994..0e6c628 100644 --- a/libsanitizer/asan/asan_linux.cc +++ b/libsanitizer/asan/asan_linux.cc @@ -120,53 +120,21 @@ void AsanLock::Unlock() { pthread_mutex_unlock((pthread_mutex_t*)&opaque_storage_); } -#ifdef __arm__ -#define UNWIND_STOP _URC_END_OF_STACK -#define UNWIND_CONTINUE _URC_NO_REASON -#else -#define UNWIND_STOP _URC_NORMAL_STOP -#define UNWIND_CONTINUE _URC_NO_REASON -#endif - -uptr Unwind_GetIP(struct _Unwind_Context *ctx) { -#ifdef __arm__ - uptr val; - _Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE, - 15 /* r15 = PC */, _UVRSD_UINT32, &val); - CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed"); - // Clear the Thumb bit. - return val & ~(uptr)1; -#else - return _Unwind_GetIP(ctx); +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { +#if defined(__arm__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__sparc__) + fast = false; #endif -} - -_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, - void *param) { - StackTrace *b = (StackTrace*)param; - CHECK(b->size < b->max_size); - uptr pc = Unwind_GetIP(ctx); - b->trace[b->size++] = pc; - if (b->size == b->max_size) return UNWIND_STOP; - return UNWIND_CONTINUE; -} - -void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) { + if (!fast) + return stack->SlowUnwindStack(pc, max_s); stack->size = 0; stack->trace[0] = pc; - if ((max_s) > 1) { + if (max_s > 1) { stack->max_size = max_s; -#if defined(__arm__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__sparc__) - _Unwind_Backtrace(Unwind_Trace, stack); - // Pop off the two ASAN functions from the backtrace. - stack->PopStackFrames(2); -#else if (!asan_inited) return; if (AsanThread *t = asanThreadRegistry().GetCurrent()) stack->FastUnwindStack(pc, bp, t->stack_top(), t->stack_bottom()); -#endif } } diff --git a/libsanitizer/asan/asan_mac.cc b/libsanitizer/asan/asan_mac.cc index db0b6d3..094c69f 100644 --- a/libsanitizer/asan/asan_mac.cc +++ b/libsanitizer/asan/asan_mac.cc @@ -158,7 +158,8 @@ void AsanLock::Unlock() { OSSpinLockUnlock((OSSpinLock*)&opaque_storage_); } -void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) { +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { + (void)fast; stack->size = 0; stack->trace[0] = pc; if ((max_s) > 1) { @@ -306,7 +307,7 @@ void asan_register_worker_thread(int parent_tid, StackTrace *stack) { // alloc_asan_context(). extern "C" void asan_dispatch_call_block_and_release(void *block) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *context = (asan_block_context_t*)block; if (flags()->verbosity >= 2) { Report("asan_dispatch_call_block_and_release(): " @@ -316,7 +317,7 @@ void asan_dispatch_call_block_and_release(void *block) { asan_register_worker_thread(context->parent_tid, &stack); // Call the original dispatcher for the block. context->func(context->block); - asan_free(context, &stack); + asan_free(context, &stack, FROM_MALLOC); } } // namespace __asan @@ -341,7 +342,7 @@ asan_block_context_t *alloc_asan_context(void *ctxt, dispatch_function_t func, #define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f) \ INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void *ctxt, \ dispatch_function_t func) { \ - GET_STACK_TRACE_HERE(kStackTraceMax); \ + GET_STACK_TRACE_THREAD; \ asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); \ if (flags()->verbosity >= 2) { \ Report(#dispatch_x_f "(): context: %p, pthread_self: %p\n", \ @@ -359,7 +360,7 @@ INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f) INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); if (flags()->verbosity >= 2) { Report("dispatch_after_f: %p\n", asan_ctxt); @@ -372,7 +373,7 @@ INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group, dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = alloc_asan_context(ctxt, func, &stack); if (flags()->verbosity >= 2) { Report("dispatch_group_async_f(): context: %p, pthread_self: %p\n", @@ -407,7 +408,7 @@ void dispatch_source_set_event_handler(dispatch_source_t ds, void(^work)(void)); void (^asan_block)(void); \ int parent_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); \ asan_block = ^(void) { \ - GET_STACK_TRACE_HERE(kStackTraceMax); \ + GET_STACK_TRACE_THREAD; \ asan_register_worker_thread(parent_tid, &stack); \ work(); \ } @@ -457,15 +458,15 @@ void *wrap_workitem_func(void *arg) { asan_block_context_t *ctxt = (asan_block_context_t*)arg; worker_t fn = (worker_t)(ctxt->func); void *result = fn(ctxt->block); - GET_STACK_TRACE_HERE(kStackTraceMax); - asan_free(arg, &stack); + GET_STACK_TRACE_THREAD; + asan_free(arg, &stack, FROM_MALLOC); return result; } INTERCEPTOR(int, pthread_workqueue_additem_np, pthread_workqueue_t workq, void *(*workitem_func)(void *), void * workitem_arg, pthread_workitem_handle_t * itemhandlep, unsigned int *gencountp) { - GET_STACK_TRACE_HERE(kStackTraceMax); + GET_STACK_TRACE_THREAD; asan_block_context_t *asan_ctxt = (asan_block_context_t*) asan_malloc(sizeof(asan_block_context_t), &stack); asan_ctxt->block = workitem_arg; diff --git a/libsanitizer/asan/asan_malloc_linux.cc b/libsanitizer/asan/asan_malloc_linux.cc index b00bbe5..e33d0c0 100644 --- a/libsanitizer/asan/asan_malloc_linux.cc +++ b/libsanitizer/asan/asan_malloc_linux.cc @@ -17,6 +17,8 @@ #include "asan_interceptors.h" #include "asan_internal.h" #include "asan_stack.h" +#include "asan_thread_registry.h" +#include "sanitizer/asan_interface.h" #if ASAN_ANDROID DECLARE_REAL_AND_INTERCEPTOR(void*, malloc, uptr size) @@ -57,17 +59,17 @@ void ReplaceSystemMalloc() { using namespace __asan; // NOLINT INTERCEPTOR(void, free, void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } INTERCEPTOR(void, cfree, void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } INTERCEPTOR(void*, malloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -83,25 +85,25 @@ INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) { CHECK(allocated < kCallocPoolSize); return mem; } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } INTERCEPTOR(void*, realloc, void *ptr, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } INTERCEPTOR(void*, memalign, uptr boundary, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(boundary, size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(boundary, size, &stack, FROM_MALLOC); } INTERCEPTOR(void*, __libc_memalign, uptr align, uptr s) ALIAS("memalign"); INTERCEPTOR(uptr, malloc_usable_size, void *ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc_usable_size(ptr, &stack); } @@ -124,19 +126,23 @@ INTERCEPTOR(int, mallopt, int cmd, int value) { } INTERCEPTOR(int, posix_memalign, void **memptr, uptr alignment, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; // Printf("posix_memalign: %zx %zu\n", alignment, size); return asan_posix_memalign(memptr, alignment, size, &stack); } INTERCEPTOR(void*, valloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_valloc(size, &stack); } INTERCEPTOR(void*, pvalloc, uptr size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_pvalloc(size, &stack); } +INTERCEPTOR(void, malloc_stats, void) { + __asan_print_accumulated_stats(); +} + #endif // __linux__ diff --git a/libsanitizer/asan/asan_malloc_mac.cc b/libsanitizer/asan/asan_malloc_mac.cc index b56b620..97aa442 100644 --- a/libsanitizer/asan/asan_malloc_mac.cc +++ b/libsanitizer/asan/asan_malloc_mac.cc @@ -90,8 +90,8 @@ INTERCEPTOR(void, free, void *ptr) { #endif } else { if (!asan_mz_size(ptr)) ptr = get_saved_cfallocator_ref(ptr); - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } } @@ -128,7 +128,7 @@ void *mz_malloc(malloc_zone_t *zone, size_t size) { CHECK(system_malloc_zone); return malloc_zone_malloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -137,7 +137,7 @@ void *cf_malloc(CFIndex size, CFOptionFlags hint, void *info) { CHECK(system_malloc_zone); return malloc_zone_malloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -153,7 +153,7 @@ void *mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { CHECK(allocated < kCallocPoolSize); return mem; } - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } @@ -162,8 +162,8 @@ void *mz_valloc(malloc_zone_t *zone, size_t size) { CHECK(system_malloc_zone); return malloc_zone_valloc(system_malloc_zone, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(GetPageSizeCached(), size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(GetPageSizeCached(), size, &stack, FROM_MALLOC); } #define GET_ZONE_FOR_PTR(ptr) \ @@ -173,8 +173,8 @@ void *mz_valloc(malloc_zone_t *zone, size_t size) { void ALWAYS_INLINE free_common(void *context, void *ptr) { if (!ptr) return; if (asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + asan_free(ptr, &stack, FROM_MALLOC); } else { // If the pointer does not belong to any of the zones, use one of the // fallback methods to free memory. @@ -188,9 +188,9 @@ void ALWAYS_INLINE free_common(void *context, void *ptr) { // If the memory chunk pointer was moved to store additional // CFAllocatorRef, fix it back. ptr = get_saved_cfallocator_ref(ptr); - GET_STACK_TRACE_HERE_FOR_FREE(ptr); + GET_STACK_TRACE_FREE; if (!flags()->mac_ignore_invalid_free) { - asan_free(ptr, &stack); + asan_free(ptr, &stack, FROM_MALLOC); } else { GET_ZONE_FOR_PTR(ptr); WarnMacFreeUnallocated((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); @@ -211,17 +211,17 @@ void cf_free(void *ptr, void *info) { void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) { if (!ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } else { if (asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } else { // We can't recover from reallocating an unknown address, because // this would require reading at most |size| bytes from // potentially unaccessible memory. - GET_STACK_TRACE_HERE_FOR_FREE(ptr); + GET_STACK_TRACE_FREE; GET_ZONE_FOR_PTR(ptr); ReportMacMzReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); } @@ -230,17 +230,17 @@ void *mz_realloc(malloc_zone_t *zone, void *ptr, size_t size) { void *cf_realloc(void *ptr, CFIndex size, CFOptionFlags hint, void *info) { if (!ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } else { if (asan_mz_size(ptr)) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } else { // We can't recover from reallocating an unknown address, because // this would require reading at most |size| bytes from // potentially unaccessible memory. - GET_STACK_TRACE_HERE_FOR_FREE(ptr); + GET_STACK_TRACE_FREE; GET_ZONE_FOR_PTR(ptr); ReportMacCfReallocUnknown((uptr)ptr, (uptr)zone_ptr, zone_name, &stack); } @@ -259,8 +259,8 @@ void *mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { CHECK(system_malloc_zone); return malloc_zone_memalign(system_malloc_zone, align, size); } - GET_STACK_TRACE_HERE_FOR_MALLOC; - return asan_memalign(align, size, &stack); + GET_STACK_TRACE_MALLOC; + return asan_memalign(align, size, &stack, FROM_MALLOC); } // This function is currently unused, and we build with -Werror. diff --git a/libsanitizer/asan/asan_malloc_win.cc b/libsanitizer/asan/asan_malloc_win.cc index 7389a25..437079f 100644 --- a/libsanitizer/asan/asan_malloc_win.cc +++ b/libsanitizer/asan/asan_malloc_win.cc @@ -29,8 +29,8 @@ using namespace __asan; // NOLINT extern "C" { void free(void *ptr) { - GET_STACK_TRACE_HERE_FOR_FREE(ptr); - return asan_free(ptr, &stack); + GET_STACK_TRACE_FREE; + return asan_free(ptr, &stack, FROM_MALLOC); } void _free_dbg(void* ptr, int) { @@ -42,7 +42,7 @@ void cfree(void *ptr) { } void *malloc(size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc(size, &stack); } @@ -51,7 +51,7 @@ void* _malloc_dbg(size_t size, int , const char*, int) { } void *calloc(size_t nmemb, size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_calloc(nmemb, size, &stack); } @@ -64,7 +64,7 @@ void *_calloc_impl(size_t nmemb, size_t size, int *errno_tmp) { } void *realloc(void *ptr, size_t size) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_realloc(ptr, size, &stack); } @@ -83,7 +83,7 @@ void* _recalloc(void* p, size_t n, size_t elem_size) { } size_t _msize(void *ptr) { - GET_STACK_TRACE_HERE_FOR_MALLOC; + GET_STACK_TRACE_MALLOC; return asan_malloc_usable_size(ptr, &stack); } diff --git a/libsanitizer/asan/asan_mapping.h b/libsanitizer/asan/asan_mapping.h index 85d1129..54e21b7 100644 --- a/libsanitizer/asan/asan_mapping.h +++ b/libsanitizer/asan/asan_mapping.h @@ -18,8 +18,8 @@ // http://code.google.com/p/address-sanitizer/wiki/AddressSanitizerAlgorithm #if ASAN_FLEXIBLE_MAPPING_AND_OFFSET == 1 -extern __attribute__((visibility("default"))) uptr __asan_mapping_scale; -extern __attribute__((visibility("default"))) uptr __asan_mapping_offset; +extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_scale; +extern SANITIZER_INTERFACE_ATTRIBUTE uptr __asan_mapping_offset; # define SHADOW_SCALE (__asan_mapping_scale) # define SHADOW_OFFSET (__asan_mapping_offset) #else diff --git a/libsanitizer/asan/asan_new_delete.cc b/libsanitizer/asan/asan_new_delete.cc index 8132e58..6597b93 100644 --- a/libsanitizer/asan/asan_new_delete.cc +++ b/libsanitizer/asan/asan_new_delete.cc @@ -33,32 +33,34 @@ namespace std { struct nothrow_t {}; } // namespace std -#define OPERATOR_NEW_BODY \ - GET_STACK_TRACE_HERE_FOR_MALLOC;\ - return asan_memalign(0, size, &stack); +#define OPERATOR_NEW_BODY(type) \ + GET_STACK_TRACE_MALLOC;\ + return asan_memalign(0, size, &stack, type); INTERCEPTOR_ATTRIBUTE -void *operator new(size_t size) { OPERATOR_NEW_BODY; } +void *operator new(size_t size) { OPERATOR_NEW_BODY(FROM_NEW); } INTERCEPTOR_ATTRIBUTE -void *operator new[](size_t size) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size) { OPERATOR_NEW_BODY(FROM_NEW_BR); } INTERCEPTOR_ATTRIBUTE -void *operator new(size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } +void *operator new(size_t size, std::nothrow_t const&) +{ OPERATOR_NEW_BODY(FROM_NEW); } INTERCEPTOR_ATTRIBUTE -void *operator new[](size_t size, std::nothrow_t const&) { OPERATOR_NEW_BODY; } +void *operator new[](size_t size, std::nothrow_t const&) +{ OPERATOR_NEW_BODY(FROM_NEW_BR); } -#define OPERATOR_DELETE_BODY \ - GET_STACK_TRACE_HERE_FOR_FREE(ptr);\ - asan_free(ptr, &stack); +#define OPERATOR_DELETE_BODY(type) \ + GET_STACK_TRACE_FREE;\ + asan_free(ptr, &stack, type); INTERCEPTOR_ATTRIBUTE -void operator delete(void *ptr) { OPERATOR_DELETE_BODY; } +void operator delete(void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW); } INTERCEPTOR_ATTRIBUTE -void operator delete[](void *ptr) { OPERATOR_DELETE_BODY; } +void operator delete[](void *ptr) { OPERATOR_DELETE_BODY(FROM_NEW_BR); } INTERCEPTOR_ATTRIBUTE void operator delete(void *ptr, std::nothrow_t const&) -{ OPERATOR_DELETE_BODY; } +{ OPERATOR_DELETE_BODY(FROM_NEW); } INTERCEPTOR_ATTRIBUTE void operator delete[](void *ptr, std::nothrow_t const&) -{ OPERATOR_DELETE_BODY; } +{ OPERATOR_DELETE_BODY(FROM_NEW_BR); } #endif diff --git a/libsanitizer/asan/asan_poisoning.cc b/libsanitizer/asan/asan_poisoning.cc index 449299a..a00baff 100644 --- a/libsanitizer/asan/asan_poisoning.cc +++ b/libsanitizer/asan/asan_poisoning.cc @@ -14,10 +14,12 @@ #include "asan_internal.h" #include "asan_mapping.h" #include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_libc.h" namespace __asan { void PoisonShadow(uptr addr, uptr size, u8 value) { + if (!flags()->poison_heap) return; CHECK(AddrIsAlignedByGranularity(addr)); CHECK(AddrIsAlignedByGranularity(addr + size)); uptr shadow_beg = MemToShadow(addr); @@ -30,6 +32,7 @@ void PoisonShadowPartialRightRedzone(uptr addr, uptr size, uptr redzone_size, u8 value) { + if (!flags()->poison_heap) return; CHECK(AddrIsAlignedByGranularity(addr)); u8 *shadow = (u8*)MemToShadow(addr); for (uptr i = 0; i < redzone_size; @@ -150,6 +153,33 @@ bool __asan_address_is_poisoned(void const volatile *addr) { return __asan::AddressIsPoisoned((uptr)addr); } +uptr __asan_region_is_poisoned(uptr beg, uptr size) { + if (!size) return 0; + uptr end = beg + size; + if (!AddrIsInMem(beg)) return beg; + if (!AddrIsInMem(end)) return end; + uptr aligned_b = RoundUpTo(beg, SHADOW_GRANULARITY); + uptr aligned_e = RoundDownTo(end, SHADOW_GRANULARITY); + uptr shadow_beg = MemToShadow(aligned_b); + uptr shadow_end = MemToShadow(aligned_e); + // First check the first and the last application bytes, + // then check the SHADOW_GRANULARITY-aligned region by calling + // mem_is_zero on the corresponding shadow. + if (!__asan::AddressIsPoisoned(beg) && + !__asan::AddressIsPoisoned(end - 1) && + (shadow_end <= shadow_beg || + __sanitizer::mem_is_zero((const char *)shadow_beg, + shadow_end - shadow_beg))) + return 0; + // The fast check failed, so we have a poisoned byte somewhere. + // Find it slowly. + for (; beg < end; beg++) + if (__asan::AddressIsPoisoned(beg)) + return beg; + UNREACHABLE("mem_is_zero returned false, but poisoned byte was not found"); + return 0; +} + // This is a simplified version of __asan_(un)poison_memory_region, which // assumes that left border of region to be poisoned is properly aligned. static void PoisonAlignedStackMemory(uptr addr, uptr size, bool do_poison) { @@ -166,7 +196,7 @@ static void PoisonAlignedStackMemory(uptr addr, uptr size, bool do_poison) { // If possible, mark all the bytes mapping to last shadow byte as // unaddressable. if (end_value > 0 && end_value <= end_offset) - *shadow_end = kAsanStackUseAfterScopeMagic; + *shadow_end = (s8)kAsanStackUseAfterScopeMagic; } else { // If necessary, mark few first bytes mapping to last shadow byte // as addressable diff --git a/libsanitizer/asan/asan_report.cc b/libsanitizer/asan/asan_report.cc index 9804ddd..ed18ab2 100644 --- a/libsanitizer/asan/asan_report.cc +++ b/libsanitizer/asan/asan_report.cc @@ -16,6 +16,9 @@ #include "asan_stack.h" #include "asan_thread.h" #include "asan_thread_registry.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_report_decorator.h" +#include "sanitizer_common/sanitizer_symbolizer.h" namespace __asan { @@ -38,14 +41,79 @@ void AppendToErrorMessageBuffer(const char *buffer) { } } +// ---------------------- Decorator ------------------------------ {{{1 +bool PrintsToTtyCached() { + static int cached = 0; + static bool prints_to_tty; + if (!cached) { // Ok wrt threads since we are printing only from one thread. + prints_to_tty = PrintsToTty(); + cached = 1; + } + return prints_to_tty; +} +class Decorator: private __sanitizer::AnsiColorDecorator { + public: + Decorator() : __sanitizer::AnsiColorDecorator(PrintsToTtyCached()) { } + const char *Warning() { return Red(); } + const char *EndWarning() { return Default(); } + const char *Access() { return Blue(); } + const char *EndAccess() { return Default(); } + const char *Location() { return Green(); } + const char *EndLocation() { return Default(); } + const char *Allocation() { return Magenta(); } + const char *EndAllocation() { return Default(); } + + const char *ShadowByte(u8 byte) { + switch (byte) { + case kAsanHeapLeftRedzoneMagic: + case kAsanHeapRightRedzoneMagic: + return Red(); + case kAsanHeapFreeMagic: + return Magenta(); + case kAsanStackLeftRedzoneMagic: + case kAsanStackMidRedzoneMagic: + case kAsanStackRightRedzoneMagic: + case kAsanStackPartialRedzoneMagic: + return Red(); + case kAsanStackAfterReturnMagic: + return Magenta(); + case kAsanInitializationOrderMagic: + return Cyan(); + case kAsanUserPoisonedMemoryMagic: + return Blue(); + case kAsanStackUseAfterScopeMagic: + return Magenta(); + case kAsanGlobalRedzoneMagic: + return Red(); + case kAsanInternalHeapMagic: + return Yellow(); + default: + return Default(); + } + } + const char *EndShadowByte() { return Default(); } +}; + // ---------------------- Helper functions ----------------------- {{{1 -static void PrintBytes(const char *before, uptr *a) { - u8 *bytes = (u8*)a; - uptr byte_num = (SANITIZER_WORDSIZE) / 8; - Printf("%s%p:", before, (void*)a); - for (uptr i = 0; i < byte_num; i++) { - Printf(" %x%x", bytes[i] >> 4, bytes[i] & 15); +static void PrintShadowByte(const char *before, u8 byte, + const char *after = "\n") { + Decorator d; + Printf("%s%s%x%x%s%s", before, + d.ShadowByte(byte), byte >> 4, byte & 15, d.EndShadowByte(), after); +} + +static void PrintShadowBytes(const char *before, u8 *bytes, + u8 *guilty, uptr n) { + Decorator d; + if (before) + Printf("%s%p:", before, bytes); + for (uptr i = 0; i < n; i++) { + u8 *p = bytes + i; + const char *before = p == guilty ? "[" : + p - 1 == guilty ? "" : " "; + const char *after = p == guilty ? "]" : ""; + PrintShadowByte(before, *p, after); } Printf("\n"); } @@ -54,15 +122,35 @@ static void PrintShadowMemoryForAddress(uptr addr) { if (!AddrIsInMem(addr)) return; uptr shadow_addr = MemToShadow(addr); - Printf("Shadow byte and word:\n"); - Printf(" %p: %x\n", (void*)shadow_addr, *(unsigned char*)shadow_addr); - uptr aligned_shadow = shadow_addr & ~(kWordSize - 1); - PrintBytes(" ", (uptr*)(aligned_shadow)); - Printf("More shadow bytes:\n"); - for (int i = -4; i <= 4; i++) { + const uptr n_bytes_per_row = 16; + uptr aligned_shadow = shadow_addr & ~(n_bytes_per_row - 1); + Printf("Shadow bytes around the buggy address:\n"); + for (int i = -5; i <= 5; i++) { const char *prefix = (i == 0) ? "=>" : " "; - PrintBytes(prefix, (uptr*)(aligned_shadow + i * kWordSize)); + PrintShadowBytes(prefix, + (u8*)(aligned_shadow + i * n_bytes_per_row), + (u8*)shadow_addr, n_bytes_per_row); } + Printf("Shadow byte legend (one shadow byte represents %d " + "application bytes):\n", (int)SHADOW_GRANULARITY); + PrintShadowByte(" Addressable: ", 0); + Printf(" Partially addressable: "); + for (uptr i = 1; i < SHADOW_GRANULARITY; i++) + PrintShadowByte("", i, " "); + Printf("\n"); + PrintShadowByte(" Heap left redzone: ", kAsanHeapLeftRedzoneMagic); + PrintShadowByte(" Heap righ redzone: ", kAsanHeapRightRedzoneMagic); + PrintShadowByte(" Freed Heap region: ", kAsanHeapFreeMagic); + PrintShadowByte(" Stack left redzone: ", kAsanStackLeftRedzoneMagic); + PrintShadowByte(" Stack mid redzone: ", kAsanStackMidRedzoneMagic); + PrintShadowByte(" Stack right redzone: ", kAsanStackRightRedzoneMagic); + PrintShadowByte(" Stack partial redzone: ", kAsanStackPartialRedzoneMagic); + PrintShadowByte(" Stack after return: ", kAsanStackAfterReturnMagic); + PrintShadowByte(" Stack use after scope: ", kAsanStackUseAfterScopeMagic); + PrintShadowByte(" Global redzone: ", kAsanGlobalRedzoneMagic); + PrintShadowByte(" Global init order: ", kAsanInitializationOrderMagic); + PrintShadowByte(" Poisoned by user: ", kAsanUserPoisonedMemoryMagic); + PrintShadowByte(" ASan internal: ", kAsanInternalHeapMagic); } static void PrintZoneForPointer(uptr ptr, uptr zone_ptr, @@ -98,6 +186,8 @@ static void PrintGlobalNameIfASCII(const __asan_global &g) { bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) { if (addr < g.beg - kGlobalAndStackRedzone) return false; if (addr >= g.beg + g.size_with_redzone) return false; + Decorator d; + Printf("%s", d.Location()); Printf("%p is located ", (void*)addr); if (addr < g.beg) { Printf("%zd bytes to the left", g.beg - addr); @@ -108,6 +198,7 @@ bool DescribeAddressRelativeToGlobal(uptr addr, const __asan_global &g) { } Printf(" of global variable '%s' (0x%zx) of size %zu\n", g.name, g.beg, g.size); + Printf("%s", d.EndLocation()); PrintGlobalNameIfASCII(g); return true; } @@ -151,9 +242,12 @@ bool DescribeAddressIfStack(uptr addr, uptr access_size) { internal_strncat(buf, frame_descr, Min(kBufSize, static_cast<sptr>(name_end - frame_descr))); + Decorator d; + Printf("%s", d.Location()); Printf("Address %p is located at offset %zu " "in frame <%s> of T%d's stack:\n", - (void*)addr, offset, buf, t->tid()); + (void*)addr, offset, Demangle(buf), t->tid()); + Printf("%s", d.EndLocation()); // Report the number of stack objects. char *p; uptr n_objects = internal_simple_strtoll(name_end, &p, 10); @@ -187,6 +281,8 @@ bool DescribeAddressIfStack(uptr addr, uptr access_size) { static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr, uptr access_size) { uptr offset; + Decorator d; + Printf("%s", d.Location()); Printf("%p is located ", (void*)addr); if (chunk.AddrIsInside(addr, access_size, &offset)) { Printf("%zu bytes inside of", offset); @@ -199,6 +295,26 @@ static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr, } Printf(" %zu-byte region [%p,%p)\n", chunk.UsedSize(), (void*)(chunk.Beg()), (void*)(chunk.End())); + Printf("%s", d.EndLocation()); +} + +// Return " (thread_name) " or an empty string if the name is empty. +const char *ThreadNameWithParenthesis(AsanThreadSummary *t, char buff[], + uptr buff_len) { + const char *name = t->name(); + if (*name == 0) return ""; + buff[0] = 0; + internal_strncat(buff, " (", 3); + internal_strncat(buff, name, buff_len - 4); + internal_strncat(buff, ")", 2); + return buff; +} + +const char *ThreadNameWithParenthesis(u32 tid, char buff[], + uptr buff_len) { + if (tid == kInvalidTid) return ""; + AsanThreadSummary *t = asanThreadRegistry().FindByTid(tid); + return ThreadNameWithParenthesis(t, buff, buff_len); } void DescribeHeapAddress(uptr addr, uptr access_size) { @@ -212,20 +328,31 @@ void DescribeHeapAddress(uptr addr, uptr access_size) { chunk.GetAllocStack(&alloc_stack); AsanThread *t = asanThreadRegistry().GetCurrent(); CHECK(t); + char tname[128]; + Decorator d; if (chunk.FreeTid() != kInvalidTid) { AsanThreadSummary *free_thread = asanThreadRegistry().FindByTid(chunk.FreeTid()); - Printf("freed by thread T%d here:\n", free_thread->tid()); + Printf("%sfreed by thread T%d%s here:%s\n", d.Allocation(), + free_thread->tid(), + ThreadNameWithParenthesis(free_thread, tname, sizeof(tname)), + d.EndAllocation()); StackTrace free_stack; chunk.GetFreeStack(&free_stack); PrintStack(&free_stack); - Printf("previously allocated by thread T%d here:\n", alloc_thread->tid()); + Printf("%spreviously allocated by thread T%d%s here:%s\n", + d.Allocation(), alloc_thread->tid(), + ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)), + d.EndAllocation()); PrintStack(&alloc_stack); DescribeThread(t->summary()); DescribeThread(free_thread); DescribeThread(alloc_thread); } else { - Printf("allocated by thread T%d here:\n", alloc_thread->tid()); + Printf("%sallocated by thread T%d%s here:%s\n", d.Allocation(), + alloc_thread->tid(), + ThreadNameWithParenthesis(alloc_thread, tname, sizeof(tname)), + d.EndAllocation()); PrintStack(&alloc_stack); DescribeThread(t->summary()); DescribeThread(alloc_thread); @@ -254,8 +381,13 @@ void DescribeThread(AsanThreadSummary *summary) { return; } summary->set_announced(true); - Printf("Thread T%d created by T%d here:\n", - summary->tid(), summary->parent_tid()); + char tname[128]; + Printf("Thread T%d%s", summary->tid(), + ThreadNameWithParenthesis(summary->tid(), tname, sizeof(tname))); + Printf(" created by T%d%s here:\n", + summary->parent_tid(), + ThreadNameWithParenthesis(summary->parent_tid(), + tname, sizeof(tname))); PrintStack(summary->stack()); // Recursively described parent thread if needed. if (flags()->print_full_thread_history) { @@ -291,7 +423,7 @@ class ScopedInErrorReport { // Die() to bypass any additional checks. Exit(flags()->exitcode); } - __asan_on_error(); + ASAN_ON_ERROR(); reporting_thread_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); Printf("====================================================" "=============\n"); @@ -322,44 +454,79 @@ class ScopedInErrorReport { void ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: SEGV on unknown address %p" " (pc %p sp %p bp %p T%d)\n", (void*)addr, (void*)pc, (void*)sp, (void*)bp, asanThreadRegistry().GetCurrentTidOrInvalid()); + Printf("%s", d.EndWarning()); Printf("AddressSanitizer can not provide additional info.\n"); - GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); + GET_STACK_TRACE_FATAL(pc, bp); PrintStack(&stack); } void ReportDoubleFree(uptr addr, StackTrace *stack) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: attempting double-free on %p:\n", addr); + Printf("%s", d.EndWarning()); PrintStack(stack); DescribeHeapAddress(addr, 1); } void ReportFreeNotMalloced(uptr addr, StackTrace *stack) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: attempting free on address " "which was not malloc()-ed: %p\n", addr); + Printf("%s", d.EndWarning()); PrintStack(stack); DescribeHeapAddress(addr, 1); } +void ReportAllocTypeMismatch(uptr addr, StackTrace *stack, + AllocType alloc_type, + AllocType dealloc_type) { + static const char *alloc_names[] = + {"INVALID", "malloc", "operator new", "operator new []"}; + static const char *dealloc_names[] = + {"INVALID", "free", "operator delete", "operator delete []"}; + CHECK_NE(alloc_type, dealloc_type); + ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); + Report("ERROR: AddressSanitizer: alloc-dealloc-mismatch (%s vs %s) on %p\n", + alloc_names[alloc_type], dealloc_names[dealloc_type], addr); + Printf("%s", d.EndWarning()); + PrintStack(stack); + DescribeHeapAddress(addr, 1); + Report("HINT: if you don't care about these warnings you may set " + "ASAN_OPTIONS=alloc_dealloc_mismatch=0\n"); +} + void ReportMallocUsableSizeNotOwned(uptr addr, StackTrace *stack) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: attempting to call " "malloc_usable_size() for pointer which is " "not owned: %p\n", addr); + Printf("%s", d.EndWarning()); PrintStack(stack); DescribeHeapAddress(addr, 1); } void ReportAsanGetAllocatedSizeNotOwned(uptr addr, StackTrace *stack) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: attempting to call " "__asan_get_allocated_size() for pointer which is " "not owned: %p\n", addr); + Printf("%s", d.EndWarning()); PrintStack(stack); DescribeHeapAddress(addr, 1); } @@ -368,9 +535,12 @@ void ReportStringFunctionMemoryRangesOverlap( const char *function, const char *offset1, uptr length1, const char *offset2, uptr length2, StackTrace *stack) { ScopedInErrorReport in_report; + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: %s-param-overlap: " "memory ranges [%p,%p) and [%p, %p) overlap\n", \ function, offset1, offset1 + length1, offset2, offset2 + length2); + Printf("%s", d.EndWarning()); PrintStack(stack); DescribeAddress((uptr)offset1, length1); DescribeAddress((uptr)offset2, length2); @@ -463,17 +633,23 @@ void __asan_report_error(uptr pc, uptr bp, uptr sp, break; } } - + Decorator d; + Printf("%s", d.Warning()); Report("ERROR: AddressSanitizer: %s on address " "%p at pc 0x%zx bp 0x%zx sp 0x%zx\n", bug_descr, (void*)addr, pc, bp, sp); + Printf("%s", d.EndWarning()); u32 curr_tid = asanThreadRegistry().GetCurrentTidOrInvalid(); - Printf("%s of size %zu at %p thread T%d\n", - access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", - access_size, (void*)addr, curr_tid); - - GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp); + char tname[128]; + Printf("%s%s of size %zu at %p thread T%d%s%s\n", + d.Access(), + access_size ? (is_write ? "WRITE" : "READ") : "ACCESS", + access_size, (void*)addr, curr_tid, + ThreadNameWithParenthesis(curr_tid, tname, sizeof(tname)), + d.EndAccess()); + + GET_STACK_TRACE_FATAL(pc, bp); PrintStack(&stack); DescribeAddress(addr, access_size); @@ -491,7 +667,13 @@ void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) { } } +void __asan_describe_address(uptr addr) { + DescribeAddress(addr, 1); +} + +#if !SANITIZER_SUPPORTS_WEAK_HOOKS // Provide default implementation of __asan_on_error that does nothing // and may be overriden by user. SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE void __asan_on_error() {} +#endif diff --git a/libsanitizer/asan/asan_report.h b/libsanitizer/asan/asan_report.h index 9710bd7..a7e0e58 100644 --- a/libsanitizer/asan/asan_report.h +++ b/libsanitizer/asan/asan_report.h @@ -10,6 +10,7 @@ // ASan-private header for error reporting functions. //===----------------------------------------------------------------------===// +#include "asan_allocator.h" #include "asan_internal.h" #include "asan_thread.h" #include "sanitizer/asan_interface.h" @@ -32,6 +33,9 @@ void DescribeThread(AsanThreadSummary *summary); void NORETURN ReportSIGSEGV(uptr pc, uptr sp, uptr bp, uptr addr); void NORETURN ReportDoubleFree(uptr addr, StackTrace *stack); void NORETURN ReportFreeNotMalloced(uptr addr, StackTrace *stack); +void NORETURN ReportAllocTypeMismatch(uptr addr, StackTrace *stack, + AllocType alloc_type, + AllocType dealloc_type); void NORETURN ReportMallocUsableSizeNotOwned(uptr addr, StackTrace *stack); void NORETURN ReportAsanGetAllocatedSizeNotOwned(uptr addr, diff --git a/libsanitizer/asan/asan_rtl.cc b/libsanitizer/asan/asan_rtl.cc index cf0025a..6480bf4 100644 --- a/libsanitizer/asan/asan_rtl.cc +++ b/libsanitizer/asan/asan_rtl.cc @@ -52,7 +52,7 @@ static void AsanCheckFailed(const char *file, int line, const char *cond, file, line, cond, (uptr)v1, (uptr)v2); // FIXME: check for infinite recursion without a thread-local counter here. PRINT_CURRENT_STACK(); - ShowStatsAndAbort(); + Die(); } // -------------------------- Flags ------------------------- {{{1 @@ -64,6 +64,10 @@ Flags *flags() { return &asan_flags; } +static const char *MaybeCallAsanDefaultOptions() { + return (&__asan_default_options) ? __asan_default_options() : ""; +} + static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->quarantine_size, "quarantine_size"); ParseFlag(str, &f->symbolize, "symbolize"); @@ -98,21 +102,20 @@ static void ParseFlagsFromString(Flags *f, const char *str) { ParseFlag(str, &f->allow_reexec, "allow_reexec"); ParseFlag(str, &f->print_full_thread_history, "print_full_thread_history"); ParseFlag(str, &f->log_path, "log_path"); + ParseFlag(str, &f->fast_unwind_on_fatal, "fast_unwind_on_fatal"); + ParseFlag(str, &f->fast_unwind_on_malloc, "fast_unwind_on_malloc"); + ParseFlag(str, &f->poison_heap, "poison_heap"); + ParseFlag(str, &f->alloc_dealloc_mismatch, "alloc_dealloc_mismatch"); + ParseFlag(str, &f->use_stack_depot, "use_stack_depot"); } -extern "C" { -SANITIZER_WEAK_ATTRIBUTE -SANITIZER_INTERFACE_ATTRIBUTE -const char* __asan_default_options() { return ""; } -} // extern "C" - void InitializeFlags(Flags *f, const char *env) { internal_memset(f, 0, sizeof(*f)); f->quarantine_size = (ASAN_LOW_MEMORY) ? 1UL << 26 : 1UL << 28; f->symbolize = false; f->verbosity = 0; - f->redzone = (ASAN_LOW_MEMORY) ? 64 : 128; + f->redzone = ASAN_ALLOCATOR_VERSION == 2 ? 16 : (ASAN_LOW_MEMORY) ? 64 : 128; f->debug = false; f->report_globals = 1; f->check_initialization_order = true; @@ -137,12 +140,17 @@ void InitializeFlags(Flags *f, const char *env) { f->allow_reexec = true; f->print_full_thread_history = true; f->log_path = 0; + f->fast_unwind_on_fatal = true; + f->fast_unwind_on_malloc = true; + f->poison_heap = true; + f->alloc_dealloc_mismatch = true; + f->use_stack_depot = true; // Only affects allocator2. // Override from user-specified string. - ParseFlagsFromString(f, __asan_default_options()); + ParseFlagsFromString(f, MaybeCallAsanDefaultOptions()); if (flags()->verbosity) { Report("Using the defaults from __asan_default_options: %s\n", - __asan_default_options()); + MaybeCallAsanDefaultOptions()); } // Override from command line. @@ -239,15 +247,12 @@ static NOINLINE void force_interface_symbols() { case 27: __asan_set_error_exit_code(0); break; case 28: __asan_stack_free(0, 0, 0); break; case 29: __asan_stack_malloc(0, 0); break; - case 30: __asan_on_error(); break; - case 31: __asan_default_options(); break; - case 32: __asan_before_dynamic_init(0, 0); break; - case 33: __asan_after_dynamic_init(); break; - case 34: __asan_malloc_hook(0, 0); break; - case 35: __asan_free_hook(0); break; - case 36: __asan_symbolize(0, 0, 0); break; - case 37: __asan_poison_stack_memory(0, 0); break; - case 38: __asan_unpoison_stack_memory(0, 0); break; + case 30: __asan_before_dynamic_init(0, 0); break; + case 31: __asan_after_dynamic_init(); break; + case 32: __asan_poison_stack_memory(0, 0); break; + case 33: __asan_unpoison_stack_memory(0, 0); break; + case 34: __asan_region_is_poisoned(0, 0); break; + case 35: __asan_describe_address(0); break; } } @@ -261,6 +266,13 @@ static void asan_atexit() { // ---------------------- Interface ---------------- {{{1 using namespace __asan; // NOLINT +#if !SANITIZER_SUPPORTS_WEAK_HOOKS +extern "C" { +SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE +const char* __asan_default_options() { return ""; } +} // extern "C" +#endif + int NOINLINE __asan_set_error_exit_code(int exit_code) { int old = flags()->exitcode; flags()->exitcode = exit_code; diff --git a/libsanitizer/asan/asan_stack.cc b/libsanitizer/asan/asan_stack.cc index 2531a7f..9b6a28e 100644 --- a/libsanitizer/asan/asan_stack.cc +++ b/libsanitizer/asan/asan_stack.cc @@ -15,9 +15,15 @@ namespace __asan { +static bool MaybeCallAsanSymbolize(const void *pc, char *out_buffer, + int out_size) { + return (&__asan_symbolize) ? __asan_symbolize(pc, out_buffer, out_size) + : false; +} + void PrintStack(StackTrace *stack) { stack->PrintStack(stack->trace, stack->size, flags()->symbolize, - flags()->strip_path_prefix, __asan_symbolize); + flags()->strip_path_prefix, MaybeCallAsanSymbolize); } } // namespace __asan @@ -27,7 +33,7 @@ void PrintStack(StackTrace *stack) { // Provide default implementation of __asan_symbolize that does nothing // and may be overriden by user if he wants to use his own symbolization. // ASan on Windows has its own implementation of this. -#ifndef _WIN32 +#if !defined(_WIN32) && !SANITIZER_SUPPORTS_WEAK_HOOKS SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE NOINLINE bool __asan_symbolize(const void *pc, char *out_buffer, int out_size) { return false; diff --git a/libsanitizer/asan/asan_stack.h b/libsanitizer/asan/asan_stack.h index da622ed..6a5ffc9 100644 --- a/libsanitizer/asan/asan_stack.h +++ b/libsanitizer/asan/asan_stack.h @@ -13,10 +13,11 @@ #define ASAN_STACK_H #include "sanitizer_common/sanitizer_stacktrace.h" +#include "asan_flags.h" namespace __asan { -void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp); +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast); void PrintStack(StackTrace *stack); } // namespace __asan @@ -25,27 +26,38 @@ void PrintStack(StackTrace *stack); // The pc will be in the position 0 of the resulting stack trace. // The bp may refer to the current frame or to the caller's frame. // fast_unwind is currently unused. -#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp) \ +#define GET_STACK_TRACE_WITH_PC_AND_BP(max_s, pc, bp, fast) \ StackTrace stack; \ - GetStackTrace(&stack, max_s, pc, bp) + GetStackTrace(&stack, max_s, pc, bp, fast) // NOTE: A Rule of thumb is to retrieve stack trace in the interceptors // as early as possible (in functions exposed to the user), as we generally // don't want stack trace to contain functions from ASan internals. -#define GET_STACK_TRACE_HERE(max_size) \ +#define GET_STACK_TRACE(max_size, fast) \ GET_STACK_TRACE_WITH_PC_AND_BP(max_size, \ - StackTrace::GetCurrentPc(), GET_CURRENT_FRAME()) + StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), fast) -#define GET_STACK_TRACE_HERE_FOR_MALLOC \ - GET_STACK_TRACE_HERE(flags()->malloc_context_size) +#define GET_STACK_TRACE_FATAL(pc, bp) \ + GET_STACK_TRACE_WITH_PC_AND_BP(kStackTraceMax, pc, bp, \ + flags()->fast_unwind_on_fatal) -#define GET_STACK_TRACE_HERE_FOR_FREE(ptr) \ - GET_STACK_TRACE_HERE(flags()->malloc_context_size) +#define GET_STACK_TRACE_FATAL_HERE \ + GET_STACK_TRACE(kStackTraceMax, flags()->fast_unwind_on_fatal) + +#define GET_STACK_TRACE_THREAD \ + GET_STACK_TRACE(kStackTraceMax, true) + +#define GET_STACK_TRACE_MALLOC \ + GET_STACK_TRACE(flags()->malloc_context_size, \ + flags()->fast_unwind_on_malloc) + +#define GET_STACK_TRACE_FREE GET_STACK_TRACE_MALLOC #define PRINT_CURRENT_STACK() \ { \ - GET_STACK_TRACE_HERE(kStackTraceMax); \ + GET_STACK_TRACE(kStackTraceMax, \ + flags()->fast_unwind_on_fatal); \ PrintStack(&stack); \ } diff --git a/libsanitizer/asan/asan_stats.cc b/libsanitizer/asan/asan_stats.cc index 566e010..94dd741 100644 --- a/libsanitizer/asan/asan_stats.cc +++ b/libsanitizer/asan/asan_stats.cc @@ -15,6 +15,7 @@ #include "asan_stats.h" #include "asan_thread_registry.h" #include "sanitizer/asan_interface.h" +#include "sanitizer_common/sanitizer_stackdepot.h" namespace __asan { @@ -40,8 +41,9 @@ void AsanStats::Print() { Printf("Stats: %zuM freed by %zu calls\n", freed>>20, frees); Printf("Stats: %zuM really freed by %zu calls\n", really_freed>>20, real_frees); - Printf("Stats: %zuM (%zu full pages) mmaped in %zu calls\n", - mmaped>>20, mmaped / GetPageSizeCached(), mmaps); + Printf("Stats: %zuM (%zuM-%zuM) mmaped; %zu maps, %zu unmaps\n", + (mmaped-munmaped)>>20, mmaped>>20, munmaped>>20, + mmaps, munmaps); PrintMallocStatsArray(" mmaps by size class: ", mmaped_by_size); PrintMallocStatsArray(" mallocs by size class: ", malloced_by_size); @@ -59,6 +61,10 @@ static void PrintAccumulatedStats() { // Use lock to keep reports from mixing up. ScopedLock lock(&print_lock); stats.Print(); + StackDepotStats *stack_depot_stats = StackDepotGetStats(); + Printf("Stats: StackDepot: %zd ids; %zdM mapped\n", + stack_depot_stats->n_uniq_ids, stack_depot_stats->mapped >> 20); + PrintInternalAllocatorStats(); } } // namespace __asan diff --git a/libsanitizer/asan/asan_stats.h b/libsanitizer/asan/asan_stats.h index c2b3298..fd27451 100644 --- a/libsanitizer/asan/asan_stats.h +++ b/libsanitizer/asan/asan_stats.h @@ -35,6 +35,8 @@ struct AsanStats { uptr realloced; uptr mmaps; uptr mmaped; + uptr munmaps; + uptr munmaped; uptr mmaped_by_size[kNumberOfSizeClasses]; uptr malloced_by_size[kNumberOfSizeClasses]; uptr freed_by_size[kNumberOfSizeClasses]; diff --git a/libsanitizer/asan/asan_thread.h b/libsanitizer/asan/asan_thread.h index dff8c88..f385ec3 100644 --- a/libsanitizer/asan/asan_thread.h +++ b/libsanitizer/asan/asan_thread.h @@ -37,6 +37,7 @@ class AsanThreadSummary { internal_memcpy(&stack_, stack, sizeof(*stack)); } thread_ = 0; + name_[0] = 0; } u32 tid() { return tid_; } void set_tid(u32 tid) { tid_ = tid; } @@ -47,6 +48,10 @@ class AsanThreadSummary { AsanThread *thread() { return thread_; } void set_thread(AsanThread *thread) { thread_ = thread; } static void TSDDtor(void *tsd); + void set_name(const char *name) { + internal_strncpy(name_, name, sizeof(name_) - 1); + } + const char *name() { return name_; } private: u32 tid_; @@ -54,8 +59,12 @@ class AsanThreadSummary { bool announced_; StackTrace stack_; AsanThread *thread_; + char name_[128]; }; +// AsanThreadSummary objects are never freed, so we need many of them. +COMPILER_CHECK(sizeof(AsanThreadSummary) <= 4094); + // AsanThread are stored in TSD and destroyed when the thread dies. class AsanThread { public: diff --git a/libsanitizer/asan/asan_thread_registry.cc b/libsanitizer/asan/asan_thread_registry.cc index 9858cce..8db6a57 100644 --- a/libsanitizer/asan/asan_thread_registry.cc +++ b/libsanitizer/asan/asan_thread_registry.cc @@ -121,13 +121,14 @@ uptr AsanThreadRegistry::GetCurrentAllocatedBytes() { uptr AsanThreadRegistry::GetHeapSize() { ScopedLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); - return accumulated_stats_.mmaped; + return accumulated_stats_.mmaped - accumulated_stats_.munmaped; } uptr AsanThreadRegistry::GetFreeBytes() { ScopedLock lock(&mu_); UpdateAccumulatedStatsUnlocked(); uptr total_free = accumulated_stats_.mmaped + - accumulated_stats_.munmaped + accumulated_stats_.really_freed + accumulated_stats_.really_freed_redzones; uptr total_used = accumulated_stats_.malloced diff --git a/libsanitizer/asan/asan_win.cc b/libsanitizer/asan/asan_win.cc index 8b7f9ef..02a2e08 100644 --- a/libsanitizer/asan/asan_win.cc +++ b/libsanitizer/asan/asan_win.cc @@ -30,7 +30,8 @@ static AsanLock dbghelp_lock(LINKER_INITIALIZED); static bool dbghelp_initialized = false; #pragma comment(lib, "dbghelp.lib") -void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp) { +void GetStackTrace(StackTrace *stack, uptr max_s, uptr pc, uptr bp, bool fast) { + (void)fast; stack->max_size = max_s; void *tmp[kStackTraceMax]; diff --git a/libsanitizer/include/sanitizer/asan_interface.h b/libsanitizer/include/sanitizer/asan_interface.h index b94b303..47f780c 100644 --- a/libsanitizer/include/sanitizer/asan_interface.h +++ b/libsanitizer/include/sanitizer/asan_interface.h @@ -115,6 +115,15 @@ extern "C" { bool __asan_address_is_poisoned(void const volatile *addr) SANITIZER_INTERFACE_ATTRIBUTE; + // If at least on byte in [beg, beg+size) is poisoned, return the address + // of the first such byte. Otherwise return 0. + uptr __asan_region_is_poisoned(uptr beg, uptr size) + SANITIZER_INTERFACE_ATTRIBUTE; + + // Print the description of addr (useful when debugging in gdb). + void __asan_describe_address(uptr addr) + SANITIZER_INTERFACE_ATTRIBUTE; + // This is an internal function that is called to report an error. // However it is still a part of the interface because users may want to // set a breakpoint on this function in a debugger. @@ -138,7 +147,7 @@ extern "C" { // User may provide function that would be called right when ASan detects // an error. This can be used to notice cases when ASan detects an error, but // the program crashes before ASan report is printed. - void __asan_on_error() + /* OPTIONAL */ void __asan_on_error() SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; // User may provide its own implementation for symbolization function. @@ -146,7 +155,8 @@ extern "C" { // "out_buffer". Description should be at most "out_size" bytes long. // User-specified function should return true if symbolization was // successful. - bool __asan_symbolize(const void *pc, char *out_buffer, int out_size) + /* OPTIONAL */ bool __asan_symbolize(const void *pc, char *out_buffer, + int out_size) SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; // Returns the estimated number of bytes that will be reserved by allocator @@ -186,20 +196,19 @@ extern "C" { void __asan_print_accumulated_stats() SANITIZER_INTERFACE_ATTRIBUTE; - // This function may be overriden by user to provide a string containing - // ASan runtime options. See asan_flags.h for details. - const char* __asan_default_options() + // This function may be optionally provided by user and should return + // a string containing ASan runtime options. See asan_flags.h for details. + /* OPTIONAL */ const char* __asan_default_options() SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; - // Malloc hooks that may be overriden by user. + // Malloc hooks that may be optionally provided by user. // __asan_malloc_hook(ptr, size) is called immediately after // allocation of "size" bytes, which returned "ptr". // __asan_free_hook(ptr) is called immediately before // deallocation of "ptr". - // If user doesn't provide implementations of these hooks, they are no-op. - void __asan_malloc_hook(void *ptr, uptr size) + /* OPTIONAL */ void __asan_malloc_hook(void *ptr, uptr size) SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; - void __asan_free_hook(void *ptr) + /* OPTIONAL */ void __asan_free_hook(void *ptr) SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; } // extern "C" diff --git a/libsanitizer/include/sanitizer/common_interface_defs.h b/libsanitizer/include/sanitizer/common_interface_defs.h index f6e814d..9fba976 100644 --- a/libsanitizer/include/sanitizer/common_interface_defs.h +++ b/libsanitizer/include/sanitizer/common_interface_defs.h @@ -28,6 +28,12 @@ # define SANITIZER_WEAK_ATTRIBUTE __attribute__((weak)) #endif +#ifdef __linux__ +# define SANITIZER_SUPPORTS_WEAK_HOOKS 1 +#else +# define SANITIZER_SUPPORTS_WEAK_HOOKS 0 +#endif + // __has_feature #if !defined(__has_feature) # define __has_feature(x) 0 @@ -73,6 +79,12 @@ extern "C" { // stderr. void __sanitizer_set_report_fd(int fd) SANITIZER_INTERFACE_ATTRIBUTE; + + // Notify the tools that the sandbox is going to be turned on. The reserved + // parameter will be used in the future to hold a structure with functions + // that the tools may call to bypass the sandbox. + void __sanitizer_sandbox_on_notify(void *reserved) + SANITIZER_WEAK_ATTRIBUTE SANITIZER_INTERFACE_ATTRIBUTE; } // extern "C" #endif // SANITIZER_COMMON_INTERFACE_DEFS_H diff --git a/libsanitizer/interception/interception.h b/libsanitizer/interception/interception.h index 8094fe5..66bdd88 100644 --- a/libsanitizer/interception/interception.h +++ b/libsanitizer/interception/interception.h @@ -17,6 +17,15 @@ # error "Interception doesn't work on this operating system." #endif +#include "sanitizer/common_interface_defs.h" + +// These typedefs should be used only in the interceptor definitions to replace +// the standard system types (e.g. SSIZE_T instead of ssize_t) +typedef __sanitizer::uptr SIZE_T; +typedef __sanitizer::sptr SSIZE_T; +typedef __sanitizer::u64 OFF_T; +typedef __sanitizer::u64 OFF64_T; + // How to use this library: // 1) Include this header to define your own interceptors // (see details below). diff --git a/libsanitizer/sanitizer_common/Makefile.am b/libsanitizer/sanitizer_common/Makefile.am index 708b2a4..cc23dc2 100644 --- a/libsanitizer/sanitizer_common/Makefile.am +++ b/libsanitizer/sanitizer_common/Makefile.am @@ -22,6 +22,7 @@ sanitizer_common_files = \ sanitizer_stackdepot.cc \ sanitizer_stacktrace.cc \ sanitizer_symbolizer.cc \ + sanitizer_symbolizer_itanium.cc \ sanitizer_symbolizer_linux.cc \ sanitizer_symbolizer_mac.cc \ sanitizer_symbolizer_win.cc \ diff --git a/libsanitizer/sanitizer_common/Makefile.in b/libsanitizer/sanitizer_common/Makefile.in index d52f42d..77b1f1e 100644 --- a/libsanitizer/sanitizer_common/Makefile.in +++ b/libsanitizer/sanitizer_common/Makefile.in @@ -59,9 +59,9 @@ am__objects_1 = sanitizer_allocator.lo sanitizer_common.lo \ sanitizer_flags.lo sanitizer_libc.lo sanitizer_linux.lo \ sanitizer_mac.lo sanitizer_posix.lo sanitizer_printf.lo \ sanitizer_stackdepot.lo sanitizer_stacktrace.lo \ - sanitizer_symbolizer.lo sanitizer_symbolizer_linux.lo \ - sanitizer_symbolizer_mac.lo sanitizer_symbolizer_win.lo \ - sanitizer_win.lo + sanitizer_symbolizer.lo sanitizer_symbolizer_itanium.lo \ + sanitizer_symbolizer_linux.lo sanitizer_symbolizer_mac.lo \ + sanitizer_symbolizer_win.lo sanitizer_win.lo am_libsanitizer_common_la_OBJECTS = $(am__objects_1) libsanitizer_common_la_OBJECTS = $(am_libsanitizer_common_la_OBJECTS) DEFAULT_INCLUDES = -I.@am__isrc@ @@ -236,6 +236,7 @@ sanitizer_common_files = \ sanitizer_stackdepot.cc \ sanitizer_stacktrace.cc \ sanitizer_symbolizer.cc \ + sanitizer_symbolizer_itanium.cc \ sanitizer_symbolizer_linux.cc \ sanitizer_symbolizer_mac.cc \ sanitizer_symbolizer_win.cc \ @@ -345,6 +346,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_stackdepot.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_stacktrace.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_itanium.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_linux.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_mac.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sanitizer_symbolizer_win.Plo@am__quote@ diff --git a/libsanitizer/sanitizer_common/sanitizer_allocator.h b/libsanitizer/sanitizer_common/sanitizer_allocator.h index 63107bd..d0fc315 100644 --- a/libsanitizer/sanitizer_common/sanitizer_allocator.h +++ b/libsanitizer/sanitizer_common/sanitizer_allocator.h @@ -20,76 +20,140 @@ namespace __sanitizer { -// Maps size class id to size and back. -template <uptr l0, uptr l1, uptr l2, uptr l3, uptr l4, uptr l5, - uptr s0, uptr s1, uptr s2, uptr s3, uptr s4, - uptr c0, uptr c1, uptr c2, uptr c3, uptr c4> -class SplineSizeClassMap { - private: - // Here we use a spline composed of 5 polynomials of oder 1. - // The first size class is l0, then the classes go with step s0 - // untill they reach l1, after which they go with step s1 and so on. - // Steps should be powers of two for cheap division. - // The size of the last size class should be a power of two. - // There should be at most 256 size classes. - static const uptr u0 = 0 + (l1 - l0) / s0; - static const uptr u1 = u0 + (l2 - l1) / s1; - static const uptr u2 = u1 + (l3 - l2) / s2; - static const uptr u3 = u2 + (l4 - l3) / s3; - static const uptr u4 = u3 + (l5 - l4) / s4; +// SizeClassMap maps allocation sizes into size classes and back. +// Class 0 corresponds to size 0. +// Classes 1 - 16 correspond to sizes 8 - 128 (size = class_id * 8). +// Next 8 classes: 128 + i * 16 (i = 1 to 8). +// Next 8 classes: 256 + i * 32 (i = 1 to 8). +// ... +// Next 8 classes: 2^k + i * 2^(k-3) (i = 1 to 8). +// Last class corresponds to kMaxSize = 1 << kMaxSizeLog. +// +// This structure of the size class map gives us: +// - Efficient table-free class-to-size and size-to-class functions. +// - Difference between two consequent size classes is betweed 12% and 6% +// +// This class also gives a hint to a thread-caching allocator about the amount +// of chunks that need to be cached per-thread: +// - kMaxNumCached is the maximal number of chunks per size class. +// - (1 << kMaxBytesCachedLog) is the maximal number of bytes per size class. +// +// Part of output of SizeClassMap::Print(): +// c00 => s: 0 diff: +0 00% l 0 cached: 0 0; id 0 +// c01 => s: 8 diff: +8 00% l 3 cached: 256 2048; id 1 +// c02 => s: 16 diff: +8 100% l 4 cached: 256 4096; id 2 +// ... +// c07 => s: 56 diff: +8 16% l 5 cached: 256 14336; id 7 +// +// c08 => s: 64 diff: +8 14% l 6 cached: 256 16384; id 8 +// ... +// c15 => s: 120 diff: +8 07% l 6 cached: 256 30720; id 15 +// +// c16 => s: 128 diff: +8 06% l 7 cached: 256 32768; id 16 +// c17 => s: 144 diff: +16 12% l 7 cached: 227 32688; id 17 +// ... +// c23 => s: 240 diff: +16 07% l 7 cached: 136 32640; id 23 +// +// c24 => s: 256 diff: +16 06% l 8 cached: 128 32768; id 24 +// c25 => s: 288 diff: +32 12% l 8 cached: 113 32544; id 25 +// ... +// c31 => s: 480 diff: +32 07% l 8 cached: 68 32640; id 31 +// +// c32 => s: 512 diff: +32 06% l 9 cached: 64 32768; id 32 - public: - // The number of size classes should be a power of two for fast division. - static const uptr kNumClasses = u4 + 1; - static const uptr kMaxSize = l5; - static const uptr kMinSize = l0; - COMPILER_CHECK(kNumClasses <= 256); - COMPILER_CHECK((kNumClasses & (kNumClasses - 1)) == 0); - COMPILER_CHECK((kMaxSize & (kMaxSize - 1)) == 0); +template <uptr kMaxSizeLog, uptr kMaxNumCached, uptr kMaxBytesCachedLog> +class SizeClassMap { + static const uptr kMinSizeLog = 3; + static const uptr kMidSizeLog = kMinSizeLog + 4; + static const uptr kMinSize = 1 << kMinSizeLog; + static const uptr kMidSize = 1 << kMidSizeLog; + static const uptr kMidClass = kMidSize / kMinSize; + static const uptr S = 3; + static const uptr M = (1 << S) - 1; + + public: + static const uptr kMaxSize = 1 << kMaxSizeLog; + static const uptr kNumClasses = + kMidClass + ((kMaxSizeLog - kMidSizeLog) << S) + 1; + COMPILER_CHECK(kNumClasses >= 32 && kNumClasses <= 256); + static const uptr kNumClassesRounded = + kNumClasses == 32 ? 32 : + kNumClasses <= 64 ? 64 : + kNumClasses <= 128 ? 128 : 256; static uptr Size(uptr class_id) { - if (class_id <= u0) return l0 + s0 * (class_id - 0); - if (class_id <= u1) return l1 + s1 * (class_id - u0); - if (class_id <= u2) return l2 + s2 * (class_id - u1); - if (class_id <= u3) return l3 + s3 * (class_id - u2); - if (class_id <= u4) return l4 + s4 * (class_id - u3); - return 0; + if (class_id <= kMidClass) + return kMinSize * class_id; + class_id -= kMidClass; + uptr t = kMidSize << (class_id >> S); + return t + (t >> S) * (class_id & M); } + static uptr ClassID(uptr size) { - if (size <= l1) return 0 + (size - l0 + s0 - 1) / s0; - if (size <= l2) return u0 + (size - l1 + s1 - 1) / s1; - if (size <= l3) return u1 + (size - l2 + s2 - 1) / s2; - if (size <= l4) return u2 + (size - l3 + s3 - 1) / s3; - if (size <= l5) return u3 + (size - l4 + s4 - 1) / s4; - return 0; + if (size <= kMidSize) + return (size + kMinSize - 1) >> kMinSizeLog; + if (size > kMaxSize) return 0; + uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(size); + uptr hbits = (size >> (l - S)) & M; + uptr lbits = size & ((1 << (l - S)) - 1); + uptr l1 = l - kMidSizeLog; + return kMidClass + (l1 << S) + hbits + (lbits > 0); } static uptr MaxCached(uptr class_id) { - if (class_id <= u0) return c0; - if (class_id <= u1) return c1; - if (class_id <= u2) return c2; - if (class_id <= u3) return c3; - if (class_id <= u4) return c4; - return 0; + if (class_id == 0) return 0; + uptr n = (1UL << kMaxBytesCachedLog) / Size(class_id); + return Max(1UL, Min(kMaxNumCached, n)); } -}; -class DefaultSizeClassMap: public SplineSizeClassMap< - /* l: */1 << 4, 1 << 9, 1 << 12, 1 << 15, 1 << 18, 1 << 21, - /* s: */1 << 4, 1 << 6, 1 << 9, 1 << 12, 1 << 15, - /* c: */256, 64, 16, 4, 1> { - private: - COMPILER_CHECK(kNumClasses == 256); + static void Print() { + uptr prev_s = 0; + uptr total_cached = 0; + for (uptr i = 0; i < kNumClasses; i++) { + uptr s = Size(i); + if (s >= kMidSize / 2 && (s & (s - 1)) == 0) + Printf("\n"); + uptr d = s - prev_s; + uptr p = prev_s ? (d * 100 / prev_s) : 0; + uptr l = SANITIZER_WORDSIZE - 1 - __builtin_clzl(s); + uptr cached = MaxCached(i) * s; + Printf("c%02zd => s: %zd diff: +%zd %02zd%% l %zd " + "cached: %zd %zd; id %zd\n", + i, Size(i), d, p, l, MaxCached(i), cached, ClassID(s)); + total_cached += cached; + prev_s = s; + } + Printf("Total cached: %zd\n", total_cached); + } + + static void Validate() { + for (uptr c = 1; c < kNumClasses; c++) { + // Printf("Validate: c%zd\n", c); + uptr s = Size(c); + CHECK_EQ(ClassID(s), c); + if (c != kNumClasses - 1) + CHECK_EQ(ClassID(s + 1), c + 1); + CHECK_EQ(ClassID(s - 1), c); + if (c) + CHECK_GT(Size(c), Size(c-1)); + } + CHECK_EQ(ClassID(kMaxSize + 1), 0); + + for (uptr s = 1; s <= kMaxSize; s++) { + uptr c = ClassID(s); + // Printf("s%zd => c%zd\n", s, c); + CHECK_LT(c, kNumClasses); + CHECK_GE(Size(c), s); + if (c > 0) + CHECK_LT(Size(c-1), s); + } + } }; -class CompactSizeClassMap: public SplineSizeClassMap< - /* l: */1 << 3, 1 << 4, 1 << 7, 1 << 8, 1 << 12, 1 << 15, - /* s: */1 << 3, 1 << 4, 1 << 7, 1 << 8, 1 << 12, - /* c: */256, 64, 16, 4, 1> { - private: - COMPILER_CHECK(kNumClasses <= 32); -}; +typedef SizeClassMap<15, 256, 16> DefaultSizeClassMap; +typedef SizeClassMap<15, 64, 14> CompactSizeClassMap; + struct AllocatorListNode { AllocatorListNode *next; @@ -97,11 +161,45 @@ struct AllocatorListNode { typedef IntrusiveList<AllocatorListNode> AllocatorFreeList; +// Move at most max_count chunks from allocate_from to allocate_to. +// This function is better be a method of AllocatorFreeList, but we can't +// inherit it from IntrusiveList as the ancient gcc complains about non-PODness. +static inline uptr BulkMove(uptr max_count, + AllocatorFreeList *allocate_from, + AllocatorFreeList *allocate_to) { + CHECK(!allocate_from->empty()); + CHECK(allocate_to->empty()); + uptr res = 0; + if (allocate_from->size() <= max_count) { + res = allocate_from->size(); + allocate_to->append_front(allocate_from); + CHECK(allocate_from->empty()); + } else { + for (uptr i = 0; i < max_count; i++) { + AllocatorListNode *node = allocate_from->front(); + allocate_from->pop_front(); + allocate_to->push_front(node); + } + res = max_count; + CHECK(!allocate_from->empty()); + } + CHECK(!allocate_to->empty()); + return res; +} + +// Allocators call these callbacks on mmap/munmap. +struct NoOpMapUnmapCallback { + void OnMap(uptr p, uptr size) const { } + void OnUnmap(uptr p, uptr size) const { } +}; + // SizeClassAllocator64 -- allocator for 64-bit address space. // // Space: a portion of address space of kSpaceSize bytes starting at // a fixed address (kSpaceBeg). Both constants are powers of two and // kSpaceBeg is kSpaceSize-aligned. +// At the beginning the entire space is mprotect-ed, then small parts of it +// are mapped on demand. // // Region: a part of Space dedicated to a single size class. // There are kNumClasses Regions of equal size. @@ -112,22 +210,35 @@ typedef IntrusiveList<AllocatorListNode> AllocatorFreeList; // A Region looks like this: // UserChunk1 ... UserChunkN <gap> MetaChunkN ... MetaChunk1 template <const uptr kSpaceBeg, const uptr kSpaceSize, - const uptr kMetadataSize, class SizeClassMap> + const uptr kMetadataSize, class SizeClassMap, + class MapUnmapCallback = NoOpMapUnmapCallback> class SizeClassAllocator64 { public: void Init() { - CHECK_EQ(AllocBeg(), reinterpret_cast<uptr>(MmapFixedNoReserve( - AllocBeg(), AllocSize()))); + CHECK_EQ(kSpaceBeg, + reinterpret_cast<uptr>(Mprotect(kSpaceBeg, kSpaceSize))); + MapWithCallback(kSpaceEnd, AdditionalSize()); } - bool CanAllocate(uptr size, uptr alignment) { + void MapWithCallback(uptr beg, uptr size) { + CHECK_EQ(beg, reinterpret_cast<uptr>(MmapFixedOrDie(beg, size))); + MapUnmapCallback().OnMap(beg, size); + } + + void UnmapWithCallback(uptr beg, uptr size) { + MapUnmapCallback().OnUnmap(beg, size); + UnmapOrDie(reinterpret_cast<void *>(beg), size); + } + + static bool CanAllocate(uptr size, uptr alignment) { return size <= SizeClassMap::kMaxSize && alignment <= SizeClassMap::kMaxSize; } void *Allocate(uptr size, uptr alignment) { + if (size < alignment) size = alignment; CHECK(CanAllocate(size, alignment)); - return AllocateBySizeClass(SizeClassMap::ClassID(size)); + return AllocateBySizeClass(ClassID(size)); } void Deallocate(void *p) { @@ -143,18 +254,8 @@ class SizeClassAllocator64 { if (region->free_list.empty()) { PopulateFreeList(class_id, region); } - CHECK(!region->free_list.empty()); - uptr count = SizeClassMap::MaxCached(class_id); - if (region->free_list.size() <= count) { - free_list->append_front(®ion->free_list); - } else { - for (uptr i = 0; i < count; i++) { - AllocatorListNode *node = region->free_list.front(); - region->free_list.pop_front(); - free_list->push_front(node); - } - } - CHECK(!free_list->empty()); + region->n_allocated += BulkMove(SizeClassMap::MaxCached(class_id), + ®ion->free_list, free_list); } // Swallow the entire free_list for the given class_id. @@ -162,6 +263,7 @@ class SizeClassAllocator64 { CHECK_LT(class_id, kNumClasses); RegionInfo *region = GetRegionInfo(class_id); SpinMutexLock l(®ion->mutex); + region->n_freed += free_list->size(); region->free_list.append_front(free_list); } @@ -170,16 +272,20 @@ class SizeClassAllocator64 { } static uptr GetSizeClass(void *p) { - return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClasses; + return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClassesRounded; } - static void *GetBlockBegin(void *p) { + void *GetBlockBegin(void *p) { uptr class_id = GetSizeClass(p); uptr size = SizeClassMap::Size(class_id); uptr chunk_idx = GetChunkIdx((uptr)p, size); uptr reg_beg = (uptr)p & ~(kRegionSize - 1); - uptr begin = reg_beg + chunk_idx * size; - return (void*)begin; + uptr beg = chunk_idx * size; + uptr next_beg = beg + size; + RegionInfo *region = GetRegionInfo(class_id); + if (region->mapped_user >= next_beg) + return reinterpret_cast<void*>(reg_beg + beg); + return 0; } static uptr GetActuallyAllocatedSize(void *p) { @@ -206,39 +312,66 @@ class SizeClassAllocator64 { // Test-only. void TestOnlyUnmap() { - UnmapOrDie(reinterpret_cast<void*>(AllocBeg()), AllocSize()); + UnmapWithCallback(kSpaceBeg, kSpaceSize + AdditionalSize()); + } + + void PrintStats() { + uptr total_mapped = 0; + uptr n_allocated = 0; + uptr n_freed = 0; + for (uptr class_id = 1; class_id < kNumClasses; class_id++) { + RegionInfo *region = GetRegionInfo(class_id); + total_mapped += region->mapped_user; + n_allocated += region->n_allocated; + n_freed += region->n_freed; + } + Printf("Stats: SizeClassAllocator64: %zdM mapped in %zd allocations; " + "remains %zd\n", + total_mapped >> 20, n_allocated, n_allocated - n_freed); + for (uptr class_id = 1; class_id < kNumClasses; class_id++) { + RegionInfo *region = GetRegionInfo(class_id); + if (region->mapped_user == 0) continue; + Printf(" %02zd (%zd): total: %zd K allocs: %zd remains: %zd\n", + class_id, + SizeClassMap::Size(class_id), + region->mapped_user >> 10, + region->n_allocated, + region->n_allocated - region->n_freed); + } } - static uptr AllocBeg() { return kSpaceBeg; } - static uptr AllocSize() { return kSpaceSize + AdditionalSize(); } - typedef SizeClassMap SizeClassMapT; - static const uptr kNumClasses = SizeClassMap::kNumClasses; // 2^k <= 256 + static const uptr kNumClasses = SizeClassMap::kNumClasses; + static const uptr kNumClassesRounded = SizeClassMap::kNumClassesRounded; private: - static const uptr kRegionSize = kSpaceSize / kNumClasses; + static const uptr kRegionSize = kSpaceSize / kNumClassesRounded; + static const uptr kSpaceEnd = kSpaceBeg + kSpaceSize; COMPILER_CHECK(kSpaceBeg % kSpaceSize == 0); - COMPILER_CHECK(kNumClasses <= SizeClassMap::kNumClasses); // kRegionSize must be >= 2^32. COMPILER_CHECK((kRegionSize) >= (1ULL << (SANITIZER_WORDSIZE / 2))); // Populate the free list with at most this number of bytes at once // or with one element if its size is greater. - static const uptr kPopulateSize = 1 << 18; + static const uptr kPopulateSize = 1 << 15; + // Call mmap for user memory with at least this size. + static const uptr kUserMapSize = 1 << 15; + // Call mmap for metadata memory with at least this size. + static const uptr kMetaMapSize = 1 << 16; struct RegionInfo { SpinMutex mutex; AllocatorFreeList free_list; uptr allocated_user; // Bytes allocated for user memory. uptr allocated_meta; // Bytes allocated for metadata. - char padding[kCacheLineSize - 3 * sizeof(uptr) - sizeof(AllocatorFreeList)]; + uptr mapped_user; // Bytes mapped for user memory. + uptr mapped_meta; // Bytes mapped for metadata. + uptr n_allocated, n_freed; // Just stats. }; - COMPILER_CHECK(sizeof(RegionInfo) == kCacheLineSize); + COMPILER_CHECK(sizeof(RegionInfo) >= kCacheLineSize); static uptr AdditionalSize() { - uptr PageSize = GetPageSizeCached(); - uptr res = Max(sizeof(RegionInfo) * kNumClasses, PageSize); - CHECK_EQ(res % PageSize, 0); - return res; + return RoundUpTo(sizeof(RegionInfo) * kNumClassesRounded, + GetPageSizeCached()); } RegionInfo *GetRegionInfo(uptr class_id) { @@ -256,11 +389,20 @@ class SizeClassAllocator64 { } void PopulateFreeList(uptr class_id, RegionInfo *region) { + CHECK(region->free_list.empty()); uptr size = SizeClassMap::Size(class_id); uptr beg_idx = region->allocated_user; uptr end_idx = beg_idx + kPopulateSize; - region->free_list.clear(); uptr region_beg = kSpaceBeg + kRegionSize * class_id; + if (end_idx + size > region->mapped_user) { + // Do the mmap for the user memory. + uptr map_size = kUserMapSize; + while (end_idx + size > region->mapped_user + map_size) + map_size += kUserMapSize; + CHECK_GE(region->mapped_user + map_size, end_idx); + MapWithCallback(region_beg + region->mapped_user, map_size); + region->mapped_user += map_size; + } uptr idx = beg_idx; uptr i = 0; do { // do-while loop because we need to put at least one item. @@ -270,7 +412,19 @@ class SizeClassAllocator64 { i++; } while (idx < end_idx); region->allocated_user += idx - beg_idx; + CHECK_LE(region->allocated_user, region->mapped_user); region->allocated_meta += i * kMetadataSize; + if (region->allocated_meta > region->mapped_meta) { + uptr map_size = kMetaMapSize; + while (region->allocated_meta > region->mapped_meta + map_size) + map_size += kMetaMapSize; + // Do the mmap for the metadata. + CHECK_GE(region->mapped_meta + map_size, region->allocated_meta); + MapWithCallback(region_beg + kRegionSize - + region->mapped_meta - map_size, map_size); + region->mapped_meta += map_size; + } + CHECK_LE(region->allocated_meta, region->mapped_meta); if (region->allocated_user + region->allocated_meta > kRegionSize) { Printf("Out of memory. Dying.\n"); Printf("The process has exhausted %zuMB for size class %zu.\n", @@ -289,6 +443,7 @@ class SizeClassAllocator64 { CHECK(!region->free_list.empty()); AllocatorListNode *node = region->free_list.front(); region->free_list.pop_front(); + region->n_allocated++; return reinterpret_cast<void*>(node); } @@ -296,7 +451,211 @@ class SizeClassAllocator64 { RegionInfo *region = GetRegionInfo(class_id); SpinMutexLock l(®ion->mutex); region->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p)); + region->n_freed++; + } +}; + +// SizeClassAllocator32 -- allocator for 32-bit address space. +// This allocator can theoretically be used on 64-bit arch, but there it is less +// efficient than SizeClassAllocator64. +// +// [kSpaceBeg, kSpaceBeg + kSpaceSize) is the range of addresses which can +// be returned by MmapOrDie(). +// +// Region: +// a result of a single call to MmapAlignedOrDie(kRegionSize, kRegionSize). +// Since the regions are aligned by kRegionSize, there are exactly +// kNumPossibleRegions possible regions in the address space and so we keep +// an u8 array possible_regions[kNumPossibleRegions] to store the size classes. +// 0 size class means the region is not used by the allocator. +// +// One Region is used to allocate chunks of a single size class. +// A Region looks like this: +// UserChunk1 .. UserChunkN <gap> MetaChunkN .. MetaChunk1 +// +// In order to avoid false sharing the objects of this class should be +// chache-line aligned. +template <const uptr kSpaceBeg, const u64 kSpaceSize, + const uptr kMetadataSize, class SizeClassMap, + class MapUnmapCallback = NoOpMapUnmapCallback> +class SizeClassAllocator32 { + public: + void Init() { + state_ = reinterpret_cast<State *>(MapWithCallback(sizeof(State))); + } + + void *MapWithCallback(uptr size) { + size = RoundUpTo(size, GetPageSizeCached()); + void *res = MmapOrDie(size, "SizeClassAllocator32"); + MapUnmapCallback().OnMap((uptr)res, size); + return res; + } + void UnmapWithCallback(uptr beg, uptr size) { + MapUnmapCallback().OnUnmap(beg, size); + UnmapOrDie(reinterpret_cast<void *>(beg), size); + } + + static bool CanAllocate(uptr size, uptr alignment) { + return size <= SizeClassMap::kMaxSize && + alignment <= SizeClassMap::kMaxSize; + } + + void *Allocate(uptr size, uptr alignment) { + if (size < alignment) size = alignment; + CHECK(CanAllocate(size, alignment)); + return AllocateBySizeClass(ClassID(size)); + } + + void Deallocate(void *p) { + CHECK(PointerIsMine(p)); + DeallocateBySizeClass(p, GetSizeClass(p)); + } + + void *GetMetaData(void *p) { + CHECK(PointerIsMine(p)); + uptr mem = reinterpret_cast<uptr>(p); + uptr beg = ComputeRegionBeg(mem); + uptr size = SizeClassMap::Size(GetSizeClass(p)); + u32 offset = mem - beg; + uptr n = offset / (u32)size; // 32-bit division + uptr meta = (beg + kRegionSize) - (n + 1) * kMetadataSize; + return reinterpret_cast<void*>(meta); + } + + // Allocate several chunks of the given class_id. + void BulkAllocate(uptr class_id, AllocatorFreeList *free_list) { + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + EnsureSizeClassHasAvailableChunks(sci, class_id); + CHECK(!sci->free_list.empty()); + BulkMove(SizeClassMap::MaxCached(class_id), &sci->free_list, free_list); + } + + // Swallow the entire free_list for the given class_id. + void BulkDeallocate(uptr class_id, AllocatorFreeList *free_list) { + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + sci->free_list.append_front(free_list); + } + + bool PointerIsMine(void *p) { + return GetSizeClass(p) != 0; + } + + uptr GetSizeClass(void *p) { + return state_->possible_regions[ComputeRegionId(reinterpret_cast<uptr>(p))]; + } + + void *GetBlockBegin(void *p) { + CHECK(PointerIsMine(p)); + uptr mem = reinterpret_cast<uptr>(p); + uptr beg = ComputeRegionBeg(mem); + uptr size = SizeClassMap::Size(GetSizeClass(p)); + u32 offset = mem - beg; + u32 n = offset / (u32)size; // 32-bit division + uptr res = beg + (n * (u32)size); + return reinterpret_cast<void*>(res); + } + + uptr GetActuallyAllocatedSize(void *p) { + CHECK(PointerIsMine(p)); + return SizeClassMap::Size(GetSizeClass(p)); + } + + uptr ClassID(uptr size) { return SizeClassMap::ClassID(size); } + + uptr TotalMemoryUsed() { + // No need to lock here. + uptr res = 0; + for (uptr i = 0; i < kNumPossibleRegions; i++) + if (state_->possible_regions[i]) + res += kRegionSize; + return res; + } + + void TestOnlyUnmap() { + for (uptr i = 0; i < kNumPossibleRegions; i++) + if (state_->possible_regions[i]) + UnmapWithCallback((i * kRegionSize), kRegionSize); + UnmapWithCallback(reinterpret_cast<uptr>(state_), sizeof(State)); + } + + void PrintStats() { + } + + typedef SizeClassMap SizeClassMapT; + static const uptr kNumClasses = SizeClassMap::kNumClasses; + + private: + static const uptr kRegionSizeLog = SANITIZER_WORDSIZE == 64 ? 24 : 20; + static const uptr kRegionSize = 1 << kRegionSizeLog; + static const uptr kNumPossibleRegions = kSpaceSize / kRegionSize; + + struct SizeClassInfo { + SpinMutex mutex; + AllocatorFreeList free_list; + char padding[kCacheLineSize - sizeof(uptr) - sizeof(AllocatorFreeList)]; + }; + COMPILER_CHECK(sizeof(SizeClassInfo) == kCacheLineSize); + + uptr ComputeRegionId(uptr mem) { + uptr res = mem >> kRegionSizeLog; + CHECK_LT(res, kNumPossibleRegions); + return res; + } + + uptr ComputeRegionBeg(uptr mem) { + return mem & ~(kRegionSize - 1); + } + + uptr AllocateRegion(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + uptr res = reinterpret_cast<uptr>(MmapAlignedOrDie(kRegionSize, kRegionSize, + "SizeClassAllocator32")); + MapUnmapCallback().OnMap(res, kRegionSize); + CHECK_EQ(0U, (res & (kRegionSize - 1))); + CHECK_EQ(0U, state_->possible_regions[ComputeRegionId(res)]); + state_->possible_regions[ComputeRegionId(res)] = class_id; + return res; + } + + SizeClassInfo *GetSizeClassInfo(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + return &state_->size_class_info_array[class_id]; + } + + void EnsureSizeClassHasAvailableChunks(SizeClassInfo *sci, uptr class_id) { + if (!sci->free_list.empty()) return; + uptr size = SizeClassMap::Size(class_id); + uptr reg = AllocateRegion(class_id); + uptr n_chunks = kRegionSize / (size + kMetadataSize); + for (uptr i = reg; i < reg + n_chunks * size; i += size) + sci->free_list.push_back(reinterpret_cast<AllocatorListNode*>(i)); + } + + void *AllocateBySizeClass(uptr class_id) { + CHECK_LT(class_id, kNumClasses); + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + EnsureSizeClassHasAvailableChunks(sci, class_id); + CHECK(!sci->free_list.empty()); + AllocatorListNode *node = sci->free_list.front(); + sci->free_list.pop_front(); + return reinterpret_cast<void*>(node); } + + void DeallocateBySizeClass(void *p, uptr class_id) { + CHECK_LT(class_id, kNumClasses); + SizeClassInfo *sci = GetSizeClassInfo(class_id); + SpinMutexLock l(&sci->mutex); + sci->free_list.push_front(reinterpret_cast<AllocatorListNode*>(p)); + } + + struct State { + u8 possible_regions[kNumPossibleRegions]; + SizeClassInfo size_class_info_array[kNumClasses]; + }; + State *state_; }; // Objects of this type should be used as local caches for SizeClassAllocator64. @@ -312,6 +671,7 @@ struct SizeClassAllocatorLocalCache { } void *Allocate(SizeClassAllocator *allocator, uptr class_id) { + CHECK_NE(class_id, 0UL); CHECK_LT(class_id, kNumClasses); AllocatorFreeList *free_list = &free_lists_[class_id]; if (free_list->empty()) @@ -323,6 +683,7 @@ struct SizeClassAllocatorLocalCache { } void Deallocate(SizeClassAllocator *allocator, uptr class_id, void *p) { + CHECK_NE(class_id, 0UL); CHECK_LT(class_id, kNumClasses); AllocatorFreeList *free_list = &free_lists_[class_id]; free_list->push_front(reinterpret_cast<AllocatorListNode*>(p)); @@ -358,6 +719,7 @@ struct SizeClassAllocatorLocalCache { // This class can (de)allocate only large chunks of memory using mmap/unmap. // The main purpose of this allocator is to cover large and rare allocation // sizes not covered by more efficient allocators (e.g. SizeClassAllocator64). +template <class MapUnmapCallback = NoOpMapUnmapCallback> class LargeMmapAllocator { public: void Init() { @@ -372,6 +734,7 @@ class LargeMmapAllocator { if (map_size < size) return 0; // Overflow. uptr map_beg = reinterpret_cast<uptr>( MmapOrDie(map_size, "LargeMmapAllocator")); + MapUnmapCallback().OnMap(map_beg, map_size); uptr map_end = map_beg + map_size; uptr res = map_beg + page_size_; if (res & (alignment - 1)) // Align. @@ -384,11 +747,13 @@ class LargeMmapAllocator { h->map_size = map_size; { SpinMutexLock l(&mutex_); - h->next = list_; - h->prev = 0; - if (list_) - list_->prev = h; - list_ = h; + uptr idx = n_chunks_++; + CHECK_LT(idx, kMaxNumChunks); + h->chunk_idx = idx; + chunks_[idx] = h; + stats.n_allocs++; + stats.currently_allocated += map_size; + stats.max_allocated = Max(stats.max_allocated, stats.currently_allocated); } return reinterpret_cast<void*>(res); } @@ -397,63 +762,81 @@ class LargeMmapAllocator { Header *h = GetHeader(p); { SpinMutexLock l(&mutex_); - Header *prev = h->prev; - Header *next = h->next; - if (prev) - prev->next = next; - if (next) - next->prev = prev; - if (h == list_) - list_ = next; + uptr idx = h->chunk_idx; + CHECK_EQ(chunks_[idx], h); + CHECK_LT(idx, n_chunks_); + chunks_[idx] = chunks_[n_chunks_ - 1]; + chunks_[idx]->chunk_idx = idx; + n_chunks_--; + stats.n_frees++; + stats.currently_allocated -= h->map_size; } + MapUnmapCallback().OnUnmap(h->map_beg, h->map_size); UnmapOrDie(reinterpret_cast<void*>(h->map_beg), h->map_size); } uptr TotalMemoryUsed() { SpinMutexLock l(&mutex_); uptr res = 0; - for (Header *l = list_; l; l = l->next) { - res += RoundUpMapSize(l->size); + for (uptr i = 0; i < n_chunks_; i++) { + Header *h = chunks_[i]; + CHECK_EQ(h->chunk_idx, i); + res += RoundUpMapSize(h->size); } return res; } bool PointerIsMine(void *p) { - // Fast check. - if ((reinterpret_cast<uptr>(p) & (page_size_ - 1))) return false; - SpinMutexLock l(&mutex_); - for (Header *l = list_; l; l = l->next) { - if (GetUser(l) == p) return true; - } - return false; + return GetBlockBegin(p) != 0; } uptr GetActuallyAllocatedSize(void *p) { - return RoundUpMapSize(GetHeader(p)->size) - page_size_; + return RoundUpTo(GetHeader(p)->size, page_size_); } // At least page_size_/2 metadata bytes is available. void *GetMetaData(void *p) { + // Too slow: CHECK_EQ(p, GetBlockBegin(p)); + CHECK(IsAligned(reinterpret_cast<uptr>(p), page_size_)); return GetHeader(p) + 1; } - void *GetBlockBegin(void *p) { + void *GetBlockBegin(void *ptr) { + uptr p = reinterpret_cast<uptr>(ptr); SpinMutexLock l(&mutex_); - for (Header *l = list_; l; l = l->next) { - void *b = GetUser(l); - if (p >= b && p < (u8*)b + l->size) - return b; + uptr nearest_chunk = 0; + // Cache-friendly linear search. + for (uptr i = 0; i < n_chunks_; i++) { + uptr ch = reinterpret_cast<uptr>(chunks_[i]); + if (p < ch) continue; // p is at left to this chunk, skip it. + if (p - ch < p - nearest_chunk) + nearest_chunk = ch; } - return 0; + if (!nearest_chunk) + return 0; + Header *h = reinterpret_cast<Header *>(nearest_chunk); + CHECK_GE(nearest_chunk, h->map_beg); + CHECK_LT(nearest_chunk, h->map_beg + h->map_size); + CHECK_LE(nearest_chunk, p); + if (h->map_beg + h->map_size < p) + return 0; + return GetUser(h); + } + + void PrintStats() { + Printf("Stats: LargeMmapAllocator: allocated %zd times, " + "remains %zd (%zd K) max %zd M\n", + stats.n_allocs, stats.n_allocs - stats.n_frees, + stats.currently_allocated >> 10, stats.max_allocated >> 20); } private: + static const int kMaxNumChunks = 1 << FIRST_32_SECOND_64(15, 18); struct Header { uptr map_beg; uptr map_size; uptr size; - Header *next; - Header *prev; + uptr chunk_idx; }; Header *GetHeader(uptr p) { @@ -472,7 +855,11 @@ class LargeMmapAllocator { } uptr page_size_; - Header *list_; + Header *chunks_[kMaxNumChunks]; + uptr n_chunks_; + struct Stats { + uptr n_allocs, n_frees, currently_allocated, max_allocated; + } stats; SpinMutex mutex_; }; @@ -501,10 +888,14 @@ class CombinedAllocator { if (alignment > 8) size = RoundUpTo(size, alignment); void *res; - if (primary_.CanAllocate(size, alignment)) - res = cache->Allocate(&primary_, primary_.ClassID(size)); - else + if (primary_.CanAllocate(size, alignment)) { + if (cache) // Allocate from cache. + res = cache->Allocate(&primary_, primary_.ClassID(size)); + else // No thread-local cache, allocate directly from primary allocator. + res = primary_.Allocate(size, alignment); + } else { // Secondary allocator does not use cache. res = secondary_.Allocate(size, alignment); + } if (alignment > 8) CHECK_EQ(reinterpret_cast<uptr>(res) & (alignment - 1), 0); if (cleared && res) @@ -544,6 +935,10 @@ class CombinedAllocator { return secondary_.PointerIsMine(p); } + bool FromPrimary(void *p) { + return primary_.PointerIsMine(p); + } + void *GetMetaData(void *p) { if (primary_.PointerIsMine(p)) return primary_.GetMetaData(p); @@ -572,6 +967,11 @@ class CombinedAllocator { cache->Drain(&primary_); } + void PrintStats() { + primary_.PrintStats(); + secondary_.PrintStats(); + } + private: PrimaryAllocator primary_; SecondaryAllocator secondary_; diff --git a/libsanitizer/sanitizer_common/sanitizer_atomic_msvc.h b/libsanitizer/sanitizer_common/sanitizer_atomic_msvc.h index 2c02baa..55e00e2 100644 --- a/libsanitizer/sanitizer_common/sanitizer_atomic_msvc.h +++ b/libsanitizer/sanitizer_common/sanitizer_atomic_msvc.h @@ -22,9 +22,31 @@ extern "C" void _mm_pause(); extern "C" long _InterlockedExchangeAdd( // NOLINT long volatile * Addend, long Value); // NOLINT #pragma intrinsic(_InterlockedExchangeAdd) -extern "C" void *InterlockedCompareExchangePointer( + +#ifdef _WIN64 +extern "C" void *_InterlockedCompareExchangePointer( void *volatile *Destination, void *Exchange, void *Comparand); +#pragma intrinsic(_InterlockedCompareExchangePointer) +#else +// There's no _InterlockedCompareExchangePointer intrinsic on x86, +// so call _InterlockedCompareExchange instead. +extern "C" +long __cdecl _InterlockedCompareExchange( // NOLINT + long volatile *Destination, // NOLINT + long Exchange, long Comparand); // NOLINT +#pragma intrinsic(_InterlockedCompareExchange) + +inline static void *_InterlockedCompareExchangePointer( + void *volatile *Destination, + void *Exchange, void *Comparand) { + return reinterpret_cast<void*>( + _InterlockedCompareExchange( + reinterpret_cast<long volatile*>(Destination), // NOLINT + reinterpret_cast<long>(Exchange), // NOLINT + reinterpret_cast<long>(Comparand))); // NOLINT +} +#endif namespace __sanitizer { @@ -113,7 +135,7 @@ INLINE bool atomic_compare_exchange_strong(volatile atomic_uintptr_t *a, uptr xchg, memory_order mo) { uptr cmpv = *cmp; - uptr prev = (uptr)InterlockedCompareExchangePointer( + uptr prev = (uptr)_InterlockedCompareExchangePointer( (void*volatile*)&a->val_dont_use, (void*)xchg, (void*)cmpv); if (prev == cmpv) return true; diff --git a/libsanitizer/sanitizer_common/sanitizer_common.cc b/libsanitizer/sanitizer_common/sanitizer_common.cc index 76a55c0..96e8808 100644 --- a/libsanitizer/sanitizer_common/sanitizer_common.cc +++ b/libsanitizer/sanitizer_common/sanitizer_common.cc @@ -153,6 +153,27 @@ void SortArray(uptr *array, uptr size) { } } +// We want to map a chunk of address space aligned to 'alignment'. +// We do it by maping a bit more and then unmaping redundant pieces. +// We probably can do it with fewer syscalls in some OS-dependent way. +void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type) { +// uptr PageSize = GetPageSizeCached(); + CHECK(IsPowerOfTwo(size)); + CHECK(IsPowerOfTwo(alignment)); + uptr map_size = size + alignment; + uptr map_res = (uptr)MmapOrDie(map_size, mem_type); + uptr map_end = map_res + map_size; + uptr res = map_res; + if (res & (alignment - 1)) // Not aligned. + res = (map_res + alignment) & ~(alignment - 1); + uptr end = res + size; + if (res != map_res) + UnmapOrDie((void*)map_res, res - map_res); + if (end != map_end) + UnmapOrDie((void*)end, map_end - end); + return (void*)res; +} + } // namespace __sanitizer using namespace __sanitizer; // NOLINT @@ -178,4 +199,9 @@ void __sanitizer_set_report_fd(int fd) { internal_close(report_fd); report_fd = fd; } + +void NOINLINE __sanitizer_sandbox_on_notify(void *reserved) { + (void)reserved; + PrepareForSandboxing(); +} } // extern "C" diff --git a/libsanitizer/sanitizer_common/sanitizer_common.h b/libsanitizer/sanitizer_common/sanitizer_common.h index 5639134..6b10488 100644 --- a/libsanitizer/sanitizer_common/sanitizer_common.h +++ b/libsanitizer/sanitizer_common/sanitizer_common.h @@ -42,9 +42,13 @@ void GetThreadStackTopAndBottom(bool at_initialization, uptr *stack_top, void *MmapOrDie(uptr size, const char *mem_type); void UnmapOrDie(void *addr, uptr size); void *MmapFixedNoReserve(uptr fixed_addr, uptr size); +void *MmapFixedOrDie(uptr fixed_addr, uptr size); void *Mprotect(uptr fixed_addr, uptr size); +// Map aligned chunk of address space; size and alignment are powers of two. +void *MmapAlignedOrDie(uptr size, uptr alignment, const char *mem_type); // Used to check if we can map shadow memory to a fixed location. bool MemoryRangeIsAvailable(uptr range_start, uptr range_end); +void FlushUnneededShadowMemory(uptr addr, uptr size); // Internal allocator void *InternalAlloc(uptr size); @@ -119,6 +123,7 @@ const char *GetPwd(); void ReExec(); bool StackSizeIsUnlimited(); void SetStackSizeLimitInBytes(uptr limit); +void PrepareForSandboxing(); // Other void SleepForSeconds(int seconds); @@ -133,6 +138,13 @@ void NORETURN Die(); void NORETURN SANITIZER_INTERFACE_ATTRIBUTE CheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2); +// Set the name of the current thread to 'name', return true on succees. +// The name may be truncated to a system-dependent limit. +bool SanitizerSetThreadName(const char *name); +// Get the name of the current thread (no more than max_len bytes), +// return true on succees. name should have space for at least max_len+1 bytes. +bool SanitizerGetThreadName(char *name, int max_len); + // Specific tools may override behavior of "Die" and "CheckFailed" functions // to do tool-specific job. void SetDieCallback(void (*callback)(void)); @@ -148,6 +160,12 @@ INLINE uptr RoundUpTo(uptr size, uptr boundary) { CHECK(IsPowerOfTwo(boundary)); return (size + boundary - 1) & ~(boundary - 1); } +INLINE uptr RoundDownTo(uptr x, uptr boundary) { + return x & ~(boundary - 1); +} +INLINE bool IsAligned(uptr a, uptr alignment) { + return (a & (alignment - 1)) == 0; +} // Don't use std::min, std::max or std::swap, to minimize dependency // on libstdc++. template<class T> T Min(T a, T b) { return a < b ? a : b; } diff --git a/libsanitizer/sanitizer_common/sanitizer_common_interceptors.h b/libsanitizer/sanitizer_common/sanitizer_common_interceptors.h new file mode 100644 index 0000000..97c6b6f --- /dev/null +++ b/libsanitizer/sanitizer_common/sanitizer_common_interceptors.h @@ -0,0 +1,77 @@ +//===-- sanitizer_common_interceptors.h -------------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Common function interceptors for tools like AddressSanitizer, +// ThreadSanitizer, MemorySanitizer, etc. +// +// This file should be included into the tool's interceptor file, +// which has to define it's own macros: +// COMMON_INTERCEPTOR_ENTER +// COMMON_INTERCEPTOR_READ_RANGE +// COMMON_INTERCEPTOR_WRITE_RANGE +// +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_COMMON_INTERCEPTORS_H +#define SANITIZER_COMMON_INTERCEPTORS_H + +#include "interception/interception.h" +#include "sanitizer_platform_interceptors.h" + +#if SANITIZER_INTERCEPT_READ +INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) { + COMMON_INTERCEPTOR_ENTER(read, fd, ptr, count); + SSIZE_T res = REAL(read)(fd, ptr, count); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res); + return res; +} +#endif + +#if SANITIZER_INTERCEPT_PREAD +INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) { + COMMON_INTERCEPTOR_ENTER(pread, fd, ptr, count, offset); + SSIZE_T res = REAL(pread)(fd, ptr, count, offset); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res); + return res; +} +#endif + +#if SANITIZER_INTERCEPT_PREAD64 +INTERCEPTOR(SSIZE_T, pread64, int fd, void *ptr, SIZE_T count, OFF64_T offset) { + COMMON_INTERCEPTOR_ENTER(pread64, fd, ptr, count, offset); + SSIZE_T res = REAL(pread64)(fd, ptr, count, offset); + if (res > 0) + COMMON_INTERCEPTOR_WRITE_RANGE(ptr, res); + return res; +} +#endif + +#if SANITIZER_INTERCEPT_READ +# define INIT_READ INTERCEPT_FUNCTION(read) +#else +# define INIT_READ +#endif + +#if SANITIZER_INTERCEPT_PREAD +# define INIT_PREAD INTERCEPT_FUNCTION(pread) +#else +# define INIT_PREAD +#endif + +#if SANITIZER_INTERCEPT_PREAD64 +# define INIT_PREAD64 INTERCEPT_FUNCTION(pread64) +#else +# define INIT_PREAD64 +#endif + +#define SANITIZER_COMMON_INTERCEPTORS_INIT \ + INIT_READ; \ + INIT_PREAD; \ + INIT_PREAD64; \ + +#endif // SANITIZER_COMMON_INTERCEPTORS_H diff --git a/libsanitizer/sanitizer_common/sanitizer_libc.cc b/libsanitizer/sanitizer_common/sanitizer_libc.cc index 4d43cd7..b02cbd4a 100644 --- a/libsanitizer/sanitizer_common/sanitizer_libc.cc +++ b/libsanitizer/sanitizer_common/sanitizer_libc.cc @@ -203,4 +203,23 @@ s64 internal_simple_strtoll(const char *nptr, char **endptr, int base) { } } +bool mem_is_zero(const char *beg, uptr size) { + CHECK_LE(size, 1UL << FIRST_32_SECOND_64(30, 40)); // Sanity check. + const char *end = beg + size; + uptr *aligned_beg = (uptr *)RoundUpTo((uptr)beg, sizeof(uptr)); + uptr *aligned_end = (uptr *)RoundDownTo((uptr)end, sizeof(uptr)); + uptr all = 0; + // Prologue. + for (const char *mem = beg; mem < (char*)aligned_beg && mem < end; mem++) + all |= *mem; + // Aligned loop. + for (; aligned_beg < aligned_end; aligned_beg++) + all |= *aligned_beg; + // Epilogue. + if ((char*)aligned_end >= beg) + for (const char *mem = (char*)aligned_end; mem < end; mem++) + all |= *mem; + return all == 0; +} + } // namespace __sanitizer diff --git a/libsanitizer/sanitizer_common/sanitizer_libc.h b/libsanitizer/sanitizer_common/sanitizer_libc.h index 4aa4a27..f193017 100644 --- a/libsanitizer/sanitizer_common/sanitizer_libc.h +++ b/libsanitizer/sanitizer_common/sanitizer_libc.h @@ -45,6 +45,11 @@ char *internal_strstr(const char *haystack, const char *needle); // Works only for base=10 and doesn't set errno. s64 internal_simple_strtoll(const char *nptr, char **endptr, int base); +// Return true if all bytes in [mem, mem+size) are zero. +// Optimized for the case when the result is true. +bool mem_is_zero(const char *mem, uptr size); + + // Memory void *internal_mmap(void *addr, uptr length, int prot, int flags, int fd, u64 offset); diff --git a/libsanitizer/sanitizer_common/sanitizer_linux.cc b/libsanitizer/sanitizer_common/sanitizer_linux.cc index 75f2ee1..1d0bf02 100644 --- a/libsanitizer/sanitizer_common/sanitizer_linux.cc +++ b/libsanitizer/sanitizer_common/sanitizer_linux.cc @@ -17,6 +17,7 @@ #include "sanitizer_mutex.h" #include "sanitizer_placement_new.h" #include "sanitizer_procmaps.h" +#include "sanitizer_stacktrace.h" #include <fcntl.h> #include <pthread.h> @@ -28,7 +29,9 @@ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> +#include <unwind.h> #include <errno.h> +#include <sys/prctl.h> // Are we using 32-bit or 64-bit syscalls? // x32 (which defines __x86_64__) has SANITIZER_WORDSIZE == 32 @@ -215,6 +218,14 @@ void ReExec() { execv(argv[0], argv.data()); } +void PrepareForSandboxing() { + // Some kinds of sandboxes may forbid filesystem access, so we won't be able + // to read the file mappings from /proc/self/maps. Luckily, neither the + // process will be able to load additional libraries, so it's fine to use the + // cached mappings. + MemoryMappingLayout::CacheMemoryMappings(); +} + // ----------------- sanitizer_procmaps.h // Linker initialized. ProcSelfMapsBuff MemoryMappingLayout::cached_proc_self_maps_; @@ -354,6 +365,75 @@ bool MemoryMappingLayout::GetObjectNameAndOffset(uptr addr, uptr *offset, return IterateForObjectNameAndOffset(addr, offset, filename, filename_size); } +bool SanitizerSetThreadName(const char *name) { + return 0 == prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); // NOLINT +} + +bool SanitizerGetThreadName(char *name, int max_len) { + char buff[17]; + if (prctl(PR_GET_NAME, (unsigned long)buff, 0, 0, 0)) // NOLINT + return false; + internal_strncpy(name, buff, max_len); + name[max_len] = 0; + return true; +} + +#ifndef SANITIZER_GO +//------------------------- SlowUnwindStack ----------------------------------- +#ifdef __arm__ +#define UNWIND_STOP _URC_END_OF_STACK +#define UNWIND_CONTINUE _URC_NO_REASON +#else +#define UNWIND_STOP _URC_NORMAL_STOP +#define UNWIND_CONTINUE _URC_NO_REASON +#endif + +uptr Unwind_GetIP(struct _Unwind_Context *ctx) { +#ifdef __arm__ + uptr val; + _Unwind_VRS_Result res = _Unwind_VRS_Get(ctx, _UVRSC_CORE, + 15 /* r15 = PC */, _UVRSD_UINT32, &val); + CHECK(res == _UVRSR_OK && "_Unwind_VRS_Get failed"); + // Clear the Thumb bit. + return val & ~(uptr)1; +#else + return _Unwind_GetIP(ctx); +#endif +} + +_Unwind_Reason_Code Unwind_Trace(struct _Unwind_Context *ctx, void *param) { + StackTrace *b = (StackTrace*)param; + CHECK(b->size < b->max_size); + uptr pc = Unwind_GetIP(ctx); + b->trace[b->size++] = pc; + if (b->size == b->max_size) return UNWIND_STOP; + return UNWIND_CONTINUE; +} + +static bool MatchPc(uptr cur_pc, uptr trace_pc) { + return cur_pc - trace_pc <= 64 || trace_pc - cur_pc <= 64; +} + +void StackTrace::SlowUnwindStack(uptr pc, uptr max_depth) { + this->size = 0; + this->max_size = max_depth; + if (max_depth > 1) { + _Unwind_Backtrace(Unwind_Trace, this); + // We need to pop a few frames so that pc is on top. + // trace[0] belongs to the current function so we always pop it. + int to_pop = 1; + /**/ if (size > 1 && MatchPc(pc, trace[1])) to_pop = 1; + else if (size > 2 && MatchPc(pc, trace[2])) to_pop = 2; + else if (size > 3 && MatchPc(pc, trace[3])) to_pop = 3; + else if (size > 4 && MatchPc(pc, trace[4])) to_pop = 4; + else if (size > 5 && MatchPc(pc, trace[5])) to_pop = 5; + this->PopStackFrames(to_pop); + } + this->trace[0] = pc; +} + +#endif // #ifndef SANITIZER_GO + } // namespace __sanitizer #endif // __linux__ diff --git a/libsanitizer/sanitizer_common/sanitizer_mac.cc b/libsanitizer/sanitizer_common/sanitizer_mac.cc index 465d0a3..0f64b30 100644 --- a/libsanitizer/sanitizer_common/sanitizer_mac.cc +++ b/libsanitizer/sanitizer_common/sanitizer_mac.cc @@ -124,6 +124,10 @@ void ReExec() { UNIMPLEMENTED(); } +void PrepareForSandboxing() { + // Nothing here for now. +} + // ----------------- sanitizer_procmaps.h MemoryMappingLayout::MemoryMappingLayout() { diff --git a/libsanitizer/sanitizer_common/sanitizer_platform_interceptors.h b/libsanitizer/sanitizer_common/sanitizer_platform_interceptors.h new file mode 100644 index 0000000..e32206c --- /dev/null +++ b/libsanitizer/sanitizer_common/sanitizer_platform_interceptors.h @@ -0,0 +1,27 @@ +//===-- sanitizer_platform_interceptors.h -----------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines macro telling whether sanitizer tools can/should intercept +// given library functions on a given platform. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_internal_defs.h" + +#if !defined(_WIN32) +# define SANITIZER_INTERCEPT_READ 1 +# define SANITIZER_INTERCEPT_PREAD 1 +#else +# define SANITIZER_INTERCEPT_READ 0 +# define SANITIZER_INTERCEPT_PREAD 0 +#endif + +#if defined(__linux__) && !defined(ANDROID) +# define SANITIZER_INTERCEPT_PREAD64 1 +#else +# define SANITIZER_INTERCEPT_PREAD64 0 +#endif diff --git a/libsanitizer/sanitizer_common/sanitizer_posix.cc b/libsanitizer/sanitizer_common/sanitizer_posix.cc index b9601eaa..17287cd 100644 --- a/libsanitizer/sanitizer_common/sanitizer_posix.cc +++ b/libsanitizer/sanitizer_common/sanitizer_posix.cc @@ -91,6 +91,21 @@ void *MmapFixedNoReserve(uptr fixed_addr, uptr size) { return p; } +void *MmapFixedOrDie(uptr fixed_addr, uptr size) { + uptr PageSize = GetPageSizeCached(); + void *p = internal_mmap((void*)(fixed_addr & ~(PageSize - 1)), + RoundUpTo(size, PageSize), + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_FIXED, + -1, 0); + if (p == (void*)-1) { + Report("ERROR: Failed to allocate 0x%zx (%zd) bytes at address %p (%d)\n", + size, size, fixed_addr, errno); + CHECK("unable to mmap" && 0); + } + return p; +} + void *Mprotect(uptr fixed_addr, uptr size) { return internal_mmap((void*)fixed_addr, size, PROT_NONE, @@ -98,6 +113,10 @@ void *Mprotect(uptr fixed_addr, uptr size) { -1, 0); } +void FlushUnneededShadowMemory(uptr addr, uptr size) { + madvise((void*)addr, size, MADV_DONTNEED); +} + void *MapFileToMemory(const char *file_name, uptr *buff_size) { fd_t fd = internal_open(file_name, false); CHECK_NE(fd, kInvalidFd); diff --git a/libsanitizer/sanitizer_common/sanitizer_printf.cc b/libsanitizer/sanitizer_common/sanitizer_printf.cc index 5876fef..7771e1d 100644 --- a/libsanitizer/sanitizer_common/sanitizer_printf.cc +++ b/libsanitizer/sanitizer_common/sanitizer_printf.cc @@ -92,7 +92,7 @@ static int AppendPointer(char **buff, const char *buff_end, u64 ptr_value) { int VSNPrintf(char *buff, int buff_length, const char *format, va_list args) { static const char *kPrintfFormatsHelp = - "Supported Printf formats: %%(0[0-9]*)?(z|ll)?{d,u,x}; %%p; %%s; %%c\n"; + "Supported Printf formats: %(0[0-9]*)?(z|ll)?{d,u,x}; %p; %s; %c\n"; RAW_CHECK(format); RAW_CHECK(buff_length > 0); const char *buff_end = &buff[buff_length - 1]; diff --git a/libsanitizer/sanitizer_common/sanitizer_report_decorator.h b/libsanitizer/sanitizer_common/sanitizer_report_decorator.h new file mode 100644 index 0000000..17f0b2e --- /dev/null +++ b/libsanitizer/sanitizer_common/sanitizer_report_decorator.h @@ -0,0 +1,35 @@ +//===-- sanitizer_report_decorator.h ----------------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Tags to decorate the sanitizer reports. +// Currently supported tags: +// * None. +// * ANSI color sequences. +// +//===----------------------------------------------------------------------===// + +#ifndef SANITIZER_ALLOCATOR_H +#define SANITIZER_ALLOCATOR_H + +namespace __sanitizer { +class AnsiColorDecorator { + public: + explicit AnsiColorDecorator(bool use_ansi_colors) : ansi_(use_ansi_colors) { } + const char *Black() { return ansi_ ? "\033[1m\033[30m" : ""; } + const char *Red() { return ansi_ ? "\033[1m\033[31m" : ""; } + const char *Green() { return ansi_ ? "\033[1m\033[32m" : ""; } + const char *Yellow() { return ansi_ ? "\033[1m\033[33m" : ""; } + const char *Blue() { return ansi_ ? "\033[1m\033[34m" : ""; } + const char *Magenta() { return ansi_ ? "\033[1m\033[35m" : ""; } + const char *Cyan() { return ansi_ ? "\033[1m\033[36m" : ""; } + const char *White() { return ansi_ ? "\033[1m\033[37m" : ""; } + const char *Default() { return ansi_ ? "\033[1m\033[0m" : ""; } + private: + bool ansi_; +}; +} // namespace __sanitizer +#endif // SANITIZER_ALLOCATOR_H diff --git a/libsanitizer/sanitizer_common/sanitizer_stackdepot.cc b/libsanitizer/sanitizer_common/sanitizer_stackdepot.cc index d9c5b69..2e22155 100644 --- a/libsanitizer/sanitizer_common/sanitizer_stackdepot.cc +++ b/libsanitizer/sanitizer_common/sanitizer_stackdepot.cc @@ -40,6 +40,12 @@ static struct { atomic_uint32_t seq[kPartCount]; // Unique id generators. } depot; +static StackDepotStats stats; + +StackDepotStats *StackDepotGetStats() { + return &stats; +} + static u32 hash(const uptr *stack, uptr size) { // murmur2 const u32 m = 0x5bd1e995; @@ -75,7 +81,7 @@ static StackDesc *tryallocDesc(uptr memsz) { } static StackDesc *allocDesc(uptr size) { - // Frist, try to allocate optimisitically. + // First, try to allocate optimisitically. uptr memsz = sizeof(StackDesc) + (size - 1) * sizeof(uptr); StackDesc *s = tryallocDesc(memsz); if (s) @@ -91,6 +97,7 @@ static StackDesc *allocDesc(uptr size) { if (allocsz < memsz) allocsz = memsz; uptr mem = (uptr)MmapOrDie(allocsz, "stack depot"); + stats.mapped += allocsz; atomic_store(&depot.region_end, mem + allocsz, memory_order_release); atomic_store(&depot.region_pos, mem, memory_order_release); } @@ -154,6 +161,7 @@ u32 StackDepotPut(const uptr *stack, uptr size) { } uptr part = (h % kTabSize) / kPartSize; id = atomic_fetch_add(&depot.seq[part], 1, memory_order_relaxed) + 1; + stats.n_uniq_ids++; CHECK_LT(id, kMaxId); id |= part << kPartShift; CHECK_NE(id, 0); diff --git a/libsanitizer/sanitizer_common/sanitizer_stackdepot.h b/libsanitizer/sanitizer_common/sanitizer_stackdepot.h index c4c388a..1e917eb 100644 --- a/libsanitizer/sanitizer_common/sanitizer_stackdepot.h +++ b/libsanitizer/sanitizer_common/sanitizer_stackdepot.h @@ -22,6 +22,13 @@ u32 StackDepotPut(const uptr *stack, uptr size); // Retrieves a stored stack trace by the id. const uptr *StackDepotGet(u32 id, uptr *size); +struct StackDepotStats { + uptr n_uniq_ids; + uptr mapped; +}; + +StackDepotStats *StackDepotGetStats(); + } // namespace __sanitizer #endif // SANITIZER_STACKDEPOT_H diff --git a/libsanitizer/sanitizer_common/sanitizer_stacktrace.cc b/libsanitizer/sanitizer_common/sanitizer_stacktrace.cc index 308c2d9..59af1c3 100644 --- a/libsanitizer/sanitizer_common/sanitizer_stacktrace.cc +++ b/libsanitizer/sanitizer_common/sanitizer_stacktrace.cc @@ -23,10 +23,7 @@ static const char *StripPathPrefix(const char *filepath, } // ----------------------- StackTrace ----------------------------- {{{1 -// PCs in stack traces are actually the return addresses, that is, -// addresses of the next instructions after the call. That's why we -// decrement them. -static uptr patch_pc(uptr pc) { +uptr StackTrace::GetPreviousInstructionPc(uptr pc) { #ifdef __arm__ // Cancel Thumb bit. pc = pc & (~1); @@ -69,7 +66,9 @@ void StackTrace::PrintStack(const uptr *addr, uptr size, InternalScopedBuffer<AddressInfo> addr_frames(64); uptr frame_num = 0; for (uptr i = 0; i < size && addr[i]; i++) { - uptr pc = patch_pc(addr[i]); + // PCs in stack traces are actually the return addresses, that is, + // addresses of the next instructions after the call. + uptr pc = GetPreviousInstructionPc(addr[i]); uptr addr_frames_num = 0; // The number of stack frames for current // instruction address. if (symbolize_callback) { diff --git a/libsanitizer/sanitizer_common/sanitizer_stacktrace.h b/libsanitizer/sanitizer_common/sanitizer_stacktrace.h index b36a1a0..c939644 100644 --- a/libsanitizer/sanitizer_common/sanitizer_stacktrace.h +++ b/libsanitizer/sanitizer_common/sanitizer_stacktrace.h @@ -42,10 +42,12 @@ struct StackTrace { } void FastUnwindStack(uptr pc, uptr bp, uptr stack_top, uptr stack_bottom); + void SlowUnwindStack(uptr pc, uptr max_depth); void PopStackFrames(uptr count); static uptr GetCurrentPc(); + static uptr GetPreviousInstructionPc(uptr pc); static uptr CompressStack(StackTrace *stack, u32 *compressed, uptr size); diff --git a/libsanitizer/sanitizer_common/sanitizer_symbolizer.h b/libsanitizer/sanitizer_common/sanitizer_symbolizer.h index 4d7ec17..0714b38 100644 --- a/libsanitizer/sanitizer_common/sanitizer_symbolizer.h +++ b/libsanitizer/sanitizer_common/sanitizer_symbolizer.h @@ -58,6 +58,9 @@ struct AddressInfo { uptr SymbolizeCode(uptr address, AddressInfo *frames, uptr max_frames); bool SymbolizeData(uptr address, AddressInfo *frame); +// Attempts to demangle the provided C++ mangled name. +const char *Demangle(const char *Name); + // Starts external symbolizer program in a subprocess. Sanitizer communicates // with external symbolizer via pipes. bool InitializeExternalSymbolizer(const char *path_to_symbolizer); diff --git a/libsanitizer/sanitizer_common/sanitizer_symbolizer_itanium.cc b/libsanitizer/sanitizer_common/sanitizer_symbolizer_itanium.cc new file mode 100644 index 0000000..b356f9a --- /dev/null +++ b/libsanitizer/sanitizer_common/sanitizer_symbolizer_itanium.cc @@ -0,0 +1,40 @@ +//===-- sanitizer_symbolizer_itanium.cc -----------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is shared between the sanitizer run-time libraries. +// Itanium C++ ABI-specific implementation of symbolizer parts. +//===----------------------------------------------------------------------===// +#if defined(__APPLE__) || defined(__linux__) + +#include "sanitizer_symbolizer.h" + +#include <stdlib.h> + +// C++ demangling function, as required by Itanium C++ ABI. This is weak, +// because we do not require a C++ ABI library to be linked to a program +// using sanitizers; if it's not present, we'll just use the mangled name. +namespace __cxxabiv1 { + extern "C" char *__cxa_demangle(const char *mangled, char *buffer, + size_t *length, int *status) + SANITIZER_WEAK_ATTRIBUTE; +} + +const char *__sanitizer::Demangle(const char *MangledName) { + // FIXME: __cxa_demangle aggressively insists on allocating memory. + // There's not much we can do about that, short of providing our + // own demangler (libc++abi's implementation could be adapted so that + // it does not allocate). For now, we just call it anyway, and we leak + // the returned value. + if (__cxxabiv1::__cxa_demangle) + if (const char *Demangled = + __cxxabiv1::__cxa_demangle(MangledName, 0, 0, 0)) + return Demangled; + + return MangledName; +} + +#endif // __APPLE__ || __linux__ diff --git a/libsanitizer/sanitizer_common/sanitizer_symbolizer_win.cc b/libsanitizer/sanitizer_common/sanitizer_symbolizer_win.cc index 3b81e79..ad00532 100644 --- a/libsanitizer/sanitizer_common/sanitizer_symbolizer_win.cc +++ b/libsanitizer/sanitizer_common/sanitizer_symbolizer_win.cc @@ -26,6 +26,10 @@ uptr GetListOfModules(LoadedModule *modules, uptr max_modules) { UNIMPLEMENTED(); }; +const char *Demangle(const char *MangledName) { + return MangledName; +} + } // namespace __sanitizer #endif // _WIN32 diff --git a/libsanitizer/sanitizer_common/sanitizer_win.cc b/libsanitizer/sanitizer_common/sanitizer_win.cc index 15ef7d9..f7300a1 100644 --- a/libsanitizer/sanitizer_common/sanitizer_win.cc +++ b/libsanitizer/sanitizer_common/sanitizer_win.cc @@ -13,6 +13,7 @@ #define WIN32_LEAN_AND_MEAN #define NOGDI #include <stdlib.h> +#include <io.h> #include <windows.h> #include "sanitizer_common.h" @@ -73,6 +74,8 @@ void UnmapOrDie(void *addr, uptr size) { } void *MmapFixedNoReserve(uptr fixed_addr, uptr size) { + // FIXME: is this really "NoReserve"? On Win32 this does not matter much, + // but on Win64 it does. void *p = VirtualAlloc((LPVOID)fixed_addr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (p == 0) @@ -81,6 +84,10 @@ void *MmapFixedNoReserve(uptr fixed_addr, uptr size) { return p; } +void *MmapFixedOrDie(uptr fixed_addr, uptr size) { + return MmapFixedNoReserve(fixed_addr, size); +} + void *Mprotect(uptr fixed_addr, uptr size) { return VirtualAlloc((LPVOID)fixed_addr, size, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS); @@ -127,6 +134,10 @@ void ReExec() { UNIMPLEMENTED(); } +void PrepareForSandboxing() { + // Nothing here for now. +} + bool StackSizeIsUnlimited() { UNIMPLEMENTED(); } @@ -173,7 +184,7 @@ int internal_close(fd_t fd) { } int internal_isatty(fd_t fd) { - UNIMPLEMENTED(); + return _isatty(fd); } fd_t internal_open(const char *filename, bool write) { diff --git a/libsanitizer/tsan/Makefile.am b/libsanitizer/tsan/Makefile.am index 26d1af2..fa9c26c 100644 --- a/libsanitizer/tsan/Makefile.am +++ b/libsanitizer/tsan/Makefile.am @@ -31,6 +31,9 @@ tsan_files = \ tsan_interface_ann.cc \ tsan_mman.cc \ tsan_rtl_report.cc \ + tsan_fd.cc \ + tsan_interface_java.cc \ + tsan_mutexset.cc \ tsan_symbolize_addr2line_linux.cc libtsan_la_SOURCES = $(tsan_files) diff --git a/libsanitizer/tsan/Makefile.in b/libsanitizer/tsan/Makefile.in index 3749819..c739e70 100644 --- a/libsanitizer/tsan/Makefile.in +++ b/libsanitizer/tsan/Makefile.in @@ -87,7 +87,8 @@ am__objects_1 = tsan_clock.lo tsan_interface_atomic.lo tsan_mutex.lo \ tsan_rtl.lo tsan_stat.lo tsan_sync.lo tsan_interceptors.lo \ tsan_md5.lo tsan_platform_mac.lo tsan_rtl_mutex.lo \ tsan_suppressions.lo tsan_interface_ann.lo tsan_mman.lo \ - tsan_rtl_report.lo tsan_symbolize_addr2line_linux.lo + tsan_rtl_report.lo tsan_fd.lo tsan_interface_java.lo \ + tsan_mutexset.lo tsan_symbolize_addr2line_linux.lo am_libtsan_la_OBJECTS = $(am__objects_1) libtsan_la_OBJECTS = $(am_libtsan_la_OBJECTS) libtsan_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ @@ -273,6 +274,9 @@ tsan_files = \ tsan_interface_ann.cc \ tsan_mman.cc \ tsan_rtl_report.cc \ + tsan_fd.cc \ + tsan_interface_java.cc \ + tsan_mutexset.cc \ tsan_symbolize_addr2line_linux.cc libtsan_la_SOURCES = $(tsan_files) @@ -393,14 +397,17 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_clock.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_fd.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_flags.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interceptors.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_ann.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_atomic.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_interface_java.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_md5.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mman.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mutex.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_mutexset.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_platform_linux.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_platform_mac.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tsan_report.Plo@am__quote@ diff --git a/libsanitizer/tsan/tsan_defs.h b/libsanitizer/tsan/tsan_defs.h index c14a6d1..6683a4e 100644 --- a/libsanitizer/tsan/tsan_defs.h +++ b/libsanitizer/tsan/tsan_defs.h @@ -137,6 +137,12 @@ T RoundDown(T p, u64 align) { return (T)((u64)p & ~(align - 1)); } +// Zeroizes high part, returns 'bits' lsb bits. +template<typename T> +T GetLsb(T v, int bits) { + return (T)((u64)v & ((1ull << bits) - 1)); +} + struct MD5Hash { u64 hash[2]; bool operator==(const MD5Hash &other) const; diff --git a/libsanitizer/tsan/tsan_fd.cc b/libsanitizer/tsan/tsan_fd.cc new file mode 100644 index 0000000..9aca9c5 --- /dev/null +++ b/libsanitizer/tsan/tsan_fd.cc @@ -0,0 +1,257 @@ +//===-- tsan_fd.cc --------------------------------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_fd.h" +#include "tsan_rtl.h" +#include <sanitizer_common/sanitizer_atomic.h> + +namespace __tsan { + +const int kTableSizeL1 = 1024; +const int kTableSizeL2 = 1024; +const int kTableSize = kTableSizeL1 * kTableSizeL2; + +struct FdSync { + atomic_uint64_t rc; +}; + +struct FdDesc { + FdSync *sync; + int creation_tid; + u32 creation_stack; +}; + +struct FdContext { + atomic_uintptr_t tab[kTableSizeL1]; + // Addresses used for synchronization. + FdSync globsync; + FdSync filesync; + FdSync socksync; + u64 connectsync; +}; + +static FdContext fdctx; + +static FdSync *allocsync() { + FdSync *s = (FdSync*)internal_alloc(MBlockFD, sizeof(FdSync)); + atomic_store(&s->rc, 1, memory_order_relaxed); + return s; +} + +static FdSync *ref(FdSync *s) { + if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) + atomic_fetch_add(&s->rc, 1, memory_order_relaxed); + return s; +} + +static void unref(ThreadState *thr, uptr pc, FdSync *s) { + if (s && atomic_load(&s->rc, memory_order_relaxed) != (u64)-1) { + if (atomic_fetch_sub(&s->rc, 1, memory_order_acq_rel) == 1) { + CHECK_NE(s, &fdctx.globsync); + CHECK_NE(s, &fdctx.filesync); + CHECK_NE(s, &fdctx.socksync); + SyncVar *v = CTX()->synctab.GetAndRemove(thr, pc, (uptr)s); + if (v) + DestroyAndFree(v); + internal_free(s); + } + } +} + +static FdDesc *fddesc(ThreadState *thr, uptr pc, int fd) { + CHECK_LT(fd, kTableSize); + atomic_uintptr_t *pl1 = &fdctx.tab[fd / kTableSizeL2]; + uptr l1 = atomic_load(pl1, memory_order_consume); + if (l1 == 0) { + uptr size = kTableSizeL2 * sizeof(FdDesc); + void *p = internal_alloc(MBlockFD, size); + internal_memset(p, 0, size); + MemoryResetRange(thr, (uptr)&fddesc, (uptr)p, size); + if (atomic_compare_exchange_strong(pl1, &l1, (uptr)p, memory_order_acq_rel)) + l1 = (uptr)p; + else + internal_free(p); + } + return &((FdDesc*)l1)[fd % kTableSizeL2]; // NOLINT +} + +// pd must be already ref'ed. +static void init(ThreadState *thr, uptr pc, int fd, FdSync *s) { + FdDesc *d = fddesc(thr, pc, fd); + // As a matter of fact, we don't intercept all close calls. + // See e.g. libc __res_iclose(). + if (d->sync) { + unref(thr, pc, d->sync); + d->sync = 0; + } + if (flags()->io_sync == 0) { + unref(thr, pc, s); + } else if (flags()->io_sync == 1) { + d->sync = s; + } else if (flags()->io_sync == 2) { + unref(thr, pc, s); + d->sync = &fdctx.globsync; + } + d->creation_tid = thr->tid; + d->creation_stack = CurrentStackId(thr, pc); + // To catch races between fd usage and open. + MemoryRangeImitateWrite(thr, pc, (uptr)d, 8); +} + +void FdInit() { + atomic_store(&fdctx.globsync.rc, (u64)-1, memory_order_relaxed); + atomic_store(&fdctx.filesync.rc, (u64)-1, memory_order_relaxed); + atomic_store(&fdctx.socksync.rc, (u64)-1, memory_order_relaxed); +} + +void FdOnFork(ThreadState *thr, uptr pc) { + // On fork() we need to reset all fd's, because the child is going + // close all them, and that will cause races between previous read/write + // and the close. + for (int l1 = 0; l1 < kTableSizeL1; l1++) { + FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed); + if (tab == 0) + break; + for (int l2 = 0; l2 < kTableSizeL2; l2++) { + FdDesc *d = &tab[l2]; + MemoryResetRange(thr, pc, (uptr)d, 8); + } + } +} + +bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack) { + for (int l1 = 0; l1 < kTableSizeL1; l1++) { + FdDesc *tab = (FdDesc*)atomic_load(&fdctx.tab[l1], memory_order_relaxed); + if (tab == 0) + break; + if (addr >= (uptr)tab && addr < (uptr)(tab + kTableSizeL2)) { + int l2 = (addr - (uptr)tab) / sizeof(FdDesc); + FdDesc *d = &tab[l2]; + *fd = l1 * kTableSizeL1 + l2; + *tid = d->creation_tid; + *stack = d->creation_stack; + return true; + } + } + return false; +} + +void FdAcquire(ThreadState *thr, uptr pc, int fd) { + FdDesc *d = fddesc(thr, pc, fd); + FdSync *s = d->sync; + DPrintf("#%d: FdAcquire(%d) -> %p\n", thr->tid, fd, s); + MemoryRead8Byte(thr, pc, (uptr)d); + if (s) + Acquire(thr, pc, (uptr)s); +} + +void FdRelease(ThreadState *thr, uptr pc, int fd) { + FdDesc *d = fddesc(thr, pc, fd); + FdSync *s = d->sync; + DPrintf("#%d: FdRelease(%d) -> %p\n", thr->tid, fd, s); + if (s) + Release(thr, pc, (uptr)s); + MemoryRead8Byte(thr, pc, (uptr)d); +} + +void FdClose(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdClose(%d)\n", thr->tid, fd); + FdDesc *d = fddesc(thr, pc, fd); + // To catch races between fd usage and close. + MemoryWrite8Byte(thr, pc, (uptr)d); + // We need to clear it, because if we do not intercept any call out there + // that creates fd, we will hit false postives. + MemoryResetRange(thr, pc, (uptr)d, 8); + unref(thr, pc, d->sync); + d->sync = 0; + d->creation_tid = 0; + d->creation_stack = 0; +} + +void FdFileCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdFileCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, &fdctx.filesync); +} + +void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd) { + DPrintf("#%d: FdDup(%d, %d)\n", thr->tid, oldfd, newfd); + // Ignore the case when user dups not yet connected socket. + FdDesc *od = fddesc(thr, pc, oldfd); + MemoryRead8Byte(thr, pc, (uptr)od); + FdClose(thr, pc, newfd); + init(thr, pc, newfd, ref(od->sync)); +} + +void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd) { + DPrintf("#%d: FdCreatePipe(%d, %d)\n", thr->tid, rfd, wfd); + FdSync *s = allocsync(); + init(thr, pc, rfd, ref(s)); + init(thr, pc, wfd, ref(s)); + unref(thr, pc, s); +} + +void FdEventCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdEventCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, allocsync()); +} + +void FdSignalCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSignalCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, 0); +} + +void FdInotifyCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdInotifyCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, 0); +} + +void FdPollCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdPollCreate(%d)\n", thr->tid, fd); + init(thr, pc, fd, allocsync()); +} + +void FdSocketCreate(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketCreate(%d)\n", thr->tid, fd); + // It can be a UDP socket. + init(thr, pc, fd, &fdctx.socksync); +} + +void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd) { + DPrintf("#%d: FdSocketAccept(%d, %d)\n", thr->tid, fd, newfd); + // Synchronize connect->accept. + Acquire(thr, pc, (uptr)&fdctx.connectsync); + init(thr, pc, newfd, &fdctx.socksync); +} + +void FdSocketConnecting(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketConnecting(%d)\n", thr->tid, fd); + // Synchronize connect->accept. + Release(thr, pc, (uptr)&fdctx.connectsync); +} + +void FdSocketConnect(ThreadState *thr, uptr pc, int fd) { + DPrintf("#%d: FdSocketConnect(%d)\n", thr->tid, fd); + init(thr, pc, fd, &fdctx.socksync); +} + +uptr File2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +uptr Dir2addr(char *path) { + (void)path; + static u64 addr; + return (uptr)&addr; +} + +} // namespace __tsan diff --git a/libsanitizer/tsan/tsan_fd.h b/libsanitizer/tsan/tsan_fd.h new file mode 100644 index 0000000..b4189a3 --- /dev/null +++ b/libsanitizer/tsan/tsan_fd.h @@ -0,0 +1,62 @@ +//===-- tsan_fd.h -----------------------------------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// This file handles synchronization via IO. +// People use IO for synchronization along the lines of: +// +// int X; +// int client_socket; // initialized elsewhere +// int server_socket; // initialized elsewhere +// +// Thread 1: +// X = 42; +// send(client_socket, ...); +// +// Thread 2: +// if (recv(server_socket, ...) > 0) +// assert(X == 42); +// +// This file determines the scope of the file descriptor (pipe, socket, +// all local files, etc) and executes acquire and release operations on +// the scope as necessary. Some scopes are very fine grained (e.g. pipe +// operations synchronize only with operations on the same pipe), while +// others are corse-grained (e.g. all operations on local files synchronize +// with each other). +//===----------------------------------------------------------------------===// +#ifndef TSAN_FD_H +#define TSAN_FD_H + +#include "tsan_rtl.h" + +namespace __tsan { + +void FdInit(); +void FdAcquire(ThreadState *thr, uptr pc, int fd); +void FdRelease(ThreadState *thr, uptr pc, int fd); +void FdClose(ThreadState *thr, uptr pc, int fd); +void FdFileCreate(ThreadState *thr, uptr pc, int fd); +void FdDup(ThreadState *thr, uptr pc, int oldfd, int newfd); +void FdPipeCreate(ThreadState *thr, uptr pc, int rfd, int wfd); +void FdEventCreate(ThreadState *thr, uptr pc, int fd); +void FdSignalCreate(ThreadState *thr, uptr pc, int fd); +void FdInotifyCreate(ThreadState *thr, uptr pc, int fd); +void FdPollCreate(ThreadState *thr, uptr pc, int fd); +void FdSocketCreate(ThreadState *thr, uptr pc, int fd); +void FdSocketAccept(ThreadState *thr, uptr pc, int fd, int newfd); +void FdSocketConnecting(ThreadState *thr, uptr pc, int fd); +void FdSocketConnect(ThreadState *thr, uptr pc, int fd); +bool FdLocation(uptr addr, int *fd, int *tid, u32 *stack); +void FdOnFork(ThreadState *thr, uptr pc); + +uptr File2addr(char *path); +uptr Dir2addr(char *path); + +} // namespace __tsan + +#endif // TSAN_INTERFACE_H diff --git a/libsanitizer/tsan/tsan_flags.cc b/libsanitizer/tsan/tsan_flags.cc index 1b726e6..630bd75 100644 --- a/libsanitizer/tsan/tsan_flags.cc +++ b/libsanitizer/tsan/tsan_flags.cc @@ -56,6 +56,7 @@ void InitializeFlags(Flags *f, const char *env) { f->running_on_valgrind = false; f->external_symbolizer_path = ""; f->history_size = kGoMode ? 1 : 2; // There are a lot of goroutines in Go. + f->io_sync = 1; // Let a frontend override. OverrideFlags(f); @@ -81,6 +82,7 @@ void InitializeFlags(Flags *f, const char *env) { ParseFlag(env, &f->stop_on_start, "stop_on_start"); ParseFlag(env, &f->external_symbolizer_path, "external_symbolizer_path"); ParseFlag(env, &f->history_size, "history_size"); + ParseFlag(env, &f->io_sync, "io_sync"); if (!f->report_bugs) { f->report_thread_leaks = false; @@ -93,6 +95,12 @@ void InitializeFlags(Flags *f, const char *env) { " (must be [0..7])\n"); Die(); } + + if (f->io_sync < 0 || f->io_sync > 2) { + Printf("ThreadSanitizer: incorrect value for io_sync" + " (must be [0..2])\n"); + Die(); + } } } // namespace __tsan diff --git a/libsanitizer/tsan/tsan_flags.h b/libsanitizer/tsan/tsan_flags.h index 6af96ec..ed27363 100644 --- a/libsanitizer/tsan/tsan_flags.h +++ b/libsanitizer/tsan/tsan_flags.h @@ -75,6 +75,11 @@ struct Flags { // the amount of memory accesses, up to history_size=7 that amounts to // 4M memory accesses. The default value is 2 (128K memory accesses). int history_size; + // Controls level of synchronization implied by IO operations. + // 0 - no synchronization + // 1 - reasonable level of synchronization (write->read) + // 2 - global synchronization of all IO operations + int io_sync; }; Flags *flags(); diff --git a/libsanitizer/tsan/tsan_interceptors.cc b/libsanitizer/tsan/tsan_interceptors.cc index dea6415..88acebf 100644 --- a/libsanitizer/tsan/tsan_interceptors.cc +++ b/libsanitizer/tsan/tsan_interceptors.cc @@ -7,6 +7,8 @@ // // This file is a part of ThreadSanitizer (TSan), a race detector. // +// FIXME: move as many interceptors as possible into +// sanitizer_common/sanitizer_common_interceptors.h //===----------------------------------------------------------------------===// #include "sanitizer_common/sanitizer_atomic.h" @@ -18,6 +20,7 @@ #include "tsan_platform.h" #include "tsan_rtl.h" #include "tsan_mman.h" +#include "tsan_fd.h" using namespace __tsan; // NOLINT @@ -50,6 +53,7 @@ extern "C" void *pthread_self(); extern "C" void _exit(int status); extern "C" int __cxa_atexit(void (*func)(void *arg), void *arg, void *dso); extern "C" int *__errno_location(); +extern "C" int fileno_unlocked(void *stream); const int PTHREAD_MUTEX_RECURSIVE = 1; const int PTHREAD_MUTEX_RECURSIVE_NP = 1; const int kPthreadAttrSize = 56; @@ -124,10 +128,8 @@ static SignalContext *SigCtx(ThreadState *thr) { SignalContext *ctx = (SignalContext*)thr->signal_ctx; if (ctx == 0 && thr->is_alive) { ScopedInRtl in_rtl; - ctx = (SignalContext*)internal_alloc( - MBlockSignal, sizeof(*ctx)); - MemoryResetRange(thr, 0, (uptr)ctx, sizeof(*ctx)); - internal_memset(ctx, 0, sizeof(*ctx)); + ctx = (SignalContext*)MmapOrDie(sizeof(*ctx), "SignalContext"); + MemoryResetRange(thr, (uptr)&SigCtx, (uptr)ctx, sizeof(*ctx)); thr->signal_ctx = ctx; } return ctx; @@ -173,8 +175,8 @@ ScopedInterceptor::~ScopedInterceptor() { StatInc(thr, StatInt_##func); \ const uptr caller_pc = GET_CALLER_PC(); \ ScopedInterceptor si(thr, #func, caller_pc); \ - /* Subtract one from pc as we need current instruction address */ \ - const uptr pc = __sanitizer::StackTrace::GetCurrentPc() - 1; \ + const uptr pc = __sanitizer::StackTrace::GetPreviousInstructionPc( \ + __sanitizer::StackTrace::GetCurrentPc()); \ (void)pc; \ /**/ @@ -306,30 +308,6 @@ TSAN_INTERCEPTOR(void, siglongjmp, void *env, int val) { Die(); } -static uptr fd2addr(int fd) { - (void)fd; - static u64 addr; - return (uptr)&addr; -} - -static uptr epollfd2addr(int fd) { - (void)fd; - static u64 addr; - return (uptr)&addr; -} - -static uptr file2addr(char *path) { - (void)path; - static u64 addr; - return (uptr)&addr; -} - -static uptr dir2addr(char *path) { - (void)path; - static u64 addr; - return (uptr)&addr; -} - TSAN_INTERCEPTOR(void*, malloc, uptr size) { void *p = 0; { @@ -660,7 +638,7 @@ static void thread_finalize(void *v) { SignalContext *sctx = thr->signal_ctx; if (sctx) { thr->signal_ctx = 0; - internal_free(sctx); + UnmapOrDie(sctx, sizeof(*sctx)); } } } @@ -934,11 +912,15 @@ TSAN_INTERCEPTOR(int, pthread_rwlock_unlock, void *m) { return res; } +// libpthread.so contains several versions of pthread_cond_init symbol. +// When we just dlsym() it, we get the wrong (old) version. +/* TSAN_INTERCEPTOR(int, pthread_cond_init, void *c, void *a) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_init, c, a); int res = REAL(pthread_cond_init)(c, a); return res; } +*/ TSAN_INTERCEPTOR(int, pthread_cond_destroy, void *c) { SCOPED_TSAN_INTERCEPTOR(pthread_cond_destroy, c); @@ -1080,11 +1062,188 @@ TSAN_INTERCEPTOR(int, sem_getvalue, void *s, int *sval) { return res; } +TSAN_INTERCEPTOR(int, open, const char *name, int flags, int mode) { + SCOPED_TSAN_INTERCEPTOR(open, name, flags, mode); + int fd = REAL(open)(name, flags, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, open64, const char *name, int flags, int mode) { + SCOPED_TSAN_INTERCEPTOR(open64, name, flags, mode); + int fd = REAL(open64)(name, flags, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, creat, const char *name, int mode) { + SCOPED_TSAN_INTERCEPTOR(creat, name, mode); + int fd = REAL(creat)(name, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, creat64, const char *name, int mode) { + SCOPED_TSAN_INTERCEPTOR(creat64, name, mode); + int fd = REAL(creat64)(name, mode); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, dup, int oldfd) { + SCOPED_TSAN_INTERCEPTOR(dup, oldfd); + int newfd = REAL(dup)(oldfd); + if (oldfd >= 0 && newfd >= 0 && newfd != oldfd) + FdDup(thr, pc, oldfd, newfd); + return newfd; +} + +TSAN_INTERCEPTOR(int, dup2, int oldfd, int newfd) { + SCOPED_TSAN_INTERCEPTOR(dup2, oldfd, newfd); + int newfd2 = REAL(dup2)(oldfd, newfd); + if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd) + FdDup(thr, pc, oldfd, newfd2); + return newfd2; +} + +TSAN_INTERCEPTOR(int, dup3, int oldfd, int newfd, int flags) { + SCOPED_TSAN_INTERCEPTOR(dup3, oldfd, newfd, flags); + int newfd2 = REAL(dup3)(oldfd, newfd, flags); + if (oldfd >= 0 && newfd2 >= 0 && newfd2 != oldfd) + FdDup(thr, pc, oldfd, newfd2); + return newfd2; +} + +TSAN_INTERCEPTOR(int, eventfd, unsigned initval, int flags) { + SCOPED_TSAN_INTERCEPTOR(eventfd, initval, flags); + int fd = REAL(eventfd)(initval, flags); + if (fd >= 0) + FdEventCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, signalfd, int fd, void *mask, int flags) { + SCOPED_TSAN_INTERCEPTOR(signalfd, fd, mask, flags); + if (fd >= 0) + FdClose(thr, pc, fd); + fd = REAL(signalfd)(fd, mask, flags); + if (fd >= 0) + FdSignalCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, inotify_init, int fake) { + SCOPED_TSAN_INTERCEPTOR(inotify_init, fake); + int fd = REAL(inotify_init)(fake); + if (fd >= 0) + FdInotifyCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, inotify_init1, int flags) { + SCOPED_TSAN_INTERCEPTOR(inotify_init1, flags); + int fd = REAL(inotify_init1)(flags); + if (fd >= 0) + FdInotifyCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, socket, int domain, int type, int protocol) { + SCOPED_TSAN_INTERCEPTOR(socket, domain, type, protocol); + int fd = REAL(socket)(domain, type, protocol); + if (fd >= 0) + FdSocketCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, socketpair, int domain, int type, int protocol, int *fd) { + SCOPED_TSAN_INTERCEPTOR(socketpair, domain, type, protocol, fd); + int res = REAL(socketpair)(domain, type, protocol, fd); + if (res == 0 && fd[0] >= 0 && fd[1] >= 0) + FdPipeCreate(thr, pc, fd[0], fd[1]); + return res; +} + +TSAN_INTERCEPTOR(int, connect, int fd, void *addr, unsigned addrlen) { + SCOPED_TSAN_INTERCEPTOR(connect, fd, addr, addrlen); + FdSocketConnecting(thr, pc, fd); + int res = REAL(connect)(fd, addr, addrlen); + if (res == 0 && fd >= 0) + FdSocketConnect(thr, pc, fd); + return res; +} + +TSAN_INTERCEPTOR(int, accept, int fd, void *addr, unsigned *addrlen) { + SCOPED_TSAN_INTERCEPTOR(accept, fd, addr, addrlen); + int fd2 = REAL(accept)(fd, addr, addrlen); + if (fd >= 0 && fd2 >= 0) + FdSocketAccept(thr, pc, fd, fd2); + return fd2; +} + +TSAN_INTERCEPTOR(int, accept4, int fd, void *addr, unsigned *addrlen, int f) { + SCOPED_TSAN_INTERCEPTOR(accept4, fd, addr, addrlen, f); + int fd2 = REAL(accept4)(fd, addr, addrlen, f); + if (fd >= 0 && fd2 >= 0) + FdSocketAccept(thr, pc, fd, fd2); + return fd2; +} + +TSAN_INTERCEPTOR(int, epoll_create, int size) { + SCOPED_TSAN_INTERCEPTOR(epoll_create, size); + int fd = REAL(epoll_create)(size); + if (fd >= 0) + FdPollCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, epoll_create1, int flags) { + SCOPED_TSAN_INTERCEPTOR(epoll_create1, flags); + int fd = REAL(epoll_create1)(flags); + if (fd >= 0) + FdPollCreate(thr, pc, fd); + return fd; +} + +TSAN_INTERCEPTOR(int, close, int fd) { + SCOPED_TSAN_INTERCEPTOR(close, fd); + if (fd >= 0) + FdClose(thr, pc, fd); + return REAL(close)(fd); +} + +TSAN_INTERCEPTOR(int, __close, int fd) { + SCOPED_TSAN_INTERCEPTOR(__close, fd); + if (fd >= 0) + FdClose(thr, pc, fd); + return REAL(__close)(fd); +} + +TSAN_INTERCEPTOR(int, pipe, int *pipefd) { + SCOPED_TSAN_INTERCEPTOR(pipe, pipefd); + int res = REAL(pipe)(pipefd); + if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0) + FdPipeCreate(thr, pc, pipefd[0], pipefd[1]); + return res; +} + +TSAN_INTERCEPTOR(int, pipe2, int *pipefd, int flags) { + SCOPED_TSAN_INTERCEPTOR(pipe2, pipefd, flags); + int res = REAL(pipe2)(pipefd, flags); + if (res == 0 && pipefd[0] >= 0 && pipefd[1] >= 0) + FdPipeCreate(thr, pc, pipefd[0], pipefd[1]); + return res; +} + TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) { SCOPED_TSAN_INTERCEPTOR(read, fd, buf, sz); int res = REAL(read)(fd, buf, sz); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1092,8 +1251,8 @@ TSAN_INTERCEPTOR(long_t, read, int fd, void *buf, long_t sz) { TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) { SCOPED_TSAN_INTERCEPTOR(pread, fd, buf, sz, off); int res = REAL(pread)(fd, buf, sz, off); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1101,8 +1260,8 @@ TSAN_INTERCEPTOR(long_t, pread, int fd, void *buf, long_t sz, unsigned off) { TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) { SCOPED_TSAN_INTERCEPTOR(pread64, fd, buf, sz, off); int res = REAL(pread64)(fd, buf, sz, off); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1110,8 +1269,8 @@ TSAN_INTERCEPTOR(long_t, pread64, int fd, void *buf, long_t sz, u64 off) { TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { SCOPED_TSAN_INTERCEPTOR(readv, fd, vec, cnt); int res = REAL(readv)(fd, vec, cnt); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1119,57 +1278,64 @@ TSAN_INTERCEPTOR(long_t, readv, int fd, void *vec, int cnt) { TSAN_INTERCEPTOR(long_t, preadv64, int fd, void *vec, int cnt, u64 off) { SCOPED_TSAN_INTERCEPTOR(preadv64, fd, vec, cnt, off); int res = REAL(preadv64)(fd, vec, cnt, off); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } TSAN_INTERCEPTOR(long_t, write, int fd, void *buf, long_t sz) { SCOPED_TSAN_INTERCEPTOR(write, fd, buf, sz); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(write)(fd, buf, sz); return res; } TSAN_INTERCEPTOR(long_t, pwrite, int fd, void *buf, long_t sz, unsigned off) { SCOPED_TSAN_INTERCEPTOR(pwrite, fd, buf, sz, off); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(pwrite)(fd, buf, sz, off); return res; } TSAN_INTERCEPTOR(long_t, pwrite64, int fd, void *buf, long_t sz, u64 off) { SCOPED_TSAN_INTERCEPTOR(pwrite64, fd, buf, sz, off); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(pwrite64)(fd, buf, sz, off); return res; } TSAN_INTERCEPTOR(long_t, writev, int fd, void *vec, int cnt) { SCOPED_TSAN_INTERCEPTOR(writev, fd, vec, cnt); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(writev)(fd, vec, cnt); return res; } TSAN_INTERCEPTOR(long_t, pwritev64, int fd, void *vec, int cnt, u64 off) { SCOPED_TSAN_INTERCEPTOR(pwritev64, fd, vec, cnt, off); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(pwritev64)(fd, vec, cnt, off); return res; } TSAN_INTERCEPTOR(long_t, send, int fd, void *buf, long_t len, int flags) { SCOPED_TSAN_INTERCEPTOR(send, fd, buf, len, flags); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(send)(fd, buf, len, flags); return res; } TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { SCOPED_TSAN_INTERCEPTOR(sendmsg, fd, msg, flags); - Release(thr, pc, fd2addr(fd)); + if (fd >= 0) + FdRelease(thr, pc, fd); int res = REAL(sendmsg)(fd, msg, flags); return res; } @@ -1177,8 +1343,8 @@ TSAN_INTERCEPTOR(long_t, sendmsg, int fd, void *msg, int flags) { TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { SCOPED_TSAN_INTERCEPTOR(recv, fd, buf, len, flags); int res = REAL(recv)(fd, buf, len, flags); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } @@ -1186,15 +1352,15 @@ TSAN_INTERCEPTOR(long_t, recv, int fd, void *buf, long_t len, int flags) { TSAN_INTERCEPTOR(long_t, recvmsg, int fd, void *msg, int flags) { SCOPED_TSAN_INTERCEPTOR(recvmsg, fd, msg, flags); int res = REAL(recvmsg)(fd, msg, flags); - if (res >= 0) { - Acquire(thr, pc, fd2addr(fd)); + if (res >= 0 && fd >= 0) { + FdAcquire(thr, pc, fd); } return res; } TSAN_INTERCEPTOR(int, unlink, char *path) { SCOPED_TSAN_INTERCEPTOR(unlink, path); - Release(thr, pc, file2addr(path)); + Release(thr, pc, File2addr(path)); int res = REAL(unlink)(path); return res; } @@ -1202,19 +1368,57 @@ TSAN_INTERCEPTOR(int, unlink, char *path) { TSAN_INTERCEPTOR(void*, fopen, char *path, char *mode) { SCOPED_TSAN_INTERCEPTOR(fopen, path, mode); void *res = REAL(fopen)(path, mode); - Acquire(thr, pc, file2addr(path)); + Acquire(thr, pc, File2addr(path)); + if (res) { + int fd = fileno_unlocked(res); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + } return res; } +TSAN_INTERCEPTOR(void*, freopen, char *path, char *mode, void *stream) { + SCOPED_TSAN_INTERCEPTOR(freopen, path, mode, stream); + if (stream) { + int fd = fileno_unlocked(stream); + if (fd >= 0) + FdClose(thr, pc, fd); + } + void *res = REAL(freopen)(path, mode, stream); + Acquire(thr, pc, File2addr(path)); + if (res) { + int fd = fileno_unlocked(res); + if (fd >= 0) + FdFileCreate(thr, pc, fd); + } + return res; +} + +TSAN_INTERCEPTOR(int, fclose, void *stream) { + { + SCOPED_TSAN_INTERCEPTOR(fclose, stream); + if (stream) { + int fd = fileno_unlocked(stream); + if (fd >= 0) + FdClose(thr, pc, fd); + } + } + return REAL(fclose)(stream); +} + TSAN_INTERCEPTOR(uptr, fread, void *ptr, uptr size, uptr nmemb, void *f) { - SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); - MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + { + SCOPED_TSAN_INTERCEPTOR(fread, ptr, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)ptr, size * nmemb, true); + } return REAL(fread)(ptr, size, nmemb, f); } TSAN_INTERCEPTOR(uptr, fwrite, const void *p, uptr size, uptr nmemb, void *f) { - SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); - MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + { + SCOPED_TSAN_INTERCEPTOR(fwrite, p, size, nmemb, f); + MemoryAccessRange(thr, pc, (uptr)p, size * nmemb, false); + } return REAL(fwrite)(p, size, nmemb, f); } @@ -1226,7 +1430,7 @@ TSAN_INTERCEPTOR(int, puts, const char *s) { TSAN_INTERCEPTOR(int, rmdir, char *path) { SCOPED_TSAN_INTERCEPTOR(rmdir, path); - Release(thr, pc, dir2addr(path)); + Release(thr, pc, Dir2addr(path)); int res = REAL(rmdir)(path); return res; } @@ -1234,14 +1438,15 @@ TSAN_INTERCEPTOR(int, rmdir, char *path) { TSAN_INTERCEPTOR(void*, opendir, char *path) { SCOPED_TSAN_INTERCEPTOR(opendir, path); void *res = REAL(opendir)(path); - Acquire(thr, pc, dir2addr(path)); + if (res != 0) + Acquire(thr, pc, Dir2addr(path)); return res; } TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) { SCOPED_TSAN_INTERCEPTOR(epoll_ctl, epfd, op, fd, ev); - if (op == EPOLL_CTL_ADD) { - Release(thr, pc, epollfd2addr(epfd)); + if (op == EPOLL_CTL_ADD && epfd >= 0) { + FdRelease(thr, pc, epfd); } int res = REAL(epoll_ctl)(epfd, op, fd, ev); return res; @@ -1250,8 +1455,8 @@ TSAN_INTERCEPTOR(int, epoll_ctl, int epfd, int op, int fd, void *ev) { TSAN_INTERCEPTOR(int, epoll_wait, int epfd, void *ev, int cnt, int timeout) { SCOPED_TSAN_INTERCEPTOR(epoll_wait, epfd, ev, cnt, timeout); int res = BLOCK_REAL(epoll_wait)(epfd, ev, cnt, timeout); - if (res > 0) { - Acquire(thr, pc, epollfd2addr(epfd)); + if (res > 0 && epfd >= 0) { + FdAcquire(thr, pc, epfd); } return res; } @@ -1423,6 +1628,19 @@ TSAN_INTERCEPTOR(int, munlockall, void) { return 0; } +TSAN_INTERCEPTOR(int, fork, int fake) { + SCOPED_TSAN_INTERCEPTOR(fork, fake); + // It's intercepted merely to process pending signals. + int pid = REAL(fork)(fake); + if (pid == 0) { + // child + FdOnFork(thr, pc); + } else if (pid > 0) { + // parent + } + return pid; +} + namespace __tsan { void ProcessPendingSignals(ThreadState *thr) { @@ -1545,7 +1763,7 @@ void InitializeInterceptors() { TSAN_INTERCEPT(pthread_rwlock_timedwrlock); TSAN_INTERCEPT(pthread_rwlock_unlock); - TSAN_INTERCEPT(pthread_cond_init); + // TSAN_INTERCEPT(pthread_cond_init); TSAN_INTERCEPT(pthread_cond_destroy); TSAN_INTERCEPT(pthread_cond_signal); TSAN_INTERCEPT(pthread_cond_broadcast); @@ -1566,6 +1784,28 @@ void InitializeInterceptors() { TSAN_INTERCEPT(sem_post); TSAN_INTERCEPT(sem_getvalue); + TSAN_INTERCEPT(open); + TSAN_INTERCEPT(open64); + TSAN_INTERCEPT(creat); + TSAN_INTERCEPT(creat64); + TSAN_INTERCEPT(dup); + TSAN_INTERCEPT(dup2); + TSAN_INTERCEPT(dup3); + TSAN_INTERCEPT(eventfd); + TSAN_INTERCEPT(signalfd); + TSAN_INTERCEPT(inotify_init); + TSAN_INTERCEPT(inotify_init1); + TSAN_INTERCEPT(socket); + TSAN_INTERCEPT(socketpair); + TSAN_INTERCEPT(connect); + TSAN_INTERCEPT(accept); + TSAN_INTERCEPT(accept4); + TSAN_INTERCEPT(epoll_create); + TSAN_INTERCEPT(epoll_create1); + TSAN_INTERCEPT(close); + TSAN_INTERCEPT(pipe); + TSAN_INTERCEPT(pipe2); + TSAN_INTERCEPT(read); TSAN_INTERCEPT(pread); TSAN_INTERCEPT(pread64); @@ -1583,6 +1823,8 @@ void InitializeInterceptors() { TSAN_INTERCEPT(unlink); TSAN_INTERCEPT(fopen); + TSAN_INTERCEPT(freopen); + TSAN_INTERCEPT(fclose); TSAN_INTERCEPT(fread); TSAN_INTERCEPT(fwrite); TSAN_INTERCEPT(puts); @@ -1608,6 +1850,8 @@ void InitializeInterceptors() { TSAN_INTERCEPT(mlockall); TSAN_INTERCEPT(munlockall); + TSAN_INTERCEPT(fork); + // Need to setup it, because interceptors check that the function is resolved. // But atexit is emitted directly into the module, so can't be resolved. REAL(atexit) = (int(*)(void(*)()))unreachable; @@ -1623,6 +1867,8 @@ void InitializeInterceptors() { Printf("ThreadSanitizer: failed to create thread key\n"); Die(); } + + FdInit(); } void internal_start_thread(void(*func)(void *arg), void *arg) { diff --git a/libsanitizer/tsan/tsan_interface_atomic.cc b/libsanitizer/tsan/tsan_interface_atomic.cc index 63860ed..770f8bd 100644 --- a/libsanitizer/tsan/tsan_interface_atomic.cc +++ b/libsanitizer/tsan/tsan_interface_atomic.cc @@ -229,7 +229,7 @@ static T AtomicLoad(ThreadState *thr, uptr pc, const volatile T *a, // Assume the access is atomic. if (!IsAcquireOrder(mo) && sizeof(T) <= sizeof(a)) return *a; - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, false); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, false); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.acquire(&s->clock); T v = *a; @@ -251,7 +251,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v, return; } __sync_synchronize(); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.ReleaseStore(&s->clock); *a = v; @@ -263,7 +263,7 @@ static void AtomicStore(ThreadState *thr, uptr pc, volatile T *a, T v, template<typename T, T (*F)(volatile T *v, T op)> static T AtomicRMW(ThreadState *thr, uptr pc, volatile T *a, T v, morder mo) { - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); if (IsAcqRelOrder(mo)) thr->clock.acq_rel(&s->clock); @@ -322,7 +322,7 @@ template<typename T> static bool AtomicCAS(ThreadState *thr, uptr pc, volatile T *a, T *c, T v, morder mo, morder fmo) { (void)fmo; // Unused because llvm does not pass it yet. - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, (uptr)a, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, (uptr)a, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); if (IsAcqRelOrder(mo)) thr->clock.acq_rel(&s->clock); diff --git a/libsanitizer/tsan/tsan_interface_atomic.h b/libsanitizer/tsan/tsan_interface_atomic.h index 9db31da..c500614 100644 --- a/libsanitizer/tsan/tsan_interface_atomic.h +++ b/libsanitizer/tsan/tsan_interface_atomic.h @@ -26,7 +26,7 @@ typedef long __tsan_atomic64; // NOLINT #if defined(__SIZEOF_INT128__) \ || (__clang_major__ * 100 + __clang_minor__ >= 302) -typedef __int128 __tsan_atomic128; +__extension__ typedef __int128 __tsan_atomic128; #define __TSAN_HAS_INT128 1 #else typedef char __tsan_atomic128; diff --git a/libsanitizer/tsan/tsan_interface_java.cc b/libsanitizer/tsan/tsan_interface_java.cc new file mode 100644 index 0000000..d7325dc --- /dev/null +++ b/libsanitizer/tsan/tsan_interface_java.cc @@ -0,0 +1,303 @@ +//===-- tsan_interface_java.cc --------------------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// + +#include "tsan_interface_java.h" +#include "tsan_rtl.h" +#include "tsan_mutex.h" +#include "sanitizer_common/sanitizer_internal_defs.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_placement_new.h" + +using namespace __tsan; // NOLINT + +namespace __tsan { + +const uptr kHeapShadow = 0x300000000000ull; +const uptr kHeapAlignment = 8; + +struct BlockDesc { + bool begin; + Mutex mtx; + SyncVar *head; + + BlockDesc() + : mtx(MutexTypeJavaMBlock, StatMtxJavaMBlock) + , head() { + CHECK_EQ(begin, false); + begin = true; + } + + ~BlockDesc() { + CHECK_EQ(begin, true); + begin = false; + ThreadState *thr = cur_thread(); + SyncVar *s = head; + while (s) { + SyncVar *s1 = s->next; + StatInc(thr, StatSyncDestroyed); + s->mtx.Lock(); + s->mtx.Unlock(); + thr->mset.Remove(s->GetId()); + DestroyAndFree(s); + s = s1; + } + } +}; + +struct JavaContext { + const uptr heap_begin; + const uptr heap_size; + BlockDesc *heap_shadow; + + JavaContext(jptr heap_begin, jptr heap_size) + : heap_begin(heap_begin) + , heap_size(heap_size) { + uptr size = heap_size / kHeapAlignment * sizeof(BlockDesc); + heap_shadow = (BlockDesc*)MmapFixedNoReserve(kHeapShadow, size); + if ((uptr)heap_shadow != kHeapShadow) { + Printf("ThreadSanitizer: failed to mmap Java heap shadow\n"); + Die(); + } + } +}; + +class ScopedJavaFunc { + public: + ScopedJavaFunc(ThreadState *thr, uptr pc) + : thr_(thr) { + Initialize(thr_); + FuncEntry(thr, pc); + CHECK_EQ(thr_->in_rtl, 0); + thr_->in_rtl++; + } + + ~ScopedJavaFunc() { + thr_->in_rtl--; + CHECK_EQ(thr_->in_rtl, 0); + FuncExit(thr_); + // FIXME(dvyukov): process pending signals. + } + + private: + ThreadState *thr_; +}; + +static u64 jctx_buf[sizeof(JavaContext) / sizeof(u64) + 1]; +static JavaContext *jctx; + +static BlockDesc *getblock(uptr addr) { + uptr i = (addr - jctx->heap_begin) / kHeapAlignment; + return &jctx->heap_shadow[i]; +} + +static uptr USED getmem(BlockDesc *b) { + uptr i = b - jctx->heap_shadow; + uptr p = jctx->heap_begin + i * kHeapAlignment; + CHECK_GE(p, jctx->heap_begin); + CHECK_LT(p, jctx->heap_begin + jctx->heap_size); + return p; +} + +static BlockDesc *getblockbegin(uptr addr) { + for (BlockDesc *b = getblock(addr);; b--) { + CHECK_GE(b, jctx->heap_shadow); + if (b->begin) + return b; + } + return 0; +} + +SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr, + bool write_lock, bool create) { + if (jctx == 0 || addr < jctx->heap_begin + || addr >= jctx->heap_begin + jctx->heap_size) + return 0; + BlockDesc *b = getblockbegin(addr); + DPrintf("#%d: GetJavaSync %p->%p\n", thr->tid, addr, b); + Lock l(&b->mtx); + SyncVar *s = b->head; + for (; s; s = s->next) { + if (s->addr == addr) { + DPrintf("#%d: found existing sync for %p\n", thr->tid, addr); + break; + } + } + if (s == 0 && create) { + DPrintf("#%d: creating new sync for %p\n", thr->tid, addr); + s = CTX()->synctab.Create(thr, pc, addr); + s->next = b->head; + b->head = s; + } + if (s) { + if (write_lock) + s->mtx.Lock(); + else + s->mtx.ReadLock(); + } + return s; +} + +SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr) { + // We do not destroy Java mutexes other than in __tsan_java_free(). + return 0; +} + +} // namespace __tsan { + +#define SCOPED_JAVA_FUNC(func) \ + ThreadState *thr = cur_thread(); \ + const uptr caller_pc = GET_CALLER_PC(); \ + const uptr pc = (uptr)&func; \ + (void)pc; \ + ScopedJavaFunc scoped(thr, caller_pc); \ +/**/ + +void __tsan_java_init(jptr heap_begin, jptr heap_size) { + SCOPED_JAVA_FUNC(__tsan_java_init); + DPrintf("#%d: java_init(%p, %p)\n", thr->tid, heap_begin, heap_size); + CHECK_EQ(jctx, 0); + CHECK_GT(heap_begin, 0); + CHECK_GT(heap_size, 0); + CHECK_EQ(heap_begin % kHeapAlignment, 0); + CHECK_EQ(heap_size % kHeapAlignment, 0); + CHECK_LT(heap_begin, heap_begin + heap_size); + jctx = new(jctx_buf) JavaContext(heap_begin, heap_size); +} + +int __tsan_java_fini() { + SCOPED_JAVA_FUNC(__tsan_java_fini); + DPrintf("#%d: java_fini()\n", thr->tid); + CHECK_NE(jctx, 0); + // FIXME(dvyukov): this does not call atexit() callbacks. + int status = Finalize(thr); + DPrintf("#%d: java_fini() = %d\n", thr->tid, status); + return status; +} + +void __tsan_java_alloc(jptr ptr, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_alloc); + DPrintf("#%d: java_alloc(%p, %p)\n", thr->tid, ptr, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(ptr % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(ptr, jctx->heap_begin); + CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size); + + BlockDesc *b = getblock(ptr); + new(b) BlockDesc(); +} + +void __tsan_java_free(jptr ptr, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_free); + DPrintf("#%d: java_free(%p, %p)\n", thr->tid, ptr, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(ptr % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(ptr, jctx->heap_begin); + CHECK_LE(ptr + size, jctx->heap_begin + jctx->heap_size); + + BlockDesc *beg = getblock(ptr); + BlockDesc *end = getblock(ptr + size); + for (BlockDesc *b = beg; b != end; b++) { + if (b->begin) + b->~BlockDesc(); + } +} + +void __tsan_java_move(jptr src, jptr dst, jptr size) { + SCOPED_JAVA_FUNC(__tsan_java_move); + DPrintf("#%d: java_move(%p, %p, %p)\n", thr->tid, src, dst, size); + CHECK_NE(jctx, 0); + CHECK_NE(size, 0); + CHECK_EQ(src % kHeapAlignment, 0); + CHECK_EQ(dst % kHeapAlignment, 0); + CHECK_EQ(size % kHeapAlignment, 0); + CHECK_GE(src, jctx->heap_begin); + CHECK_LE(src + size, jctx->heap_begin + jctx->heap_size); + CHECK_GE(dst, jctx->heap_begin); + CHECK_LE(dst + size, jctx->heap_begin + jctx->heap_size); + CHECK(dst >= src + size || src >= dst + size); + + // Assuming it's not running concurrently with threads that do + // memory accesses and mutex operations (stop-the-world phase). + { // NOLINT + BlockDesc *s = getblock(src); + BlockDesc *d = getblock(dst); + BlockDesc *send = getblock(src + size); + for (; s != send; s++, d++) { + CHECK_EQ(d->begin, false); + if (s->begin) { + DPrintf("#%d: moving block %p->%p\n", thr->tid, getmem(s), getmem(d)); + new(d) BlockDesc; + d->head = s->head; + for (SyncVar *sync = d->head; sync; sync = sync->next) { + uptr newaddr = sync->addr - src + dst; + DPrintf("#%d: moving sync %p->%p\n", thr->tid, sync->addr, newaddr); + sync->addr = newaddr; + } + s->head = 0; + s->~BlockDesc(); + } + } + } + + { // NOLINT + u64 *s = (u64*)MemToShadow(src); + u64 *d = (u64*)MemToShadow(dst); + u64 *send = (u64*)MemToShadow(src + size); + for (; s != send; s++, d++) { + *d = *s; + *s = 0; + } + } +} + +void __tsan_java_mutex_lock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_lock); + DPrintf("#%d: java_mutex_lock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexLock(thr, pc, addr); +} + +void __tsan_java_mutex_unlock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_unlock); + DPrintf("#%d: java_mutex_unlock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexUnlock(thr, pc, addr); +} + +void __tsan_java_mutex_read_lock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_read_lock); + DPrintf("#%d: java_mutex_read_lock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexReadLock(thr, pc, addr); +} + +void __tsan_java_mutex_read_unlock(jptr addr) { + SCOPED_JAVA_FUNC(__tsan_java_mutex_read_unlock); + DPrintf("#%d: java_mutex_read_unlock(%p)\n", thr->tid, addr); + CHECK_NE(jctx, 0); + CHECK_GE(addr, jctx->heap_begin); + CHECK_LT(addr, jctx->heap_begin + jctx->heap_size); + + MutexReadUnlock(thr, pc, addr); +} diff --git a/libsanitizer/tsan/tsan_interface_java.h b/libsanitizer/tsan/tsan_interface_java.h new file mode 100644 index 0000000..01922bc --- /dev/null +++ b/libsanitizer/tsan/tsan_interface_java.h @@ -0,0 +1,72 @@ +//===-- tsan_interface_java.h -----------------------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// Interface for verification of Java or mixed Java/C++ programs. +// The interface is intended to be used from within a JVM and notify TSan +// about such events like Java locks and GC memory compaction. +// +// For plain memory accesses and function entry/exit a JVM is intended to use +// C++ interfaces: __tsan_readN/writeN and __tsan_func_enter/exit. +// +// For volatile memory accesses and atomic operations JVM is intended to use +// standard atomics API: __tsan_atomicN_load/store/etc. +// +// For usage examples see lit_tests/java_*.cc +//===----------------------------------------------------------------------===// +#ifndef TSAN_INTERFACE_JAVA_H +#define TSAN_INTERFACE_JAVA_H + +#ifndef INTERFACE_ATTRIBUTE +# define INTERFACE_ATTRIBUTE __attribute__((visibility("default"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned long jptr; // NOLINT + +// Must be called before any other callback from Java. +void __tsan_java_init(jptr heap_begin, jptr heap_size) INTERFACE_ATTRIBUTE; +// Must be called when the application exits. +// Not necessary the last callback (concurrently running threads are OK). +// Returns exit status or 0 if tsan does not want to override it. +int __tsan_java_fini() INTERFACE_ATTRIBUTE; + +// Callback for memory allocations. +// May be omitted for allocations that are not subject to data races +// nor contain synchronization objects (e.g. String). +void __tsan_java_alloc(jptr ptr, jptr size) INTERFACE_ATTRIBUTE; +// Callback for memory free. +// Can be aggregated for several objects (preferably). +void __tsan_java_free(jptr ptr, jptr size) INTERFACE_ATTRIBUTE; +// Callback for memory move by GC. +// Can be aggregated for several objects (preferably). +// The ranges must not overlap. +void __tsan_java_move(jptr src, jptr dst, jptr size) INTERFACE_ATTRIBUTE; + +// Mutex lock. +// Addr is any unique address associated with the mutex. +// Must not be called on recursive reentry. +// Object.wait() is handled as a pair of unlock/lock. +void __tsan_java_mutex_lock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex unlock. +void __tsan_java_mutex_unlock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex read lock. +void __tsan_java_mutex_read_lock(jptr addr) INTERFACE_ATTRIBUTE; +// Mutex read unlock. +void __tsan_java_mutex_read_unlock(jptr addr) INTERFACE_ATTRIBUTE; + +#ifdef __cplusplus +} // extern "C" +#endif + +#undef INTERFACE_ATTRIBUTE + +#endif // #ifndef TSAN_INTERFACE_JAVA_H diff --git a/libsanitizer/tsan/tsan_mman.cc b/libsanitizer/tsan/tsan_mman.cc index ba4252e..9a8a524 100644 --- a/libsanitizer/tsan/tsan_mman.cc +++ b/libsanitizer/tsan/tsan_mman.cc @@ -58,8 +58,9 @@ void *user_alloc(ThreadState *thr, uptr pc, uptr sz, uptr align) { void *p = allocator()->Allocate(&thr->alloc_cache, sz, align); if (p == 0) return 0; - MBlock *b = (MBlock*)allocator()->GetMetaData(p); + MBlock *b = new(allocator()->GetMetaData(p)) MBlock; b->size = sz; + b->head = 0; b->alloc_tid = thr->unique_id; b->alloc_stack_id = CurrentStackId(thr, pc); if (CTX() && CTX()->initialized) { @@ -90,6 +91,7 @@ void user_free(ThreadState *thr, uptr pc, void *p) { if (CTX() && CTX()->initialized && thr->in_rtl == 1) { MemoryRangeFreed(thr, pc, (uptr)p, b->size); } + b->~MBlock(); allocator()->Deallocate(&thr->alloc_cache, p); SignalUnsafeCall(thr, pc); } @@ -115,9 +117,11 @@ void *user_realloc(ThreadState *thr, uptr pc, void *p, uptr sz) { } MBlock *user_mblock(ThreadState *thr, void *p) { - // CHECK_GT(thr->in_rtl, 0); CHECK_NE(p, (void*)0); - return (MBlock*)allocator()->GetMetaData(p); + Allocator *a = allocator(); + void *b = a->GetBlockBegin(p); + CHECK_NE(b, 0); + return (MBlock*)a->GetMetaData(b); } void invoke_malloc_hook(void *ptr, uptr size) { diff --git a/libsanitizer/tsan/tsan_mman.h b/libsanitizer/tsan/tsan_mman.h index 326bda7..8697d22 100644 --- a/libsanitizer/tsan/tsan_mman.h +++ b/libsanitizer/tsan/tsan_mman.h @@ -57,6 +57,7 @@ enum MBlockType { MBlockSuppression, MBlockExpectRace, MBlockSignal, + MBlockFD, // This must be the last. MBlockTypeCount diff --git a/libsanitizer/tsan/tsan_mutex.cc b/libsanitizer/tsan/tsan_mutex.cc index 6a1e0ce..716722b 100644 --- a/libsanitizer/tsan/tsan_mutex.cc +++ b/libsanitizer/tsan/tsan_mutex.cc @@ -23,22 +23,28 @@ namespace __tsan { // then Report mutex can be locked while under Threads mutex. // The leaf mutexes can be locked under any other mutexes. // Recursive locking is not supported. +#if TSAN_DEBUG && !TSAN_GO const MutexType MutexTypeLeaf = (MutexType)-1; static MutexType CanLockTab[MutexTypeCount][MutexTypeCount] = { - /*0 MutexTypeInvalid*/ {}, - /*1 MutexTypeTrace*/ {MutexTypeLeaf}, - /*2 MutexTypeThreads*/ {MutexTypeReport}, - /*3 MutexTypeReport*/ {}, - /*4 MutexTypeSyncVar*/ {}, - /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, - /*6 MutexTypeSlab*/ {MutexTypeLeaf}, - /*7 MutexTypeAnnotations*/ {}, - /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, + /*0 MutexTypeInvalid*/ {}, + /*1 MutexTypeTrace*/ {MutexTypeLeaf}, + /*2 MutexTypeThreads*/ {MutexTypeReport}, + /*3 MutexTypeReport*/ {MutexTypeSyncTab, MutexTypeMBlock, + MutexTypeJavaMBlock}, + /*4 MutexTypeSyncVar*/ {}, + /*5 MutexTypeSyncTab*/ {MutexTypeSyncVar}, + /*6 MutexTypeSlab*/ {MutexTypeLeaf}, + /*7 MutexTypeAnnotations*/ {}, + /*8 MutexTypeAtExit*/ {MutexTypeSyncTab}, + /*9 MutexTypeMBlock*/ {MutexTypeSyncVar}, + /*10 MutexTypeJavaMBlock*/ {MutexTypeSyncVar}, }; static bool CanLockAdj[MutexTypeCount][MutexTypeCount]; +#endif void InitializeMutex() { +#if TSAN_DEBUG && !TSAN_GO // Build the "can lock" adjacency matrix. // If [i][j]==true, then one can lock mutex j while under mutex i. const int N = MutexTypeCount; @@ -112,14 +118,18 @@ void InitializeMutex() { Die(); } } +#endif } DeadlockDetector::DeadlockDetector() { // Rely on zero initialization because some mutexes can be locked before ctor. } +#if TSAN_DEBUG && !TSAN_GO void DeadlockDetector::Lock(MutexType t) { // Printf("LOCK %d @%zu\n", t, seq_ + 1); + CHECK_GT(t, MutexTypeInvalid); + CHECK_LT(t, MutexTypeCount); u64 max_seq = 0; u64 max_idx = MutexTypeInvalid; for (int i = 0; i != MutexTypeCount; i++) { @@ -148,6 +158,7 @@ void DeadlockDetector::Unlock(MutexType t) { CHECK(locked_[t]); locked_[t] = 0; } +#endif const uptr kUnlocked = 0; const uptr kWriteLock = 1; diff --git a/libsanitizer/tsan/tsan_mutex.h b/libsanitizer/tsan/tsan_mutex.h index 118066e..6d14505 100644 --- a/libsanitizer/tsan/tsan_mutex.h +++ b/libsanitizer/tsan/tsan_mutex.h @@ -27,6 +27,8 @@ enum MutexType { MutexTypeSlab, MutexTypeAnnotations, MutexTypeAtExit, + MutexTypeMBlock, + MutexTypeJavaMBlock, // This must be the last. MutexTypeCount diff --git a/libsanitizer/tsan/tsan_mutexset.cc b/libsanitizer/tsan/tsan_mutexset.cc new file mode 100644 index 0000000..3ebae3a --- /dev/null +++ b/libsanitizer/tsan/tsan_mutexset.cc @@ -0,0 +1,87 @@ +//===-- tsan_mutexset.cc --------------------------------------------------===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +//===----------------------------------------------------------------------===// +#include "tsan_mutexset.h" +#include "tsan_rtl.h" + +namespace __tsan { + +const uptr MutexSet::kMaxSize; + +MutexSet::MutexSet() { + size_ = 0; + internal_memset(&descs_, 0, sizeof(descs_)); +} + +void MutexSet::Add(u64 id, bool write, u64 epoch) { + // Look up existing mutex with the same id. + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + descs_[i].count++; + descs_[i].epoch = epoch; + return; + } + } + // On overflow, find the oldest mutex and drop it. + if (size_ == kMaxSize) { + u64 minepoch = (u64)-1; + u64 mini = (u64)-1; + for (uptr i = 0; i < size_; i++) { + if (descs_[i].epoch < minepoch) { + minepoch = descs_[i].epoch; + mini = i; + } + } + RemovePos(mini); + CHECK_EQ(size_, kMaxSize - 1); + } + // Add new mutex descriptor. + descs_[size_].id = id; + descs_[size_].write = write; + descs_[size_].epoch = epoch; + descs_[size_].count = 1; + size_++; +} + +void MutexSet::Del(u64 id, bool write) { + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + if (--descs_[i].count == 0) + RemovePos(i); + return; + } + } +} + +void MutexSet::Remove(u64 id) { + for (uptr i = 0; i < size_; i++) { + if (descs_[i].id == id) { + RemovePos(i); + return; + } + } +} + +void MutexSet::RemovePos(uptr i) { + CHECK_LT(i, size_); + descs_[i] = descs_[size_ - 1]; + size_--; +} + +uptr MutexSet::Size() const { + return size_; +} + +MutexSet::Desc MutexSet::Get(uptr i) const { + CHECK_LT(i, size_); + return descs_[i]; +} + +} // namespace __tsan diff --git a/libsanitizer/tsan/tsan_mutexset.h b/libsanitizer/tsan/tsan_mutexset.h new file mode 100644 index 0000000..6924ead --- /dev/null +++ b/libsanitizer/tsan/tsan_mutexset.h @@ -0,0 +1,63 @@ +//===-- tsan_mutexset.h -----------------------------------------*- C++ -*-===// +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of ThreadSanitizer (TSan), a race detector. +// +// MutexSet holds the set of mutexes currently held by a thread. +//===----------------------------------------------------------------------===// +#ifndef TSAN_MUTEXSET_H +#define TSAN_MUTEXSET_H + +#include "tsan_defs.h" + +namespace __tsan { + +class MutexSet { + public: + // Holds limited number of mutexes. + // The oldest mutexes are discarded on overflow. + static const uptr kMaxSize = 64; + struct Desc { + u64 id; + u64 epoch; + int count; + bool write; + }; + + MutexSet(); + // The 'id' is obtained from SyncVar::GetId(). + void Add(u64 id, bool write, u64 epoch); + void Del(u64 id, bool write); + void Remove(u64 id); // Removes the mutex completely (if it's destroyed). + uptr Size() const; + Desc Get(uptr i) const; + + private: +#ifndef TSAN_GO + uptr size_; + Desc descs_[kMaxSize]; +#endif + + void RemovePos(uptr i); +}; + +// Go does not have mutexes, so do not spend memory and time. +// (Go sync.Mutex is actually a semaphore -- can be unlocked +// in different goroutine). +#ifdef TSAN_GO +MutexSet::MutexSet() {} +void MutexSet::Add(u64 id, bool write, u64 epoch) {} +void MutexSet::Del(u64 id, bool write) {} +void MutexSet::Remove(u64 id) {} +void MutexSet::RemovePos(uptr i) {} +uptr MutexSet::Size() const { return 0; } +MutexSet::Desc MutexSet::Get(uptr i) const { return Desc(); } +#endif + +} // namespace __tsan + +#endif // TSAN_REPORT_H diff --git a/libsanitizer/tsan/tsan_platform.h b/libsanitizer/tsan/tsan_platform.h index 5c776f1..9fdc4dd 100644 --- a/libsanitizer/tsan/tsan_platform.h +++ b/libsanitizer/tsan/tsan_platform.h @@ -135,7 +135,6 @@ void FlushShadowMemory(); const char *InitializePlatform(); void FinalizePlatform(); -void MapThreadTrace(uptr addr, uptr size); uptr ALWAYS_INLINE INLINE GetThreadTrace(int tid) { uptr p = kTraceMemBegin + (uptr)tid * kTraceSize * sizeof(Event); DCHECK_LT(p, kTraceMemBegin + kTraceMemSize); diff --git a/libsanitizer/tsan/tsan_platform_linux.cc b/libsanitizer/tsan/tsan_platform_linux.cc index 34221af..2e7cd51 100644 --- a/libsanitizer/tsan/tsan_platform_linux.cc +++ b/libsanitizer/tsan/tsan_platform_linux.cc @@ -69,9 +69,7 @@ uptr GetShadowMemoryConsumption() { } void FlushShadowMemory() { - madvise((void*)kLinuxShadowBeg, - kLinuxShadowEnd - kLinuxShadowBeg, - MADV_DONTNEED); + FlushUnneededShadowMemory(kLinuxShadowBeg, kLinuxShadowEnd - kLinuxShadowBeg); } #ifndef TSAN_GO @@ -118,16 +116,6 @@ void InitializeShadowMemory() { } #endif -void MapThreadTrace(uptr addr, uptr size) { - DPrintf("Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size); - CHECK_GE(addr, kTraceMemBegin); - CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize); - if (addr != (uptr)MmapFixedNoReserve(addr, size)) { - Printf("FATAL: ThreadSanitizer can not mmap thread trace\n"); - Die(); - } -} - static uptr g_data_start; static uptr g_data_end; @@ -180,18 +168,14 @@ static uptr g_tls_size; #else # define INTERNAL_FUNCTION #endif -extern "C" void _dl_get_tls_static_info(size_t*, size_t*) - __attribute__((weak)) INTERNAL_FUNCTION; static int InitTlsSize() { typedef void (*get_tls_func)(size_t*, size_t*) INTERNAL_FUNCTION; - get_tls_func get_tls = &_dl_get_tls_static_info; - if (get_tls == 0) { - void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); - CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr)); - internal_memcpy(&get_tls, &get_tls_static_info_ptr, - sizeof(get_tls_static_info_ptr)); - } + get_tls_func get_tls; + void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info"); + CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr)); + internal_memcpy(&get_tls, &get_tls_static_info_ptr, + sizeof(get_tls_static_info_ptr)); CHECK_NE(get_tls, 0); size_t tls_size = 0; size_t tls_align = 0; @@ -220,29 +204,35 @@ const char *InitializePlatform() { // Disable core dumps, dumping of 16TB usually takes a bit long. setlim(RLIMIT_CORE, 0); } - bool reexec = false; - // TSan doesn't play well with unlimited stack size (as stack - // overlaps with shadow memory). If we detect unlimited stack size, - // we re-exec the program with limited stack size as a best effort. - if (getlim(RLIMIT_STACK) == (rlim_t)-1) { - const uptr kMaxStackSize = 32 * 1024 * 1024; - Report("WARNING: Program is run with unlimited stack size, which " - "wouldn't work with ThreadSanitizer.\n"); - Report("Re-execing with stack size limited to %zd bytes.\n", kMaxStackSize); - SetStackSizeLimitInBytes(kMaxStackSize); - reexec = true; - } - if (getlim(RLIMIT_AS) != (rlim_t)-1) { - Report("WARNING: Program is run with limited virtual address space, which " - "wouldn't work with ThreadSanitizer.\n"); - Report("Re-execing with unlimited virtual address space.\n"); - setlim(RLIMIT_AS, -1); - reexec = true; - } + // Go maps shadow memory lazily and works fine with limited address space. + // Unlimited stack is not a problem as well, because the executable + // is not compiled with -pie. + if (kCppMode) { + bool reexec = false; + // TSan doesn't play well with unlimited stack size (as stack + // overlaps with shadow memory). If we detect unlimited stack size, + // we re-exec the program with limited stack size as a best effort. + if (getlim(RLIMIT_STACK) == (rlim_t)-1) { + const uptr kMaxStackSize = 32 * 1024 * 1024; + Report("WARNING: Program is run with unlimited stack size, which " + "wouldn't work with ThreadSanitizer.\n"); + Report("Re-execing with stack size limited to %zd bytes.\n", + kMaxStackSize); + SetStackSizeLimitInBytes(kMaxStackSize); + reexec = true; + } - if (reexec) - ReExec(); + if (getlim(RLIMIT_AS) != (rlim_t)-1) { + Report("WARNING: Program is run with limited virtual address space," + " which wouldn't work with ThreadSanitizer.\n"); + Report("Re-execing with unlimited virtual address space.\n"); + setlim(RLIMIT_AS, -1); + reexec = true; + } + if (reexec) + ReExec(); + } #ifndef TSAN_GO CheckPIE(); diff --git a/libsanitizer/tsan/tsan_report.cc b/libsanitizer/tsan/tsan_report.cc index 18870a7..ca35266 100644 --- a/libsanitizer/tsan/tsan_report.cc +++ b/libsanitizer/tsan/tsan_report.cc @@ -23,12 +23,24 @@ ReportDesc::ReportDesc() , sleep() { } +ReportMop::ReportMop() + : mset(MBlockReportMutex) { +} + ReportDesc::~ReportDesc() { // FIXME(dvyukov): it must be leaking a lot of memory. } #ifndef TSAN_GO +const int kThreadBufSize = 32; +const char *thread_name(char *buf, int tid) { + if (tid == 0) + return "main thread"; + internal_snprintf(buf, kThreadBufSize, "thread T%d", tid); + return buf; +} + static void PrintHeader(ReportType typ) { Printf("WARNING: ThreadSanitizer: "); @@ -65,52 +77,69 @@ void PrintStack(const ReportStack *ent) { Printf("\n"); } +static void PrintMutexSet(Vector<ReportMopMutex> const& mset) { + for (uptr i = 0; i < mset.Size(); i++) { + if (i == 0) + Printf(" (mutexes:"); + const ReportMopMutex m = mset[i]; + Printf(" %s M%llu", m.write ? "write" : "read", m.id); + Printf(i == mset.Size() - 1 ? ")" : ","); + } +} + static void PrintMop(const ReportMop *mop, bool first) { - Printf(" %s of size %d at %p", + char thrbuf[kThreadBufSize]; + Printf(" %s of size %d at %p by %s", (first ? (mop->write ? "Write" : "Read") : (mop->write ? "Previous write" : "Previous read")), - mop->size, (void*)mop->addr); - if (mop->tid == 0) - Printf(" by main thread:\n"); - else - Printf(" by thread %d:\n", mop->tid); + mop->size, (void*)mop->addr, + thread_name(thrbuf, mop->tid)); + PrintMutexSet(mop->mset); + Printf(":\n"); PrintStack(mop->stack); } static void PrintLocation(const ReportLocation *loc) { + char thrbuf[kThreadBufSize]; if (loc->type == ReportLocationGlobal) { Printf(" Location is global '%s' of size %zu at %zx %s:%d (%s+%p)\n\n", loc->name, loc->size, loc->addr, loc->file, loc->line, loc->module, loc->offset); } else if (loc->type == ReportLocationHeap) { - Printf(" Location is heap block of size %zu at %p allocated", - loc->size, loc->addr); - if (loc->tid == 0) - Printf(" by main thread:\n"); - else - Printf(" by thread %d:\n", loc->tid); + char thrbuf[kThreadBufSize]; + Printf(" Location is heap block of size %zu at %p allocated by %s:\n", + loc->size, loc->addr, thread_name(thrbuf, loc->tid)); PrintStack(loc->stack); } else if (loc->type == ReportLocationStack) { - Printf(" Location is stack of thread %d:\n\n", loc->tid); + Printf(" Location is stack of %s\n\n", thread_name(thrbuf, loc->tid)); + } else if (loc->type == ReportLocationFD) { + Printf(" Location is file descriptor %d created by %s at:\n", + loc->fd, thread_name(thrbuf, loc->tid)); + PrintStack(loc->stack); } } static void PrintMutex(const ReportMutex *rm) { - if (rm->stack == 0) - return; - Printf(" Mutex %d created at:\n", rm->id); - PrintStack(rm->stack); + if (rm->destroyed) { + Printf(" Mutex M%llu is already destroyed.\n\n", rm->id); + } else { + Printf(" Mutex M%llu created at:\n", rm->id); + PrintStack(rm->stack); + } } static void PrintThread(const ReportThread *rt) { if (rt->id == 0) // Little sense in describing the main thread. return; - Printf(" Thread %d", rt->id); + Printf(" Thread T%d", rt->id); if (rt->name) Printf(" '%s'", rt->name); - Printf(" (tid=%zu, %s)", rt->pid, rt->running ? "running" : "finished"); + char thrbuf[kThreadBufSize]; + Printf(" (tid=%zu, %s) created by %s", + rt->pid, rt->running ? "running" : "finished", + thread_name(thrbuf, rt->parent_tid)); if (rt->stack) - Printf(" created at:"); + Printf(" at:"); Printf("\n"); PrintStack(rt->stack); } diff --git a/libsanitizer/tsan/tsan_report.h b/libsanitizer/tsan/tsan_report.h index 6776f1e..23fbc68 100644 --- a/libsanitizer/tsan/tsan_report.h +++ b/libsanitizer/tsan/tsan_report.h @@ -36,20 +36,27 @@ struct ReportStack { int col; }; +struct ReportMopMutex { + u64 id; + bool write; +}; + struct ReportMop { int tid; uptr addr; int size; bool write; - int nmutex; - int *mutex; + Vector<ReportMopMutex> mset; ReportStack *stack; + + ReportMop(); }; enum ReportLocationType { ReportLocationGlobal, ReportLocationHeap, - ReportLocationStack + ReportLocationStack, + ReportLocationFD }; struct ReportLocation { @@ -59,6 +66,7 @@ struct ReportLocation { char *module; uptr offset; int tid; + int fd; char *name; char *file; int line; @@ -70,11 +78,13 @@ struct ReportThread { uptr pid; bool running; char *name; + int parent_tid; ReportStack *stack; }; struct ReportMutex { - int id; + u64 id; + bool destroyed; ReportStack *stack; }; diff --git a/libsanitizer/tsan/tsan_rtl.cc b/libsanitizer/tsan/tsan_rtl.cc index 2778ac3..3615a7a 100644 --- a/libsanitizer/tsan/tsan_rtl.cc +++ b/libsanitizer/tsan/tsan_rtl.cc @@ -164,6 +164,16 @@ void MapShadow(uptr addr, uptr size) { MmapFixedNoReserve(MemToShadow(addr), size * kShadowMultiplier); } +void MapThreadTrace(uptr addr, uptr size) { + DPrintf("#0: Mapping trace at %p-%p(0x%zx)\n", addr, addr + size, size); + CHECK_GE(addr, kTraceMemBegin); + CHECK_LE(addr + size, kTraceMemBegin + kTraceMemSize); + if (addr != (uptr)MmapFixedNoReserve(addr, size)) { + Printf("FATAL: ThreadSanitizer can not mmap thread trace\n"); + Die(); + } +} + void Initialize(ThreadState *thr) { // Thread safe because done before all threads exist. static bool is_initialized = false; @@ -289,6 +299,7 @@ void TraceSwitch(ThreadState *thr) { TraceHeader *hdr = &thr->trace.headers[trace]; hdr->epoch0 = thr->fast_state.epoch(); hdr->stack0.ObtainCurrent(thr, 0); + hdr->mset0 = thr->mset; thr->nomalloc--; } @@ -443,7 +454,7 @@ ALWAYS_INLINE void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, int kAccessSizeLog, bool kAccessIsWrite) { u64 *shadow_mem = (u64*)MemToShadow(addr); - DPrintf2("#%d: tsan::OnMemoryAccess: @%p %p size=%d" + DPrintf2("#%d: MemoryAccess: @%p %p size=%d" " is_write=%d shadow_mem=%p {%zx, %zx, %zx, %zx}\n", (int)thr->fast_state.tid(), (void*)pc, (void*)addr, (int)(1 << kAccessSizeLog), kAccessIsWrite, shadow_mem, diff --git a/libsanitizer/tsan/tsan_rtl.h b/libsanitizer/tsan/tsan_rtl.h index 56fcad1..b911791 100644 --- a/libsanitizer/tsan/tsan_rtl.h +++ b/libsanitizer/tsan/tsan_rtl.h @@ -34,6 +34,7 @@ #include "tsan_vector.h" #include "tsan_report.h" #include "tsan_platform.h" +#include "tsan_mutexset.h" #if SANITIZER_WORDSIZE != 64 # error "ThreadSanitizer is supported only on 64-bit platforms" @@ -48,6 +49,10 @@ struct MBlock { u32 alloc_tid; u32 alloc_stack_id; SyncVar *head; + + MBlock() + : mtx(MutexTypeMBlock, StatMtxMBlock) { + } }; #ifndef TSAN_GO @@ -58,10 +63,22 @@ const uptr kAllocatorSpace = 0x7d0000000000ULL; #endif const uptr kAllocatorSize = 0x10000000000ULL; // 1T. +struct TsanMapUnmapCallback { + void OnMap(uptr p, uptr size) const { } + void OnUnmap(uptr p, uptr size) const { + // We are about to unmap a chunk of user memory. + // Mark the corresponding shadow memory as not needed. + uptr shadow_beg = MemToShadow(p); + uptr shadow_end = MemToShadow(p + size); + CHECK(IsAligned(shadow_end|shadow_beg, GetPageSizeCached())); + FlushUnneededShadowMemory(shadow_beg, shadow_end - shadow_beg); + } +}; + typedef SizeClassAllocator64<kAllocatorSpace, kAllocatorSize, sizeof(MBlock), DefaultSizeClassMap> PrimaryAllocator; typedef SizeClassAllocatorLocalCache<PrimaryAllocator> AllocatorCache; -typedef LargeMmapAllocator SecondaryAllocator; +typedef LargeMmapAllocator<TsanMapUnmapCallback> SecondaryAllocator; typedef CombinedAllocator<PrimaryAllocator, AllocatorCache, SecondaryAllocator> Allocator; Allocator *allocator(); @@ -298,6 +315,7 @@ struct ThreadState { uptr *shadow_stack; uptr *shadow_stack_end; #endif + MutexSet mset; ThreadClock clock; #ifndef TSAN_GO AllocatorCache alloc_cache; @@ -369,6 +387,7 @@ struct ThreadContext { u64 epoch0; u64 epoch1; StackTrace creation_stack; + int creation_tid; ThreadDeadInfo *dead_info; ThreadContext *dead_next; // In dead thread list. char *name; // As annotated by user. @@ -445,7 +464,8 @@ class ScopedReport { ~ScopedReport(); void AddStack(const StackTrace *stack); - void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack); + void AddMemoryAccess(uptr addr, Shadow s, const StackTrace *stack, + const MutexSet *mset); void AddThread(const ThreadContext *tctx); void AddMutex(const SyncVar *s); void AddLocation(uptr addr, uptr size); @@ -457,11 +477,13 @@ class ScopedReport { Context *ctx_; ReportDesc *rep_; + void AddMutex(u64 id); + ScopedReport(const ScopedReport&); void operator = (const ScopedReport&); }; -void RestoreStack(int tid, const u64 epoch, StackTrace *stk); +void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset); void StatAggregate(u64 *dst, u64 *src); void StatOutput(u64 *stat); @@ -471,6 +493,7 @@ void ALWAYS_INLINE INLINE StatInc(ThreadState *thr, StatType typ, u64 n = 1) { } void MapShadow(uptr addr, uptr size); +void MapThreadTrace(uptr addr, uptr size); void InitializeShadowMemory(); void InitializeInterceptors(); void InitializeDynamicAnnotations(); @@ -502,6 +525,10 @@ void PrintCurrentStack(ThreadState *thr, uptr pc); void Initialize(ThreadState *thr); int Finalize(ThreadState *thr); +SyncVar* GetJavaSync(ThreadState *thr, uptr pc, uptr addr, + bool write_lock, bool create); +SyncVar* GetAndRemoveJavaSync(ThreadState *thr, uptr pc, uptr addr); + void MemoryAccess(ThreadState *thr, uptr pc, uptr addr, int kAccessSizeLog, bool kAccessIsWrite); void MemoryAccessImpl(ThreadState *thr, uptr addr, @@ -575,7 +602,10 @@ uptr TraceParts(); extern "C" void __tsan_trace_switch(); void ALWAYS_INLINE INLINE TraceAddEvent(ThreadState *thr, FastState fs, - EventType typ, uptr addr) { + EventType typ, u64 addr) { + DCHECK_GE((int)typ, 0); + DCHECK_LE((int)typ, 7); + DCHECK_EQ(GetLsb(addr, 61), addr); StatInc(thr, StatEvents); u64 pos = fs.GetTracePos(); if (UNLIKELY((pos % kTracePartSize) == 0)) { diff --git a/libsanitizer/tsan/tsan_rtl_mutex.cc b/libsanitizer/tsan/tsan_rtl_mutex.cc index e5b43be..8dd0e6d 100644 --- a/libsanitizer/tsan/tsan_rtl_mutex.cc +++ b/libsanitizer/tsan/tsan_rtl_mutex.cc @@ -26,7 +26,7 @@ void MutexCreate(ThreadState *thr, uptr pc, uptr addr, StatInc(thr, StatMutexCreate); if (!linker_init && IsAppMem(addr)) MemoryWrite1Byte(thr, pc, addr); - SyncVar *s = ctx->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = ctx->synctab.GetOrCreateAndLock(thr, pc, addr, true); s->is_rw = rw; s->is_recursive = recursive; s->is_linker_init = linker_init; @@ -59,11 +59,12 @@ void MutexDestroy(ThreadState *thr, uptr pc, uptr addr) { trace.ObtainCurrent(thr, pc); rep.AddStack(&trace); FastState last(s->last_lock); - RestoreStack(last.tid(), last.epoch(), &trace); + RestoreStack(last.tid(), last.epoch(), &trace, 0); rep.AddStack(&trace); rep.AddLocation(s->addr, 1); OutputReport(ctx, rep); } + thr->mset.Remove(s->GetId()); DestroyAndFree(s); } @@ -72,9 +73,9 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexLock %zx\n", thr->tid, addr); if (IsAppMem(addr)) MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeLock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + TraceAddEvent(thr, thr->fast_state, EventTypeLock, s->GetId()); if (s->owner_tid == SyncVar::kInvalidTid) { CHECK_EQ(s->recursion, 0); s->owner_tid = thr->tid; @@ -96,6 +97,7 @@ void MutexLock(ThreadState *thr, uptr pc, uptr addr) { StatInc(thr, StatMutexRecLock); } s->recursion++; + thr->mset.Add(s->GetId(), true, thr->fast_state.epoch()); s->mtx.Unlock(); } @@ -104,9 +106,9 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexUnlock %zx\n", thr->tid, addr); if (IsAppMem(addr)) MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId()); if (s->recursion == 0) { if (!s->is_broken) { s->is_broken = true; @@ -132,6 +134,7 @@ void MutexUnlock(ThreadState *thr, uptr pc, uptr addr) { StatInc(thr, StatMutexRecUnlock); } } + thr->mset.Del(s->GetId(), true); s->mtx.Unlock(); } @@ -141,9 +144,9 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) { StatInc(thr, StatMutexReadLock); if (IsAppMem(addr)) MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeRLock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + TraceAddEvent(thr, thr->fast_state, EventTypeRLock, s->GetId()); if (s->owner_tid != SyncVar::kInvalidTid) { Printf("ThreadSanitizer WARNING: read lock of a write locked mutex\n"); PrintCurrentStack(thr, pc); @@ -152,6 +155,7 @@ void MutexReadLock(ThreadState *thr, uptr pc, uptr addr) { thr->clock.acquire(&s->clock); s->last_lock = thr->fast_state.raw(); StatInc(thr, StatSyncAcquire); + thr->mset.Add(s->GetId(), false, thr->fast_state.epoch()); s->mtx.ReadUnlock(); } @@ -161,9 +165,9 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { StatInc(thr, StatMutexReadUnlock); if (IsAppMem(addr)) MemoryRead1Byte(thr, pc, addr); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId()); if (s->owner_tid != SyncVar::kInvalidTid) { Printf("ThreadSanitizer WARNING: read unlock of a write " "locked mutex\n"); @@ -174,6 +178,7 @@ void MutexReadUnlock(ThreadState *thr, uptr pc, uptr addr) { thr->clock.release(&s->read_clock); StatInc(thr, StatSyncRelease); s->mtx.Unlock(); + thr->mset.Del(s->GetId(), false); } void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { @@ -181,18 +186,22 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { DPrintf("#%d: MutexReadOrWriteUnlock %zx\n", thr->tid, addr); if (IsAppMem(addr)) MemoryRead1Byte(thr, pc, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); + bool write = true; if (s->owner_tid == SyncVar::kInvalidTid) { // Seems to be read unlock. + write = false; StatInc(thr, StatMutexReadUnlock); thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, addr); + TraceAddEvent(thr, thr->fast_state, EventTypeRUnlock, s->GetId()); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.release(&s->read_clock); StatInc(thr, StatSyncRelease); } else if (s->owner_tid == thr->tid) { // Seems to be write unlock. + thr->fast_state.IncrementEpoch(); + TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, s->GetId()); CHECK_GT(s->recursion, 0); s->recursion--; if (s->recursion == 0) { @@ -202,8 +211,6 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { // The sequence of events is quite tricky and doubled in several places. // First, it's a bug to increment the epoch w/o writing to the trace. // Then, the acquire/release logic can be factored out as well. - thr->fast_state.IncrementEpoch(); - TraceAddEvent(thr, thr->fast_state, EventTypeUnlock, addr); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->fast_synch_epoch = thr->fast_state.epoch(); thr->clock.ReleaseStore(&s->clock); @@ -216,13 +223,14 @@ void MutexReadOrWriteUnlock(ThreadState *thr, uptr pc, uptr addr) { Printf("ThreadSanitizer WARNING: mutex unlock by another thread\n"); PrintCurrentStack(thr, pc); } + thr->mset.Del(s->GetId(), write); s->mtx.Unlock(); } void Acquire(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: Acquire %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, false); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, false); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.acquire(&s->clock); StatInc(thr, StatSyncAcquire); @@ -246,7 +254,7 @@ void AcquireGlobal(ThreadState *thr, uptr pc) { void Release(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: Release %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.release(&s->clock); StatInc(thr, StatSyncRelease); @@ -256,7 +264,7 @@ void Release(ThreadState *thr, uptr pc, uptr addr) { void ReleaseStore(ThreadState *thr, uptr pc, uptr addr) { CHECK_GT(thr->in_rtl, 0); DPrintf("#%d: ReleaseStore %zx\n", thr->tid, addr); - SyncVar *s = CTX()->synctab.GetAndLock(thr, pc, addr, true); + SyncVar *s = CTX()->synctab.GetOrCreateAndLock(thr, pc, addr, true); thr->clock.set(thr->tid, thr->fast_state.epoch()); thr->clock.ReleaseStore(&s->clock); StatInc(thr, StatSyncRelease); diff --git a/libsanitizer/tsan/tsan_rtl_report.cc b/libsanitizer/tsan/tsan_rtl_report.cc index 6aae6cf..b65b24f 100644 --- a/libsanitizer/tsan/tsan_rtl_report.cc +++ b/libsanitizer/tsan/tsan_rtl_report.cc @@ -12,6 +12,7 @@ #include "sanitizer_common/sanitizer_libc.h" #include "sanitizer_common/sanitizer_placement_new.h" #include "sanitizer_common/sanitizer_stackdepot.h" +#include "sanitizer_common/sanitizer_common.h" #include "tsan_platform.h" #include "tsan_rtl.h" #include "tsan_suppressions.h" @@ -20,9 +21,12 @@ #include "tsan_sync.h" #include "tsan_mman.h" #include "tsan_flags.h" +#include "tsan_fd.h" namespace __tsan { +using namespace __sanitizer; // NOLINT + void TsanCheckFailed(const char *file, int line, const char *cond, u64 v1, u64 v2) { ScopedInRtl in_rtl; @@ -132,7 +136,7 @@ void ScopedReport::AddStack(const StackTrace *stack) { } void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, - const StackTrace *stack) { + const StackTrace *stack, const MutexSet *mset) { void *mem = internal_alloc(MBlockReportMop, sizeof(ReportMop)); ReportMop *mop = new(mem) ReportMop; rep_->mops.PushBack(mop); @@ -140,8 +144,27 @@ void ScopedReport::AddMemoryAccess(uptr addr, Shadow s, mop->addr = addr + s.addr0(); mop->size = s.size(); mop->write = s.is_write(); - mop->nmutex = 0; mop->stack = SymbolizeStack(*stack); + for (uptr i = 0; i < mset->Size(); i++) { + MutexSet::Desc d = mset->Get(i); + u64 uid = 0; + uptr addr = SyncVar::SplitId(d.id, &uid); + SyncVar *s = ctx_->synctab.GetIfExistsAndLock(addr, false); + // Check that the mutex is still alive. + // Another mutex can be created at the same address, + // so check uid as well. + if (s && s->CheckId(uid)) { + ReportMopMutex mtx = {s->uid, d.write}; + mop->mset.PushBack(mtx); + AddMutex(s); + } else { + ReportMopMutex mtx = {d.id, d.write}; + mop->mset.PushBack(mtx); + AddMutex(d.id); + } + if (s) + s->mtx.ReadUnlock(); + } } void ScopedReport::AddThread(const ThreadContext *tctx) { @@ -156,6 +179,7 @@ void ScopedReport::AddThread(const ThreadContext *tctx) { rt->pid = tctx->os_id; rt->running = (tctx->status == ThreadStatusRunning); rt->name = tctx->name ? internal_strdup(tctx->name) : 0; + rt->parent_tid = tctx->creation_tid; rt->stack = SymbolizeStack(tctx->creation_stack); } @@ -173,17 +197,58 @@ static ThreadContext *FindThread(int unique_id) { #endif void ScopedReport::AddMutex(const SyncVar *s) { + for (uptr i = 0; i < rep_->mutexes.Size(); i++) { + if (rep_->mutexes[i]->id == s->uid) + return; + } void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); ReportMutex *rm = new(mem) ReportMutex(); rep_->mutexes.PushBack(rm); - rm->id = 42; + rm->id = s->uid; + rm->destroyed = false; rm->stack = SymbolizeStack(s->creation_stack); } +void ScopedReport::AddMutex(u64 id) { + for (uptr i = 0; i < rep_->mutexes.Size(); i++) { + if (rep_->mutexes[i]->id == id) + return; + } + void *mem = internal_alloc(MBlockReportMutex, sizeof(ReportMutex)); + ReportMutex *rm = new(mem) ReportMutex(); + rep_->mutexes.PushBack(rm); + rm->id = id; + rm->destroyed = true; + rm->stack = 0; +} + void ScopedReport::AddLocation(uptr addr, uptr size) { if (addr == 0) return; #ifndef TSAN_GO + int fd = -1; + int creat_tid = -1; + u32 creat_stack = 0; + if (FdLocation(addr, &fd, &creat_tid, &creat_stack) + || FdLocation(AlternativeAddress(addr), &fd, &creat_tid, &creat_stack)) { + void *mem = internal_alloc(MBlockReportLoc, sizeof(ReportLocation)); + ReportLocation *loc = new(mem) ReportLocation(); + rep_->locs.PushBack(loc); + loc->type = ReportLocationFD; + loc->fd = fd; + loc->tid = creat_tid; + uptr ssz = 0; + const uptr *stack = StackDepotGet(creat_stack, &ssz); + if (stack) { + StackTrace trace; + trace.Init(stack, ssz); + loc->stack = SymbolizeStack(trace); + } + ThreadContext *tctx = FindThread(creat_tid); + if (tctx) + AddThread(tctx); + return; + } if (allocator()->PointerIsMine((void*)addr)) { MBlock *b = user_mblock(0, (void*)addr); ThreadContext *tctx = FindThread(b->alloc_tid); @@ -246,7 +311,10 @@ const ReportDesc *ScopedReport::GetReport() const { return rep_; } -void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { +void RestoreStack(int tid, const u64 epoch, StackTrace *stk, MutexSet *mset) { + // This function restores stack trace and mutex set for the thread/epoch. + // It does so by getting stack trace and mutex set at the beginning of + // trace part, and then replaying the trace till the given epoch. ThreadContext *tctx = CTX()->threads[tid]; if (tctx == 0) return; @@ -267,6 +335,7 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { TraceHeader* hdr = &trace->headers[partidx]; if (epoch < hdr->epoch0) return; + const u64 epoch0 = RoundDown(epoch, TraceSize()); const u64 eend = epoch % TraceSize(); const u64 ebegin = RoundDown(eend, kTracePartSize); DPrintf("#%d: RestoreStack epoch=%zu ebegin=%zu eend=%zu partidx=%d\n", @@ -276,12 +345,14 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { stack[i] = hdr->stack0.Get(i); DPrintf2(" #%02lu: pc=%zx\n", i, stack[i]); } + if (mset) + *mset = hdr->mset0; uptr pos = hdr->stack0.Size(); Event *events = (Event*)GetThreadTrace(tid); for (uptr i = ebegin; i <= eend; i++) { Event ev = events[i]; EventType typ = (EventType)(ev >> 61); - uptr pc = (uptr)(ev & 0xffffffffffffull); + uptr pc = (uptr)(ev & ((1ull << 61) - 1)); DPrintf2(" %zu typ=%d pc=%zx\n", i, typ, pc); if (typ == EventTypeMop) { stack[pos] = pc; @@ -291,6 +362,17 @@ void RestoreStack(int tid, const u64 epoch, StackTrace *stk) { if (pos > 0) pos--; } + if (mset) { + if (typ == EventTypeLock) { + mset->Add(pc, true, epoch0 + i); + } else if (typ == EventTypeUnlock) { + mset->Del(pc, true); + } else if (typ == EventTypeRLock) { + mset->Add(pc, false, epoch0 + i); + } else if (typ == EventTypeRUnlock) { + mset->Del(pc, false); + } + } for (uptr j = 0; j <= pos; j++) DPrintf2(" #%zu: %zx\n", j, stack[j]); } @@ -400,8 +482,11 @@ static bool IsJavaNonsense(const ReportDesc *rep) { if (frame != 0 && frame->func != 0 && (internal_strcmp(frame->func, "memset") == 0 || internal_strcmp(frame->func, "memcpy") == 0 + || internal_strcmp(frame->func, "memmove") == 0 || internal_strcmp(frame->func, "strcmp") == 0 || internal_strcmp(frame->func, "strncpy") == 0 + || internal_strcmp(frame->func, "strlen") == 0 + || internal_strcmp(frame->func, "free") == 0 || internal_strcmp(frame->func, "pthread_mutex_lock") == 0)) { frame = frame->next; if (frame == 0 @@ -423,6 +508,10 @@ void ReportRace(ThreadState *thr) { return; ScopedInRtl in_rtl; + if (thr->in_signal_handler) + Printf("ThreadSanitizer: printing report from signal handler." + " Can crash or hang.\n"); + bool freed = false; { Shadow s(thr->racy_state[1]); @@ -454,15 +543,18 @@ void ReportRace(ThreadState *thr) { traces[0].ObtainCurrent(thr, toppc); if (IsFiredSuppression(ctx, rep, traces[0])) return; + InternalScopedBuffer<MutexSet> mset2(1); + new(mset2.data()) MutexSet(); Shadow s2(thr->racy_state[1]); - RestoreStack(s2.tid(), s2.epoch(), &traces[1]); + RestoreStack(s2.tid(), s2.epoch(), &traces[1], mset2.data()); if (HandleRacyStacks(thr, traces, addr_min, addr_max)) return; for (uptr i = 0; i < kMop; i++) { Shadow s(thr->racy_state[i]); - rep.AddMemoryAccess(addr, s, &traces[i]); + rep.AddMemoryAccess(addr, s, &traces[i], + i == 0 ? &thr->mset : mset2.data()); } if (flags()->suppress_java && IsJavaNonsense(rep.GetReport())) diff --git a/libsanitizer/tsan/tsan_rtl_thread.cc b/libsanitizer/tsan/tsan_rtl_thread.cc index 462d12c..d5b3444 100644 --- a/libsanitizer/tsan/tsan_rtl_thread.cc +++ b/libsanitizer/tsan/tsan_rtl_thread.cc @@ -154,6 +154,7 @@ int ThreadCreate(ThreadState *thr, uptr pc, uptr uid, bool detached) { thr->clock.release(&tctx->sync); StatInc(thr, StatSyncRelease); tctx->creation_stack.ObtainCurrent(thr, pc); + tctx->creation_tid = thr->tid; } return tid; } @@ -303,6 +304,7 @@ void ThreadJoin(ThreadState *thr, uptr pc, int tid) { Printf("ThreadSanitizer: join of non-existent thread\n"); return; } + // FIXME(dvyukov): print message and continue (it's user error). CHECK_EQ(tctx->detached, false); CHECK_EQ(tctx->status, ThreadStatusFinished); thr->clock.acquire(&tctx->sync); diff --git a/libsanitizer/tsan/tsan_stat.cc b/libsanitizer/tsan/tsan_stat.cc index 7b91330..394c911 100644 --- a/libsanitizer/tsan/tsan_stat.cc +++ b/libsanitizer/tsan/tsan_stat.cc @@ -179,6 +179,28 @@ void StatOutput(u64 *stat) { name[StatInt_sem_timedwait] = " sem_timedwait "; name[StatInt_sem_post] = " sem_post "; name[StatInt_sem_getvalue] = " sem_getvalue "; + name[StatInt_open] = " open "; + name[StatInt_open64] = " open64 "; + name[StatInt_creat] = " creat "; + name[StatInt_creat64] = " creat64 "; + name[StatInt_dup] = " dup "; + name[StatInt_dup2] = " dup2 "; + name[StatInt_dup3] = " dup3 "; + name[StatInt_eventfd] = " eventfd "; + name[StatInt_signalfd] = " signalfd "; + name[StatInt_inotify_init] = " inotify_init "; + name[StatInt_inotify_init1] = " inotify_init1 "; + name[StatInt_socket] = " socket "; + name[StatInt_socketpair] = " socketpair "; + name[StatInt_connect] = " connect "; + name[StatInt_accept] = " accept "; + name[StatInt_accept4] = " accept4 "; + name[StatInt_epoll_create] = " epoll_create "; + name[StatInt_epoll_create1] = " epoll_create1 "; + name[StatInt_close] = " close "; + name[StatInt___close] = " __close "; + name[StatInt_pipe] = " pipe "; + name[StatInt_pipe2] = " pipe2 "; name[StatInt_read] = " read "; name[StatInt_pread] = " pread "; name[StatInt_pread64] = " pread64 "; @@ -195,6 +217,8 @@ void StatOutput(u64 *stat) { name[StatInt_recvmsg] = " recvmsg "; name[StatInt_unlink] = " unlink "; name[StatInt_fopen] = " fopen "; + name[StatInt_freopen] = " freopen "; + name[StatInt_fclose] = " fclose "; name[StatInt_fread] = " fread "; name[StatInt_fwrite] = " fwrite "; name[StatInt_puts] = " puts "; @@ -208,6 +232,7 @@ void StatOutput(u64 *stat) { name[StatInt_usleep] = " usleep "; name[StatInt_nanosleep] = " nanosleep "; name[StatInt_gettimeofday] = " gettimeofday "; + name[StatInt_fork] = " fork "; name[StatAnnotation] = "Dynamic annotations "; name[StatAnnotateHappensBefore] = " HappensBefore "; @@ -251,6 +276,8 @@ void StatOutput(u64 *stat) { name[StatMtxSlab] = " Slab "; name[StatMtxAtExit] = " Atexit "; name[StatMtxAnnotations] = " Annotations "; + name[StatMtxMBlock] = " MBlock "; + name[StatMtxJavaMBlock] = " JavaMBlock "; Printf("Statistics:\n"); for (int i = 0; i < StatCnt; i++) diff --git a/libsanitizer/tsan/tsan_stat.h b/libsanitizer/tsan/tsan_stat.h index 0dc1cd9..cdd5736 100644 --- a/libsanitizer/tsan/tsan_stat.h +++ b/libsanitizer/tsan/tsan_stat.h @@ -174,6 +174,28 @@ enum StatType { StatInt_sem_timedwait, StatInt_sem_post, StatInt_sem_getvalue, + StatInt_open, + StatInt_open64, + StatInt_creat, + StatInt_creat64, + StatInt_dup, + StatInt_dup2, + StatInt_dup3, + StatInt_eventfd, + StatInt_signalfd, + StatInt_inotify_init, + StatInt_inotify_init1, + StatInt_socket, + StatInt_socketpair, + StatInt_connect, + StatInt_accept, + StatInt_accept4, + StatInt_epoll_create, + StatInt_epoll_create1, + StatInt_close, + StatInt___close, + StatInt_pipe, + StatInt_pipe2, StatInt_read, StatInt_pread, StatInt_pread64, @@ -190,6 +212,8 @@ enum StatType { StatInt_recvmsg, StatInt_unlink, StatInt_fopen, + StatInt_freopen, + StatInt_fclose, StatInt_fread, StatInt_fwrite, StatInt_puts, @@ -207,6 +231,7 @@ enum StatType { StatInt_usleep, StatInt_nanosleep, StatInt_gettimeofday, + StatInt_fork, // Dynamic annotations. StatAnnotation, @@ -253,6 +278,8 @@ enum StatType { StatMtxSlab, StatMtxAnnotations, StatMtxAtExit, + StatMtxMBlock, + StatMtxJavaMBlock, // This must be the last. StatCnt diff --git a/libsanitizer/tsan/tsan_symbolize_addr2line_linux.cc b/libsanitizer/tsan/tsan_symbolize_addr2line_linux.cc index 23540c0..9bdd1ff 100644 --- a/libsanitizer/tsan/tsan_symbolize_addr2line_linux.cc +++ b/libsanitizer/tsan/tsan_symbolize_addr2line_linux.cc @@ -102,11 +102,11 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { m->base = (uptr)info->dlpi_addr; m->inp_fd = -1; m->out_fd = -1; - DPrintf("Module %s %zx\n", m->name, m->base); + DPrintf2("Module %s %zx\n", m->name, m->base); for (int i = 0; i < info->dlpi_phnum; i++) { const Elf64_Phdr *s = &info->dlpi_phdr[i]; - DPrintf(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx" - " p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n", + DPrintf2(" Section p_type=%zx p_offset=%zx p_vaddr=%zx p_paddr=%zx" + " p_filesz=%zx p_memsz=%zx p_flags=%zx p_align=%zx\n", (uptr)s->p_type, (uptr)s->p_offset, (uptr)s->p_vaddr, (uptr)s->p_paddr, (uptr)s->p_filesz, (uptr)s->p_memsz, (uptr)s->p_flags, (uptr)s->p_align); @@ -119,7 +119,7 @@ static int dl_iterate_phdr_cb(dl_phdr_info *info, size_t size, void *arg) { sec->end = sec->base + s->p_memsz; sec->next = ctx->sections; ctx->sections = sec; - DPrintf(" Section %zx-%zx\n", sec->base, sec->end); + DPrintf2(" Section %zx-%zx\n", sec->base, sec->end); } return 0; } diff --git a/libsanitizer/tsan/tsan_sync.cc b/libsanitizer/tsan/tsan_sync.cc index 3bd1b35..d392408 100644 --- a/libsanitizer/tsan/tsan_sync.cc +++ b/libsanitizer/tsan/tsan_sync.cc @@ -15,9 +15,10 @@ namespace __tsan { -SyncVar::SyncVar(uptr addr) +SyncVar::SyncVar(uptr addr, u64 uid) : mtx(MutexTypeSyncVar, StatMtxSyncVar) , addr(addr) + , uid(uid) , owner_tid(kInvalidTid) , last_lock() , recursion() @@ -45,9 +46,38 @@ SyncTab::~SyncTab() { } } +SyncVar* SyncTab::GetOrCreateAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock) { + return GetAndLock(thr, pc, addr, write_lock, true); +} + +SyncVar* SyncTab::GetIfExistsAndLock(uptr addr, bool write_lock) { + return GetAndLock(0, 0, addr, write_lock, false); +} + +SyncVar* SyncTab::Create(ThreadState *thr, uptr pc, uptr addr) { + StatInc(thr, StatSyncCreated); + void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); + const u64 uid = atomic_fetch_add(&uid_gen_, 1, memory_order_relaxed); + SyncVar *res = new(mem) SyncVar(addr, uid); +#ifndef TSAN_GO + res->creation_stack.ObtainCurrent(thr, pc); +#endif + return res; +} + SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, - uptr addr, bool write_lock) { + uptr addr, bool write_lock, bool create) { #ifndef TSAN_GO + { // NOLINT + SyncVar *res = GetJavaSync(thr, pc, addr, write_lock, create); + if (res) + return res; + } + + // Here we ask only PrimaryAllocator, because + // SecondaryAllocator::PointerIsMine() is slow and we have fallback on + // the hashmap anyway. if (PrimaryAllocator::PointerIsMine((void*)addr)) { MBlock *b = user_mblock(thr, (void*)addr); Lock l(&b->mtx); @@ -57,10 +87,9 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, break; } if (res == 0) { - StatInc(thr, StatSyncCreated); - void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); - res = new(mem) SyncVar(addr); - res->creation_stack.ObtainCurrent(thr, pc); + if (!create) + return 0; + res = Create(thr, pc, addr); res->next = b->head; b->head = res; } @@ -85,6 +114,8 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, } } } + if (!create) + return 0; { Lock l(&p->mtx); SyncVar *res = p->val; @@ -93,12 +124,7 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, break; } if (res == 0) { - StatInc(thr, StatSyncCreated); - void *mem = internal_alloc(MBlockSync, sizeof(SyncVar)); - res = new(mem) SyncVar(addr); -#ifndef TSAN_GO - res->creation_stack.ObtainCurrent(thr, pc); -#endif + res = Create(thr, pc, addr); res->next = p->val; p->val = res; } @@ -112,6 +138,11 @@ SyncVar* SyncTab::GetAndLock(ThreadState *thr, uptr pc, SyncVar* SyncTab::GetAndRemove(ThreadState *thr, uptr pc, uptr addr) { #ifndef TSAN_GO + { // NOLINT + SyncVar *res = GetAndRemoveJavaSync(thr, pc, addr); + if (res) + return res; + } if (PrimaryAllocator::PointerIsMine((void*)addr)) { MBlock *b = user_mblock(thr, (void*)addr); SyncVar *res = 0; diff --git a/libsanitizer/tsan/tsan_sync.h b/libsanitizer/tsan/tsan_sync.h index 2912d2c..4dbb055 100644 --- a/libsanitizer/tsan/tsan_sync.h +++ b/libsanitizer/tsan/tsan_sync.h @@ -48,12 +48,13 @@ class StackTrace { }; struct SyncVar { - explicit SyncVar(uptr addr); + explicit SyncVar(uptr addr, u64 uid); static const int kInvalidTid = -1; Mutex mtx; - const uptr addr; + uptr addr; + const u64 uid; // Globally unique id. SyncClock clock; SyncClock read_clock; // Used for rw mutexes only. StackTrace creation_stack; @@ -67,6 +68,18 @@ struct SyncVar { SyncVar *next; // In SyncTab hashtable. uptr GetMemoryConsumption(); + u64 GetId() const { + // 47 lsb is addr, then 14 bits is low part of uid, then 3 zero bits. + return GetLsb((u64)addr | (uid << 47), 61); + } + bool CheckId(u64 uid) const { + CHECK_EQ(uid, GetLsb(uid, 14)); + return GetLsb(this->uid, 14) == uid; + } + static uptr SplitId(u64 id, u64 *uid) { + *uid = id >> 47; + return (uptr)GetLsb(id, 47); + } }; class SyncTab { @@ -74,13 +87,15 @@ class SyncTab { SyncTab(); ~SyncTab(); - // If the SyncVar does not exist yet, it is created. - SyncVar* GetAndLock(ThreadState *thr, uptr pc, - uptr addr, bool write_lock); + SyncVar* GetOrCreateAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock); + SyncVar* GetIfExistsAndLock(uptr addr, bool write_lock); // If the SyncVar does not exist, returns 0. SyncVar* GetAndRemove(ThreadState *thr, uptr pc, uptr addr); + SyncVar* Create(ThreadState *thr, uptr pc, uptr addr); + uptr GetMemoryConsumption(uptr *nsync); private: @@ -94,9 +109,13 @@ class SyncTab { // FIXME: Implement something more sane. static const int kPartCount = 1009; Part tab_[kPartCount]; + atomic_uint64_t uid_gen_; int PartIdx(uptr addr); + SyncVar* GetAndLock(ThreadState *thr, uptr pc, + uptr addr, bool write_lock, bool create); + SyncTab(const SyncTab&); // Not implemented. void operator = (const SyncTab&); // Not implemented. }; diff --git a/libsanitizer/tsan/tsan_trace.h b/libsanitizer/tsan/tsan_trace.h index 154cc15..6986483 100644 --- a/libsanitizer/tsan/tsan_trace.h +++ b/libsanitizer/tsan/tsan_trace.h @@ -14,6 +14,7 @@ #include "tsan_defs.h" #include "tsan_mutex.h" #include "tsan_sync.h" +#include "tsan_mutexset.h" namespace __tsan { @@ -41,6 +42,7 @@ typedef u64 Event; struct TraceHeader { StackTrace stack0; // Start stack for the trace. u64 epoch0; // Start epoch for the trace. + MutexSet mset0; #ifndef TSAN_GO uptr stack0buf[kTraceStackSize]; #endif |