diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/base/CodeAddressService.h | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h new file mode 100644 index 0000000000..821bf2c73c --- /dev/null +++ b/xpcom/base/CodeAddressService.h @@ -0,0 +1,250 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CodeAddressService_h__ +#define CodeAddressService_h__ + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include "mozilla/AllocPolicy.h" +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/StackWalk.h" + +namespace mozilla { + +namespace detail { + +template <class AllocPolicy> +class CodeAddressServiceAllocPolicy : public AllocPolicy { + public: + char* strdup_(const char* aStr) { + char* s = AllocPolicy::template pod_malloc<char>(strlen(aStr) + 1); + if (!s) { + MOZ_CRASH("CodeAddressService OOM"); + } + strcpy(s, aStr); + return s; + } +}; + +// Default implementation of DescribeCodeAddressLock. +struct DefaultDescribeCodeAddressLock { + static void Unlock() {} + static void Lock() {} + // Because CodeAddressService asserts that IsLocked() is true, returning true + // here is a sensible default when there is no relevant lock. + static bool IsLocked() { return true; } +}; + +} // namespace detail + +// This class is used to print details about code locations. +// +// |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h. +// +// |DescribeCodeAddressLock| is needed when the callers may be holding a lock +// used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement +// static methods IsLocked(), Unlock() and Lock(). +template <class AllocPolicy_ = MallocAllocPolicy, + class DescribeCodeAddressLock = + detail::DefaultDescribeCodeAddressLock> +class CodeAddressService + : private detail::CodeAddressServiceAllocPolicy<AllocPolicy_> { + protected: + // GetLocation() is the key function in this class. It's basically a wrapper + // around MozDescribeCodeAddress. + // + // However, MozDescribeCodeAddress is very slow on some platforms, and we + // have lots of repeated (i.e. same PC) calls to it. So we do some caching + // of results. Each cached result includes two strings (|mFunction| and + // |mLibrary|), so we also optimize them for space in the following ways. + // + // - The number of distinct library names is small, e.g. a few dozen. There + // is lots of repetition, especially of libxul. So we intern them in their + // own table, which saves space over duplicating them for each cache entry. + // + // - The number of distinct function names is much higher, so we duplicate + // them in each cache entry. That's more space-efficient than interning + // because entries containing single-occurrence function names are quickly + // overwritten, and their copies released. In addition, empty function + // names are common, so we use nullptr to represent them compactly. + + using AllocPolicy = detail::CodeAddressServiceAllocPolicy<AllocPolicy_>; + using StringHashSet = HashSet<const char*, CStringHasher, AllocPolicy>; + + StringHashSet mLibraryStrings; + + struct Entry : private AllocPolicy { + const void* mPc; + char* mFunction; // owned by the Entry; may be null + const char* mLibrary; // owned by mLibraryStrings; never null + // in a non-empty entry is in use + ptrdiff_t mLOffset; + char* mFileName; // owned by the Entry; may be null + uint32_t mLineNo : 31; + uint32_t mInUse : 1; // is the entry used? + + Entry() + : mPc(0), + mFunction(nullptr), + mLibrary(nullptr), + mLOffset(0), + mFileName(nullptr), + mLineNo(0), + mInUse(0) {} + + ~Entry() { + // We don't free mLibrary because it's externally owned. + AllocPolicy::free_(mFunction); + AllocPolicy::free_(mFileName); + } + + void Replace(const void* aPc, const char* aFunction, const char* aLibrary, + ptrdiff_t aLOffset, const char* aFileName, + unsigned long aLineNo) { + mPc = aPc; + + // Convert "" to nullptr. Otherwise, make a copy of the name. + AllocPolicy::free_(mFunction); + mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction); + AllocPolicy::free_(mFileName); + mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(aFileName); + + mLibrary = aLibrary; + mLOffset = aLOffset; + mLineNo = aLineNo; + + mInUse = 1; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + // Don't measure mLibrary because it's externally owned. + size_t n = 0; + n += aMallocSizeOf(mFunction); + n += aMallocSizeOf(mFileName); + return n; + } + }; + + const char* InternLibraryString(const char* aString) { + auto p = mLibraryStrings.lookupForAdd(aString); + if (p) { + return *p; + } + + const char* newString = AllocPolicy::strdup_(aString); + if (!mLibraryStrings.add(p, newString)) { + MOZ_CRASH("CodeAddressService OOM"); + } + return newString; + } + + Entry& GetEntry(const void* aPc) { + MOZ_ASSERT(DescribeCodeAddressLock::IsLocked()); + + uint32_t index = HashGeneric(aPc) & kMask; + MOZ_ASSERT(index < kNumEntries); + Entry& entry = mEntries[index]; + + if (!entry.mInUse || entry.mPc != aPc) { + mNumCacheMisses++; + + // MozDescribeCodeAddress can (on Linux) acquire a lock inside + // the shared library loader. Another thread might call malloc + // while holding that lock (when loading a shared library). So + // we have to exit the lock around this call. For details, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 + MozCodeAddressDetails details; + { + DescribeCodeAddressLock::Unlock(); + (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details); + DescribeCodeAddressLock::Lock(); + } + + const char* library = InternLibraryString(details.library); + entry.Replace(aPc, details.function, library, details.loffset, + details.filename, details.lineno); + + } else { + mNumCacheHits++; + } + + MOZ_ASSERT(entry.mPc == aPc); + + return entry; + } + + // A direct-mapped cache. When doing dmd::Analyze() just after starting + // desktop Firefox (which is similar to analyzing after a longer-running + // session, thanks to the limit on how many records we print), a cache with + // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit + // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB + // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms). + static const size_t kNumEntries = 1 << 12; + static const size_t kMask = kNumEntries - 1; + Entry mEntries[kNumEntries]; + + size_t mNumCacheHits; + size_t mNumCacheMisses; + + public: + CodeAddressService() + : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {} + + ~CodeAddressService() { + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + AllocPolicy::free_(const_cast<char*>(iter.get())); + } + } + + // Returns the minimum number of characters necessary to format the frame + // information, without the terminating null. The buffer will be truncated + // if the returned value is greater than aBufLen-1. + int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, + size_t aBufLen) { + Entry& entry = GetEntry(aPc); + return MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc, + entry.mFunction, entry.mLibrary, entry.mLOffset, + entry.mFileName, entry.mLineNo); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + for (uint32_t i = 0; i < kNumEntries; i++) { + n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf); + } + + n += mLibraryStrings.shallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + n += aMallocSizeOf(iter.get()); + } + + return n; + } + + size_t CacheCapacity() const { return kNumEntries; } + + size_t CacheCount() const { + size_t n = 0; + for (size_t i = 0; i < kNumEntries; i++) { + if (mEntries[i].mInUse) { + n++; + } + } + return n; + } + + size_t NumCacheHits() const { return mNumCacheHits; } + size_t NumCacheMisses() const { return mNumCacheMisses; } +}; + +} // namespace mozilla + +#endif // CodeAddressService_h__ |