Line data Source code
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 : #include "ThreadStackHelper.h"
8 : #include "MainThreadUtils.h"
9 : #include "nsJSPrincipals.h"
10 : #include "nsScriptSecurityManager.h"
11 : #include "jsfriendapi.h"
12 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
13 : #include "js/ProfilingStack.h"
14 : #endif
15 :
16 : #include "mozilla/Assertions.h"
17 : #include "mozilla/Attributes.h"
18 : #include "mozilla/IntegerPrintfMacros.h"
19 : #include "mozilla/Move.h"
20 : #include "mozilla/Scoped.h"
21 : #include "mozilla/UniquePtr.h"
22 : #include "mozilla/MemoryChecking.h"
23 : #include "mozilla/Sprintf.h"
24 : #include "nsThread.h"
25 :
26 : #ifdef __GNUC__
27 : # pragma GCC diagnostic push
28 : # pragma GCC diagnostic ignored "-Wshadow"
29 : #endif
30 :
31 : #if defined(MOZ_VALGRIND)
32 : # include <valgrind/valgrind.h>
33 : #endif
34 :
35 : #include <string.h>
36 : #include <vector>
37 : #include <cstdlib>
38 :
39 : #ifdef XP_LINUX
40 : #include <ucontext.h>
41 : #include <unistd.h>
42 : #include <sys/syscall.h>
43 : #endif
44 :
45 : #ifdef __GNUC__
46 : # pragma GCC diagnostic pop // -Wshadow
47 : #endif
48 :
49 : #if defined(XP_LINUX) || defined(XP_MACOSX)
50 : #include <pthread.h>
51 : #endif
52 :
53 : #ifdef ANDROID
54 : #ifndef SYS_gettid
55 : #define SYS_gettid __NR_gettid
56 : #endif
57 : #if defined(__arm__) && !defined(__NR_rt_tgsigqueueinfo)
58 : // Some NDKs don't define this constant even though the kernel supports it.
59 : #define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
60 : #endif
61 : #ifndef SYS_rt_tgsigqueueinfo
62 : #define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo
63 : #endif
64 : #endif
65 :
66 : namespace mozilla {
67 :
68 0 : ThreadStackHelper::ThreadStackHelper()
69 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
70 : : mStackToFill(nullptr)
71 : , mPseudoStack(profiler_get_pseudo_stack())
72 : , mMaxStackSize(Stack::sMaxInlineStorage)
73 : , mMaxBufferSize(512)
74 : #endif
75 : #ifdef MOZ_THREADSTACKHELPER_NATIVE
76 : , mNativeStackToFill(nullptr)
77 : #endif
78 : {
79 0 : mThreadId = profiler_current_thread_id();
80 0 : }
81 :
82 : namespace {
83 : template<typename T>
84 : class ScopedSetPtr
85 : {
86 : private:
87 : T*& mPtr;
88 : public:
89 : ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
90 : ~ScopedSetPtr() { mPtr = nullptr; }
91 : };
92 : } // namespace
93 :
94 : void
95 0 : ThreadStackHelper::GetPseudoStack(Stack& aStack, nsACString& aRunnableName)
96 : {
97 0 : GetStacksInternal(&aStack, nullptr, aRunnableName);
98 0 : }
99 :
100 : void
101 0 : ThreadStackHelper::GetNativeStack(NativeStack& aNativeStack, nsACString& aRunnableName)
102 : {
103 0 : GetStacksInternal(nullptr, &aNativeStack, aRunnableName);
104 0 : }
105 :
106 : void
107 0 : ThreadStackHelper::GetPseudoAndNativeStack(Stack& aStack,
108 : NativeStack& aNativeStack,
109 : nsACString& aRunnableName)
110 : {
111 0 : GetStacksInternal(&aStack, &aNativeStack, aRunnableName);
112 0 : }
113 :
114 : void
115 0 : ThreadStackHelper::GetStacksInternal(Stack* aStack,
116 : NativeStack* aNativeStack,
117 : nsACString& aRunnableName)
118 : {
119 0 : aRunnableName.AssignLiteral("???");
120 :
121 : #if defined(MOZ_THREADSTACKHELPER_PSEUDO) || defined(MOZ_THREADSTACKHELPER_NATIVE)
122 : // Always run PrepareStackBuffer first to clear aStack
123 : if (aStack && !PrepareStackBuffer(*aStack)) {
124 : // Skip and return empty aStack
125 : return;
126 : }
127 :
128 : // Prepare the native stack
129 : if (aNativeStack) {
130 : aNativeStack->clear();
131 : aNativeStack->reserve(Telemetry::HangStack::sMaxNativeFrames);
132 : }
133 :
134 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
135 : ScopedSetPtr<Stack> stackPtr(mStackToFill, aStack);
136 : #endif
137 : #ifdef MOZ_THREADSTACKHELPER_NATIVE
138 : ScopedSetPtr<NativeStack> nativeStackPtr(mNativeStackToFill, aNativeStack);
139 : #endif
140 :
141 : char nameBuffer[1000] = {0};
142 : auto callback = [&, this] (void** aPCs, size_t aCount, bool aIsMainThread) {
143 : // NOTE: We cannot allocate any memory in this callback, as the target
144 : // thread is suspended, so we first copy it into a stack-allocated buffer,
145 : // and then once the target thread is resumed, we can copy it into a real
146 : // nsCString.
147 : //
148 : // Currently we only store the names of runnables which are running on the
149 : // main thread, so we only want to read sMainThreadRunnableName and copy its
150 : // value in the case that we are currently suspending the main thread.
151 : if (aIsMainThread && nsThread::sMainThreadRunnableName) {
152 : strncpy(nameBuffer, nsThread::sMainThreadRunnableName, sizeof(nameBuffer));
153 : // Make sure the string is null-terminated.
154 : nameBuffer[sizeof(nameBuffer) - 1] = '\0';
155 : }
156 :
157 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
158 : if (mStackToFill) {
159 : FillStackBuffer();
160 : }
161 : #endif
162 :
163 : #ifdef MOZ_THREADSTACKHELPER_NATIVE
164 : if (mNativeStackToFill) {
165 : while (aCount-- &&
166 : mNativeStackToFill->size() < mNativeStackToFill->capacity()) {
167 : mNativeStackToFill->push_back(reinterpret_cast<uintptr_t>(aPCs[aCount]));
168 : }
169 : }
170 : #endif
171 : };
172 :
173 : if (mStackToFill || mNativeStackToFill) {
174 : profiler_suspend_and_sample_thread(mThreadId,
175 : callback,
176 : /* aSampleNative = */ !!aNativeStack);
177 : }
178 :
179 : // Copy the name buffer allocation into the output string.
180 : if (nameBuffer[0] != 0) {
181 : aRunnableName = nameBuffer;
182 : }
183 : #endif
184 0 : }
185 :
186 : bool
187 0 : ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
188 : {
189 : // Return false to skip getting the stack and return an empty stack
190 0 : aStack.clear();
191 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
192 : /* Normally, provided the profiler is enabled, it would be an error if we
193 : don't have a pseudostack here (the thread probably forgot to call
194 : profiler_register_thread). However, on B2G, profiling secondary threads
195 : may be disabled despite profiler being enabled. This is by-design and
196 : is not an error. */
197 : #ifdef MOZ_WIDGET_GONK
198 : if (!mPseudoStack) {
199 : return false;
200 : }
201 : #endif
202 : MOZ_ASSERT(mPseudoStack);
203 : if (!aStack.reserve(mMaxStackSize) ||
204 : !aStack.reserve(aStack.capacity()) || // reserve up to the capacity
205 : !aStack.EnsureBufferCapacity(mMaxBufferSize)) {
206 : return false;
207 : }
208 : return true;
209 : #else
210 0 : return false;
211 : #endif
212 : }
213 :
214 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
215 :
216 : namespace {
217 :
218 : bool
219 : IsChromeJSScript(JSScript* aScript)
220 : {
221 : // May be called from another thread or inside a signal handler.
222 : // We assume querying the script is safe but we must not manipulate it.
223 :
224 : nsIScriptSecurityManager* const secman =
225 : nsScriptSecurityManager::GetScriptSecurityManager();
226 : NS_ENSURE_TRUE(secman, false);
227 :
228 : JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
229 : return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
230 : }
231 :
232 : // Get the full path after the URI scheme, if the URI matches the scheme.
233 : // For example, GetFullPathForScheme("a://b/c/d/e", "a://") returns "b/c/d/e".
234 : template <size_t LEN>
235 : const char*
236 : GetFullPathForScheme(const char* filename, const char (&scheme)[LEN]) {
237 : // Account for the null terminator included in LEN.
238 : if (!strncmp(filename, scheme, LEN - 1)) {
239 : return filename + LEN - 1;
240 : }
241 : return nullptr;
242 : }
243 :
244 : // Get the full path after a URI component, if the URI contains the component.
245 : // For example, GetPathAfterComponent("a://b/c/d/e", "/c/") returns "d/e".
246 : template <size_t LEN>
247 : const char*
248 : GetPathAfterComponent(const char* filename, const char (&component)[LEN]) {
249 : const char* found = nullptr;
250 : const char* next = strstr(filename, component);
251 : while (next) {
252 : // Move 'found' to end of the component, after the separator '/'.
253 : // 'LEN - 1' accounts for the null terminator included in LEN,
254 : found = next + LEN - 1;
255 : // Resume searching before the separator '/'.
256 : next = strstr(found - 1, component);
257 : }
258 : return found;
259 : }
260 :
261 : } // namespace
262 :
263 : const char*
264 : ThreadStackHelper::AppendJSEntry(const js::ProfileEntry* aEntry,
265 : intptr_t& aAvailableBufferSize,
266 : const char* aPrevLabel)
267 : {
268 : // May be called from another thread or inside a signal handler.
269 : // We assume querying the script is safe but we must not manupulate it.
270 : // Also we must not allocate any memory from heap.
271 : MOZ_ASSERT(aEntry->isJs());
272 :
273 : const char* label;
274 : JSScript* script = aEntry->script();
275 : if (!script) {
276 : label = "(profiling suppressed)";
277 : } else if (IsChromeJSScript(aEntry->script())) {
278 : const char* filename = JS_GetScriptFilename(aEntry->script());
279 : const unsigned lineno = JS_PCToLineNumber(aEntry->script(), aEntry->pc());
280 : MOZ_ASSERT(filename);
281 :
282 : char buffer[128]; // Enough to fit longest js file name from the tree
283 :
284 : // Some script names are in the form "foo -> bar -> baz".
285 : // Here we find the origin of these redirected scripts.
286 : const char* basename = GetPathAfterComponent(filename, " -> ");
287 : if (basename) {
288 : filename = basename;
289 : }
290 :
291 : basename = GetFullPathForScheme(filename, "chrome://");
292 : if (!basename) {
293 : basename = GetFullPathForScheme(filename, "resource://");
294 : }
295 : if (!basename) {
296 : // If the (add-on) script is located under the {profile}/extensions
297 : // directory, extract the path after the /extensions/ part.
298 : basename = GetPathAfterComponent(filename, "/extensions/");
299 : }
300 : if (!basename) {
301 : // Only keep the file base name for paths outside the above formats.
302 : basename = strrchr(filename, '/');
303 : basename = basename ? basename + 1 : filename;
304 : // Look for Windows path separator as well.
305 : filename = strrchr(basename, '\\');
306 : if (filename) {
307 : basename = filename + 1;
308 : }
309 : }
310 :
311 : size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno);
312 : if (len < sizeof(buffer)) {
313 : if (mStackToFill->IsSameAsEntry(aPrevLabel, buffer)) {
314 : return aPrevLabel;
315 : }
316 :
317 : // Keep track of the required buffer size
318 : aAvailableBufferSize -= (len + 1);
319 : if (aAvailableBufferSize >= 0) {
320 : // Buffer is big enough.
321 : return mStackToFill->InfallibleAppendViaBuffer(buffer, len);
322 : }
323 : // Buffer is not big enough; fall through to using static label below.
324 : }
325 : // snprintf failed or buffer is not big enough.
326 : label = "(chrome script)";
327 : } else {
328 : label = "(content script)";
329 : }
330 :
331 : if (mStackToFill->IsSameAsEntry(aPrevLabel, label)) {
332 : return aPrevLabel;
333 : }
334 : mStackToFill->infallibleAppend(label);
335 : return label;
336 : }
337 :
338 : #endif // MOZ_THREADSTACKHELPER_PSEUDO
339 :
340 : void
341 0 : ThreadStackHelper::FillStackBuffer()
342 : {
343 : #ifdef MOZ_THREADSTACKHELPER_PSEUDO
344 : MOZ_ASSERT(mStackToFill->empty());
345 :
346 : size_t reservedSize = mStackToFill->capacity();
347 : size_t reservedBufferSize = mStackToFill->AvailableBufferSize();
348 : intptr_t availableBufferSize = intptr_t(reservedBufferSize);
349 :
350 : // Go from front to back
351 : const js::ProfileEntry* entry = mPseudoStack->entries;
352 : const js::ProfileEntry* end = entry + mPseudoStack->stackSize();
353 : // Deduplicate identical, consecutive frames
354 : const char* prevLabel = nullptr;
355 : for (; reservedSize-- && entry != end; entry++) {
356 : if (entry->isJs()) {
357 : prevLabel = AppendJSEntry(entry, availableBufferSize, prevLabel);
358 : continue;
359 : }
360 : const char* const label = entry->label();
361 : if (mStackToFill->IsSameAsEntry(prevLabel, label)) {
362 : // Avoid duplicate labels to save space in the stack.
363 : continue;
364 : }
365 : mStackToFill->infallibleAppend(label);
366 : prevLabel = label;
367 : }
368 :
369 : // end != entry if we exited early due to not enough reserved frames.
370 : // Expand the number of reserved frames for next time.
371 : mMaxStackSize = mStackToFill->capacity() + (end - entry);
372 :
373 : // availableBufferSize < 0 if we needed a larger buffer than we reserved.
374 : // Calculate a new reserve size for next time.
375 : if (availableBufferSize < 0) {
376 : mMaxBufferSize = reservedBufferSize - availableBufferSize;
377 : }
378 : #endif
379 0 : }
380 :
381 : } // namespace mozilla
|