CodeAddressService.h 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #ifndef CodeAddressService_h__
  6. #define CodeAddressService_h__
  7. #include "mozilla/Assertions.h"
  8. #include "mozilla/HashFunctions.h"
  9. #include "mozilla/IntegerPrintfMacros.h"
  10. #include "mozilla/MemoryReporting.h"
  11. #include "mozilla/Types.h"
  12. #include "mozilla/StackWalk.h"
  13. namespace mozilla {
  14. // This class is used to print details about code locations.
  15. //
  16. // |StringTable| must implement an Intern() method that returns an interned
  17. // copy of the string that was passed in, as well as a standard
  18. // SizeOfExcludingThis() method.
  19. //
  20. // |StringAlloc| must implement |copy| and |free|. |copy| copies a string,
  21. // while |free| is used to free strings created by |copy|.
  22. //
  23. // |DescribeCodeAddressLock| is needed when the callers may be holding a lock
  24. // used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement
  25. // static methods IsLocked(), Unlock() and Lock().
  26. template <class StringTable,
  27. class StringAlloc,
  28. class DescribeCodeAddressLock>
  29. class CodeAddressService
  30. {
  31. // GetLocation() is the key function in this class. It's basically a wrapper
  32. // around MozDescribeCodeAddress.
  33. //
  34. // However, MozDescribeCodeAddress is very slow on some platforms, and we
  35. // have lots of repeated (i.e. same PC) calls to it. So we do some caching
  36. // of results. Each cached result includes two strings (|mFunction| and
  37. // |mLibrary|), so we also optimize them for space in the following ways.
  38. //
  39. // - The number of distinct library names is small, e.g. a few dozen. There
  40. // is lots of repetition, especially of libxul. So we intern them in their
  41. // own table, which saves space over duplicating them for each cache entry.
  42. //
  43. // - The number of distinct function names is much higher, so we duplicate
  44. // them in each cache entry. That's more space-efficient than interning
  45. // because entries containing single-occurrence function names are quickly
  46. // overwritten, and their copies released. In addition, empty function
  47. // names are common, so we use nullptr to represent them compactly.
  48. StringTable mLibraryStrings;
  49. struct Entry
  50. {
  51. const void* mPc;
  52. char* mFunction; // owned by the Entry; may be null
  53. const char* mLibrary; // owned by mLibraryStrings; never null
  54. // in a non-empty entry is in use
  55. ptrdiff_t mLOffset;
  56. char* mFileName; // owned by the Entry; may be null
  57. uint32_t mLineNo:31;
  58. uint32_t mInUse:1; // is the entry used?
  59. Entry()
  60. : mPc(0), mFunction(nullptr), mLibrary(nullptr), mLOffset(0),
  61. mFileName(nullptr), mLineNo(0), mInUse(0)
  62. {}
  63. ~Entry()
  64. {
  65. // We don't free mLibrary because it's externally owned.
  66. StringAlloc::free(mFunction);
  67. StringAlloc::free(mFileName);
  68. }
  69. void Replace(const void* aPc, const char* aFunction,
  70. const char* aLibrary, ptrdiff_t aLOffset,
  71. const char* aFileName, unsigned long aLineNo)
  72. {
  73. mPc = aPc;
  74. // Convert "" to nullptr. Otherwise, make a copy of the name.
  75. StringAlloc::free(mFunction);
  76. mFunction = !aFunction[0] ? nullptr : StringAlloc::copy(aFunction);
  77. StringAlloc::free(mFileName);
  78. mFileName = !aFileName[0] ? nullptr : StringAlloc::copy(aFileName);
  79. mLibrary = aLibrary;
  80. mLOffset = aLOffset;
  81. mLineNo = aLineNo;
  82. mInUse = 1;
  83. }
  84. size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  85. {
  86. // Don't measure mLibrary because it's externally owned.
  87. size_t n = 0;
  88. n += aMallocSizeOf(mFunction);
  89. n += aMallocSizeOf(mFileName);
  90. return n;
  91. }
  92. };
  93. // A direct-mapped cache. When doing dmd::Analyze() just after starting
  94. // desktop Firefox (which is similar to analyzing after a longer-running
  95. // session, thanks to the limit on how many records we print), a cache with
  96. // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit
  97. // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB
  98. // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms).
  99. static const size_t kNumEntries = 1 << 12;
  100. static const size_t kMask = kNumEntries - 1;
  101. Entry mEntries[kNumEntries];
  102. size_t mNumCacheHits;
  103. size_t mNumCacheMisses;
  104. public:
  105. CodeAddressService()
  106. : mEntries(), mNumCacheHits(0), mNumCacheMisses(0)
  107. {
  108. }
  109. void GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf,
  110. size_t aBufLen)
  111. {
  112. MOZ_ASSERT(DescribeCodeAddressLock::IsLocked());
  113. uint32_t index = HashGeneric(aPc) & kMask;
  114. MOZ_ASSERT(index < kNumEntries);
  115. Entry& entry = mEntries[index];
  116. if (!entry.mInUse || entry.mPc != aPc) {
  117. mNumCacheMisses++;
  118. // MozDescribeCodeAddress can (on Linux) acquire a lock inside
  119. // the shared library loader. Another thread might call malloc
  120. // while holding that lock (when loading a shared library). So
  121. // we have to exit the lock around this call. For details, see
  122. // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3
  123. MozCodeAddressDetails details;
  124. {
  125. DescribeCodeAddressLock::Unlock();
  126. (void)MozDescribeCodeAddress(const_cast<void*>(aPc), &details);
  127. DescribeCodeAddressLock::Lock();
  128. }
  129. const char* library = mLibraryStrings.Intern(details.library);
  130. entry.Replace(aPc, details.function, library, details.loffset,
  131. details.filename, details.lineno);
  132. } else {
  133. mNumCacheHits++;
  134. }
  135. MOZ_ASSERT(entry.mPc == aPc);
  136. MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc,
  137. entry.mFunction, entry.mLibrary, entry.mLOffset,
  138. entry.mFileName, entry.mLineNo);
  139. }
  140. size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
  141. {
  142. size_t n = aMallocSizeOf(this);
  143. for (uint32_t i = 0; i < kNumEntries; i++) {
  144. n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf);
  145. }
  146. n += mLibraryStrings.SizeOfExcludingThis(aMallocSizeOf);
  147. return n;
  148. }
  149. size_t CacheCapacity() const { return kNumEntries; }
  150. size_t CacheCount() const
  151. {
  152. size_t n = 0;
  153. for (size_t i = 0; i < kNumEntries; i++) {
  154. if (mEntries[i].mInUse) {
  155. n++;
  156. }
  157. }
  158. return n;
  159. }
  160. size_t NumCacheHits() const { return mNumCacheHits; }
  161. size_t NumCacheMisses() const { return mNumCacheMisses; }
  162. };
  163. } // namespace mozilla
  164. #endif // CodeAddressService_h__