1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef CodeAddressService_h__ 8 #define CodeAddressService_h__ 9 10 #include <cstddef> 11 #include <cstdint> 12 #include <cstring> 13 #include "mozilla/AllocPolicy.h" 14 #include "mozilla/Assertions.h" 15 #include "mozilla/HashFunctions.h" 16 #include "mozilla/HashTable.h" 17 #include "mozilla/MemoryReporting.h" 18 #include "mozilla/StackWalk.h" 19 20 namespace mozilla { 21 22 namespace detail { 23 24 template <class AllocPolicy> 25 class CodeAddressServiceAllocPolicy : public AllocPolicy { 26 public: strdup_(const char * aStr)27 char* strdup_(const char* aStr) { 28 char* s = AllocPolicy::template pod_malloc<char>(strlen(aStr) + 1); 29 if (!s) { 30 MOZ_CRASH("CodeAddressService OOM"); 31 } 32 strcpy(s, aStr); 33 return s; 34 } 35 }; 36 37 // Default implementation of DescribeCodeAddressLock. 38 struct DefaultDescribeCodeAddressLock { UnlockDefaultDescribeCodeAddressLock39 static void Unlock() {} LockDefaultDescribeCodeAddressLock40 static void Lock() {} 41 // Because CodeAddressService asserts that IsLocked() is true, returning true 42 // here is a sensible default when there is no relevant lock. IsLockedDefaultDescribeCodeAddressLock43 static bool IsLocked() { return true; } 44 }; 45 46 } // namespace detail 47 48 // This class is used to print details about code locations. 49 // 50 // |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h. 51 // 52 // |DescribeCodeAddressLock| is needed when the callers may be holding a lock 53 // used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement 54 // static methods IsLocked(), Unlock() and Lock(). 55 template <class AllocPolicy_ = MallocAllocPolicy, 56 class DescribeCodeAddressLock = 57 detail::DefaultDescribeCodeAddressLock> 58 class CodeAddressService 59 : private detail::CodeAddressServiceAllocPolicy<AllocPolicy_> { 60 protected: 61 // GetLocation() is the key function in this class. It's basically a wrapper 62 // around MozDescribeCodeAddress. 63 // 64 // However, MozDescribeCodeAddress is very slow on some platforms, and we 65 // have lots of repeated (i.e. same PC) calls to it. So we do some caching 66 // of results. Each cached result includes two strings (|mFunction| and 67 // |mLibrary|), so we also optimize them for space in the following ways. 68 // 69 // - The number of distinct library names is small, e.g. a few dozen. There 70 // is lots of repetition, especially of libxul. So we intern them in their 71 // own table, which saves space over duplicating them for each cache entry. 72 // 73 // - The number of distinct function names is much higher, so we duplicate 74 // them in each cache entry. That's more space-efficient than interning 75 // because entries containing single-occurrence function names are quickly 76 // overwritten, and their copies released. In addition, empty function 77 // names are common, so we use nullptr to represent them compactly. 78 79 using AllocPolicy = detail::CodeAddressServiceAllocPolicy<AllocPolicy_>; 80 using StringHashSet = HashSet<const char*, CStringHasher, AllocPolicy>; 81 82 StringHashSet mLibraryStrings; 83 84 struct Entry : private AllocPolicy { 85 const void* mPc; 86 char* mFunction; // owned by the Entry; may be null 87 const char* mLibrary; // owned by mLibraryStrings; never null 88 // in a non-empty entry is in use 89 ptrdiff_t mLOffset; 90 char* mFileName; // owned by the Entry; may be null 91 uint32_t mLineNo : 31; 92 uint32_t mInUse : 1; // is the entry used? 93 EntryEntry94 Entry() 95 : mPc(0), 96 mFunction(nullptr), 97 mLibrary(nullptr), 98 mLOffset(0), 99 mFileName(nullptr), 100 mLineNo(0), 101 mInUse(0) {} 102 ~EntryEntry103 ~Entry() { 104 // We don't free mLibrary because it's externally owned. 105 AllocPolicy::free_(mFunction); 106 AllocPolicy::free_(mFileName); 107 } 108 ReplaceEntry109 void Replace(const void* aPc, const char* aFunction, const char* aLibrary, 110 ptrdiff_t aLOffset, const char* aFileName, 111 unsigned long aLineNo) { 112 mPc = aPc; 113 114 // Convert "" to nullptr. Otherwise, make a copy of the name. 115 AllocPolicy::free_(mFunction); 116 mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction); 117 AllocPolicy::free_(mFileName); 118 mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(aFileName); 119 120 mLibrary = aLibrary; 121 mLOffset = aLOffset; 122 mLineNo = aLineNo; 123 124 mInUse = 1; 125 } 126 SizeOfExcludingThisEntry127 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 128 // Don't measure mLibrary because it's externally owned. 129 size_t n = 0; 130 n += aMallocSizeOf(mFunction); 131 n += aMallocSizeOf(mFileName); 132 return n; 133 } 134 }; 135 InternLibraryString(const char * aString)136 const char* InternLibraryString(const char* aString) { 137 auto p = mLibraryStrings.lookupForAdd(aString); 138 if (p) { 139 return *p; 140 } 141 142 const char* newString = AllocPolicy::strdup_(aString); 143 if (!mLibraryStrings.add(p, newString)) { 144 MOZ_CRASH("CodeAddressService OOM"); 145 } 146 return newString; 147 } 148 GetEntry(const void * aPc)149 Entry& GetEntry(const void* aPc) { 150 MOZ_ASSERT(DescribeCodeAddressLock::IsLocked()); 151 152 uint32_t index = HashGeneric(aPc) & kMask; 153 MOZ_ASSERT(index < kNumEntries); 154 Entry& entry = mEntries[index]; 155 156 if (!entry.mInUse || entry.mPc != aPc) { 157 mNumCacheMisses++; 158 159 // MozDescribeCodeAddress can (on Linux) acquire a lock inside 160 // the shared library loader. Another thread might call malloc 161 // while holding that lock (when loading a shared library). So 162 // we have to exit the lock around this call. For details, see 163 // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 164 MozCodeAddressDetails details; 165 { 166 DescribeCodeAddressLock::Unlock(); 167 (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details); 168 DescribeCodeAddressLock::Lock(); 169 } 170 171 const char* library = InternLibraryString(details.library); 172 entry.Replace(aPc, details.function, library, details.loffset, 173 details.filename, details.lineno); 174 175 } else { 176 mNumCacheHits++; 177 } 178 179 MOZ_ASSERT(entry.mPc == aPc); 180 181 return entry; 182 } 183 184 // A direct-mapped cache. When doing dmd::Analyze() just after starting 185 // desktop Firefox (which is similar to analyzing after a longer-running 186 // session, thanks to the limit on how many records we print), a cache with 187 // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit 188 // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB 189 // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms). 190 static const size_t kNumEntries = 1 << 12; 191 static const size_t kMask = kNumEntries - 1; 192 Entry mEntries[kNumEntries]; 193 194 size_t mNumCacheHits; 195 size_t mNumCacheMisses; 196 197 public: CodeAddressService()198 CodeAddressService() 199 : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {} 200 ~CodeAddressService()201 ~CodeAddressService() { 202 for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { 203 AllocPolicy::free_(const_cast<char*>(iter.get())); 204 } 205 } 206 207 // Returns the minimum number of characters necessary to format the frame 208 // information, without the terminating null. The buffer will be truncated 209 // if the returned value is greater than aBufLen-1. GetLocation(uint32_t aFrameNumber,const void * aPc,char * aBuf,size_t aBufLen)210 int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, 211 size_t aBufLen) { 212 Entry& entry = GetEntry(aPc); 213 return MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc, 214 entry.mFunction, entry.mLibrary, entry.mLOffset, 215 entry.mFileName, entry.mLineNo); 216 } 217 SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)218 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { 219 size_t n = aMallocSizeOf(this); 220 for (uint32_t i = 0; i < kNumEntries; i++) { 221 n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf); 222 } 223 224 n += mLibraryStrings.shallowSizeOfExcludingThis(aMallocSizeOf); 225 for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { 226 n += aMallocSizeOf(iter.get()); 227 } 228 229 return n; 230 } 231 CacheCapacity()232 size_t CacheCapacity() const { return kNumEntries; } 233 CacheCount()234 size_t CacheCount() const { 235 size_t n = 0; 236 for (size_t i = 0; i < kNumEntries; i++) { 237 if (mEntries[i].mInUse) { 238 n++; 239 } 240 } 241 return n; 242 } 243 NumCacheHits()244 size_t NumCacheHits() const { return mNumCacheHits; } NumCacheMisses()245 size_t NumCacheMisses() const { return mNumCacheMisses; } 246 }; 247 248 } // namespace mozilla 249 250 #endif // CodeAddressService_h__ 251