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