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