1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
|
/* 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/. */
#include "ThreadAnnotation.h"
#include <stddef.h>
#include "mozilla/Assertions.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/UniquePtr.h"
#include "prthread.h"
#include "nsDebug.h"
#include "nsExceptionHandler.h"
#include "nsString.h"
#include "nsTArray.h"
using mozilla::StaticMutex;
using mozilla::StaticMutexAutoLock;
using mozilla::UniquePtr;
namespace CrashReporter {
namespace {
#ifdef XP_MACOSX
/*
* On the Mac, exception handler callbacks are invoked in a context where all
* other threads are paused. As a result, attempting to acquire a mutex is
* problematic because 1) the mutex may be held by another thread which is
* now suspended and 2) acquiring an unheld mutex can trigger memory allocation
* which generally requires allocator locks. This class is a wrapper around a
* StaticMutex, providing an IsLocked() method which only makes sense to use
* in the Mac exception handling context when other threads are paused.
*/
class MacCrashReporterLock {
public:
void Lock() {
sInnerMutex.Lock();
sIsLocked = true;
}
void Unlock() {
sIsLocked = false;
sInnerMutex.Unlock();
}
/*
* Returns true if the lock is held at the time the method is called.
* The return value is out-of-date by the time this method returns unless
* we have a guarantee that other threads are not running such as in Mac
* breadkpad exception handler context.
*/
bool IsLocked() { return sIsLocked; }
void AssertCurrentThreadOwns() { sInnerMutex.AssertCurrentThreadOwns(); }
private:
static StaticMutex sInnerMutex;
static bool sIsLocked;
};
StaticMutex MacCrashReporterLock::sInnerMutex;
bool MacCrashReporterLock::sIsLocked;
// Use MacCrashReporterLock for locking
typedef mozilla::detail::BaseAutoLock<MacCrashReporterLock&>
CrashReporterAutoLock;
typedef MacCrashReporterLock CrashReporterLockType;
#else /* !XP_MACOSX */
// Use StaticMutex for locking
typedef StaticMutexAutoLock CrashReporterAutoLock;
typedef StaticMutex CrashReporterLockType;
#endif /* XP_MACOSX */
// Protects access to sInitialized and sThreadAnnotations.
static CrashReporterLockType sMutex;
class ThreadAnnotationSpan {
public:
ThreadAnnotationSpan(uint32_t aBegin, uint32_t aEnd)
: mBegin(aBegin), mEnd(aEnd) {
MOZ_ASSERT(mBegin < mEnd);
}
~ThreadAnnotationSpan();
class Comparator {
public:
bool Equals(const ThreadAnnotationSpan* const& a,
const ThreadAnnotationSpan* const& b) const {
return a->mBegin == b->mBegin;
}
bool LessThan(const ThreadAnnotationSpan* const& a,
const ThreadAnnotationSpan* const& b) const {
return a->mBegin < b->mBegin;
}
};
private:
// ~ThreadAnnotationSpan() does nontrivial thing. Make sure we don't
// instantiate accidentally.
ThreadAnnotationSpan(const ThreadAnnotationSpan& aOther) = delete;
ThreadAnnotationSpan& operator=(const ThreadAnnotationSpan& aOther) = delete;
friend class ThreadAnnotationData;
friend class Comparator;
uint32_t mBegin;
uint32_t mEnd;
};
// This class keeps the flat version of thread annotations for each thread.
// When a thread calls CrashReporter::SetCurrentThreadName(), it adds
// information about the calling thread (thread id and name) to this class.
// When crash happens, the crash reporter gets flat representation and add to
// the crash annotation file.
class ThreadAnnotationData {
public:
ThreadAnnotationData() = default;
~ThreadAnnotationData() = default;
// Adds <pre> tid:"thread name",</pre> annotation to the current annotations.
// Returns an instance of ThreadAnnotationSpan for cleanup on thread
// termination.
ThreadAnnotationSpan* AddThreadAnnotation(ThreadId aTid,
const char* aThreadName) {
if (!aTid || !aThreadName) {
return nullptr;
}
uint32_t oldLength = mData.Length();
mData.AppendPrintf("%u:\"%s\",", aTid, aThreadName);
uint32_t newLength = mData.Length();
ThreadAnnotationSpan* rv = new ThreadAnnotationSpan(oldLength, newLength);
mDataSpans.AppendElement(rv);
return rv;
}
// Called on thread termination. Removes the thread annotation, represented as
// ThreadAnnotationSpan, from the flat representation.
void EraseThreadAnnotation(const ThreadAnnotationSpan& aThreadInfo) {
uint32_t begin = aThreadInfo.mBegin;
uint32_t end = aThreadInfo.mEnd;
if (!(begin < end && end <= mData.Length())) {
return;
}
uint32_t cutLength = end - begin;
mData.Cut(begin, cutLength);
// Adjust the ThreadAnnotationSpan affected by data shifting.
size_t index = mDataSpans.BinaryIndexOf(&aThreadInfo,
ThreadAnnotationSpan::Comparator());
for (size_t i = index + 1; i < mDataSpans.Length(); i++) {
ThreadAnnotationSpan* elem = mDataSpans[i];
MOZ_ASSERT(elem->mBegin >= cutLength);
MOZ_ASSERT(elem->mEnd > cutLength);
elem->mBegin -= cutLength;
elem->mEnd -= cutLength;
}
// No loner tracking aThreadInfo.
mDataSpans.RemoveElementAt(index);
}
// Gets the flat representation of thread annotations.
void GetData(const std::function<void(const char*)>& aCallback) {
aCallback(mData.BeginReading());
}
private:
// The flat representation of thread annotations.
nsCString mData;
// This array tracks the created ThreadAnnotationSpan instances so that we
// can make adjustments accordingly when we cut substrings from mData on
// thread exit.
nsTArray<ThreadAnnotationSpan*> mDataSpans;
};
static bool sInitialized = false;
static UniquePtr<ThreadAnnotationData> sThreadAnnotations;
static unsigned sTLSThreadInfoKey = (unsigned)-1;
void ThreadLocalDestructor(void* aUserData) {
MOZ_ASSERT(aUserData);
CrashReporterAutoLock lock(sMutex);
ThreadAnnotationSpan* aThreadInfo =
static_cast<ThreadAnnotationSpan*>(aUserData);
delete aThreadInfo;
}
// This is called on thread termination.
ThreadAnnotationSpan::~ThreadAnnotationSpan() {
// Note that we can't lock the mutex here because this function may be called
// from SetCurrentThreadName().
sMutex.AssertCurrentThreadOwns();
if (sThreadAnnotations) {
sThreadAnnotations->EraseThreadAnnotation(*this);
}
}
} // Anonymous namespace.
void InitThreadAnnotation() {
CrashReporterAutoLock lock(sMutex);
if (sInitialized) {
return;
}
PRStatus status =
PR_NewThreadPrivateIndex(&sTLSThreadInfoKey, &ThreadLocalDestructor);
if (status == PR_FAILURE) {
return;
}
sInitialized = true;
sThreadAnnotations = mozilla::MakeUnique<ThreadAnnotationData>();
}
void SetCurrentThreadName(const char* aName) {
if (PR_GetThreadPrivate(sTLSThreadInfoKey)) {
// Explicitly set TLS value to null (and call the dtor function ) before
// acquiring sMutex to avoid reentrant deadlock.
PR_SetThreadPrivate(sTLSThreadInfoKey, nullptr);
}
CrashReporterAutoLock lock(sMutex);
if (!sInitialized) {
return;
}
ThreadAnnotationSpan* threadInfo =
sThreadAnnotations->AddThreadAnnotation(CurrentThreadId(), aName);
// This may destroy the old insatnce.
PR_SetThreadPrivate(sTLSThreadInfoKey, threadInfo);
}
void GetFlatThreadAnnotation(const std::function<void(const char*)>& aCallback,
bool aIsHandlingException) {
bool lockNeeded = true;
#ifdef XP_MACOSX
if (aIsHandlingException) {
// Don't acquire the lock on Mac because we are
// executing in exception context where all other
// threads are paused. If the lock is held, skip
// thread annotations to avoid deadlock caused by
// waiting for a suspended thread. If the lock
// isn't held, acquiring it serves no purpose and
// can trigger memory allocations.
if (sMutex.IsLocked()) {
aCallback("");
return;
}
lockNeeded = false;
}
#endif
if (lockNeeded) {
sMutex.Lock();
}
if (sThreadAnnotations) {
sThreadAnnotations->GetData(aCallback);
} else {
// Maybe already shutdown: call aCallback with empty annotation data.
aCallback("");
}
if (lockNeeded) {
sMutex.Unlock();
}
}
void ShutdownThreadAnnotation() {
CrashReporterAutoLock lock(sMutex);
sInitialized = false;
sThreadAnnotations.reset();
}
} // namespace CrashReporter
|