Files
tubestation/toolkit/components/backgroundhangmonitor/ThreadStackHelper.cpp
Michael Layzell 9782242b7e Bug 1367406 - Part 1: Add interleaved stack functionality to HangStack, r=froydnj
- This patch was written in 3 interdependent parts, which are described below -

Part 1A: Allow HangStack to contain raw PCs and Module offsets, r=froydnj

The HangStack previously consisted of an array of const char* pointers into its
backing string buffer, which represented pseudostack entries. With interleaved
stacks, it is now possible for the stack to contain raw unresolved program
counters (Kind::PC), and module/offset pairs (Kind::MODOFFSET). To do this, we
use a discriminated union, and make the backing array use the discriminated
union instead of const char*s.

The code cannot use mozilla::Variant<const char*, uintptr_t, Module>
unfortuantely, as we cannot use the implementation of ParamTraits for Variant in
HangStack's ParamTraits implementation.

When deserializing a HangStack over IPC, we need to read the string frame
entries into the backing string buffer, and generate const char* entries for
each of the strings which we read in over IPC. The default implementation of
ParamTraits wouldn't give us access to the enclusing HangStack object while
deserializing each individual entry, so we couldn't use it. In fact, Entries
don't have ParamTraits implemented for them at all, and can only be sent over
IPC as part of a HangStack due to this dependency.

Part 1B: Remove nsIHangDetails.pseudoStack, replace ProcessedStack w/ new HangStack type, r=froydnj

Previously there were two stack objects on each HangDetails object: mStack and
mPseudoStack. mStack was a Telemetry::ProcessedStack, while mPseudoStack was a
HangStack. After the changes in part 1A, HangStack can now contain all of the
information of both the old HangStack and ProcessedStack, so the mPseudoStack
field is renamed to mStack, and the old mStack field is removed.

This patch also implements the new GetStack getter, which generates the JS data
format for the new HangStack type.

Part 1C: Collect interleaved stacks w/ ProfilerStackCollector API in ThreadStackHelper, r=froydnj

This new API was added by njn in bug 1380286, and provides both pseudostack and
native stack entries to the consumer of the API.

This patch changes ThreadStackHelper to use this new API instead of the previous
one, and use it to collect the frames directly into HangStack objects.
2017-08-16 11:44:02 -04:00

320 lines
9.3 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "ThreadStackHelper.h"
#include "MainThreadUtils.h"
#include "nsJSPrincipals.h"
#include "nsScriptSecurityManager.h"
#include "jsfriendapi.h"
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
#include "js/ProfilingStack.h"
#endif
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/Move.h"
#include "mozilla/Scoped.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/MemoryChecking.h"
#include "mozilla/Sprintf.h"
#include "nsThread.h"
#include "mozilla/HangStack.h"
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wshadow"
#endif
#if defined(MOZ_VALGRIND)
# include <valgrind/valgrind.h>
#endif
#include <string.h>
#include <vector>
#include <cstdlib>
#ifdef XP_LINUX
#include <ucontext.h>
#include <unistd.h>
#include <sys/syscall.h>
#endif
#ifdef __GNUC__
# pragma GCC diagnostic pop // -Wshadow
#endif
#if defined(XP_LINUX) || defined(XP_MACOSX)
#include <pthread.h>
#endif
#ifdef ANDROID
#ifndef SYS_gettid
#define SYS_gettid __NR_gettid
#endif
#if defined(__arm__) && !defined(__NR_rt_tgsigqueueinfo)
// Some NDKs don't define this constant even though the kernel supports it.
#define __NR_rt_tgsigqueueinfo (__NR_SYSCALL_BASE+363)
#endif
#ifndef SYS_rt_tgsigqueueinfo
#define SYS_rt_tgsigqueueinfo __NR_rt_tgsigqueueinfo
#endif
#endif
namespace mozilla {
ThreadStackHelper::ThreadStackHelper()
: mStackToFill(nullptr)
, mMaxStackSize(HangStack::sMaxInlineStorage)
, mMaxBufferSize(512)
, mDesiredStackSize(0)
, mDesiredBufferSize(0)
{
mThreadId = profiler_current_thread_id();
}
bool
ThreadStackHelper::PrepareStackBuffer(HangStack& aStack)
{
// If we need to grow because we used more than we could store last time,
// increase our maximum sizes for this time.
if (mDesiredBufferSize > mMaxBufferSize) {
mMaxBufferSize = mDesiredBufferSize;
}
if (mDesiredStackSize > mMaxStackSize) {
mMaxStackSize = mDesiredStackSize;
}
mDesiredBufferSize = 0;
mDesiredStackSize = 0;
// Return false to skip getting the stack and return an empty stack
aStack.clear();
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
if (!aStack.reserve(mMaxStackSize) ||
!aStack.reserve(aStack.capacity()) || // reserve up to the capacity
!aStack.EnsureBufferCapacity(mMaxBufferSize)) {
return false;
}
return true;
#else
return false;
#endif
}
namespace {
template<typename T>
class ScopedSetPtr
{
private:
T*& mPtr;
public:
ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
~ScopedSetPtr() { mPtr = nullptr; }
};
} // namespace
void
ThreadStackHelper::GetStack(HangStack& aStack, nsACString& aRunnableName, bool aStackWalk)
{
aRunnableName.AssignLiteral("???");
if (!PrepareStackBuffer(aStack)) {
return;
}
Array<char, nsThread::kRunnableNameBufSize> runnableName;
runnableName[0] = '\0';
ScopedSetPtr<HangStack> _stackGuard(mStackToFill, &aStack);
ScopedSetPtr<Array<char, nsThread::kRunnableNameBufSize>>
_runnableGuard(mRunnableNameBuffer, &runnableName);
// XXX: We don't need to pass in ProfilerFeature::StackWalk to trigger
// stackwalking, as that is instead controlled by the last argument.
profiler_suspend_and_sample_thread(
mThreadId, ProfilerFeature::Privacy, *this, aStackWalk);
// Copy the name buffer allocation into the output string. We explicitly set
// the last byte to null in case we read in some corrupted data without a null
// terminator.
runnableName[nsThread::kRunnableNameBufSize - 1] = '\0';
aRunnableName.AssignASCII(runnableName.cbegin());
}
void
ThreadStackHelper::SetIsMainThread()
{
MOZ_RELEASE_ASSERT(mRunnableNameBuffer);
// NOTE: We cannot allocate any memory in this callback, as the target
// thread is suspended, so we first copy it into a stack-allocated buffer,
// and then once the target thread is resumed, we can copy it into a real
// nsCString.
//
// Currently we only store the names of runnables which are running on the
// main thread, so we only want to read sMainThreadRunnableName and copy its
// value in the case that we are currently suspending the main thread.
*mRunnableNameBuffer = nsThread::sMainThreadRunnableName;
}
void
ThreadStackHelper::TryAppendFrame(HangStack::Frame aFrame)
{
MOZ_RELEASE_ASSERT(mStackToFill);
// Record that we _want_ to use another frame entry. If this exceeds
// mMaxStackSize, we'll allocate more room on the next hang.
mDesiredStackSize += 1;
// Perform the append if we have enough space to do so.
if (mStackToFill->canAppendWithoutRealloc(1)) {
mStackToFill->infallibleAppend(aFrame);
}
}
void
ThreadStackHelper::CollectNativeLeafAddr(void* aAddr)
{
MOZ_RELEASE_ASSERT(mStackToFill);
TryAppendFrame(HangStack::Frame(reinterpret_cast<uintptr_t>(aAddr)));
}
void
ThreadStackHelper::CollectJitReturnAddr(void* aAddr)
{
MOZ_RELEASE_ASSERT(mStackToFill);
TryAppendFrame(HangStack::Frame("(jit frame)"));
}
void
ThreadStackHelper::CollectWasmFrame(const char* aLabel)
{
MOZ_RELEASE_ASSERT(mStackToFill);
// We don't want to collect WASM frames, as they are probably for content, so
// we just add a "(content wasm)" frame.
TryAppendFrame(HangStack::Frame("(wasm)"));
}
namespace {
bool
IsChromeJSScript(JSScript* aScript)
{
// May be called from another thread or inside a signal handler.
// We assume querying the script is safe but we must not manipulate it.
nsIScriptSecurityManager* const secman =
nsScriptSecurityManager::GetScriptSecurityManager();
NS_ENSURE_TRUE(secman, false);
JSPrincipals* const principals = JS_GetScriptPrincipals(aScript);
return secman->IsSystemPrincipal(nsJSPrincipals::get(principals));
}
// Get the full path after the URI scheme, if the URI matches the scheme.
// For example, GetFullPathForScheme("a://b/c/d/e", "a://") returns "b/c/d/e".
template <size_t LEN>
const char*
GetFullPathForScheme(const char* filename, const char (&scheme)[LEN]) {
// Account for the null terminator included in LEN.
if (!strncmp(filename, scheme, LEN - 1)) {
return filename + LEN - 1;
}
return nullptr;
}
// Get the full path after a URI component, if the URI contains the component.
// For example, GetPathAfterComponent("a://b/c/d/e", "/c/") returns "d/e".
template <size_t LEN>
const char*
GetPathAfterComponent(const char* filename, const char (&component)[LEN]) {
const char* found = nullptr;
const char* next = strstr(filename, component);
while (next) {
// Move 'found' to end of the component, after the separator '/'.
// 'LEN - 1' accounts for the null terminator included in LEN,
found = next + LEN - 1;
// Resume searching before the separator '/'.
next = strstr(found - 1, component);
}
return found;
}
} // namespace
void
ThreadStackHelper::CollectPseudoEntry(const js::ProfileEntry& aEntry)
{
// For non-js frames we just include the raw label.
if (!aEntry.isJs()) {
const char* label = aEntry.label();
TryAppendFrame(HangStack::Frame(label));
return;
}
if (!aEntry.script()) {
TryAppendFrame(HangStack::Frame("(profiling suppressed)"));
return;
}
if (!IsChromeJSScript(aEntry.script())) {
TryAppendFrame(HangStack::Frame("(content script)"));
return;
}
// Rather than using the profiler's dynamic string, we compute our own string.
// This is because we want to do some size-saving strategies, and throw out
// information which won't help us as much.
// XXX: We currently don't collect the function name which hung.
const char* filename = JS_GetScriptFilename(aEntry.script());
unsigned lineno = JS_PCToLineNumber(aEntry.script(), aEntry.pc());
// Some script names are in the form "foo -> bar -> baz".
// Here we find the origin of these redirected scripts.
const char* basename = GetPathAfterComponent(filename, " -> ");
if (basename) {
filename = basename;
}
// Strip chrome:// or resource:// off of the filename if present.
basename = GetFullPathForScheme(filename, "chrome://");
if (!basename) {
basename = GetFullPathForScheme(filename, "resource://");
}
if (!basename) {
// If we're in an add-on script, under the {profile}/extensions
// directory, extract the path after the /extensions/ part.
basename = GetPathAfterComponent(filename, "/extensions/");
}
if (!basename) {
// Only keep the file base name for paths outside the above formats.
basename = strrchr(filename, '/');
basename = basename ? basename + 1 : filename;
// Look for Windows path separator as well.
filename = strrchr(basename, '\\');
if (filename) {
basename = filename + 1;
}
}
mDesiredStackSize += 1;
char buffer[128]; // Enough to fit longest js file name from the tree
size_t len = SprintfLiteral(buffer, "%s:%u", basename, lineno);
if (len < sizeof(buffer)) {
mDesiredBufferSize += len + 1;
if (mStackToFill->canAppendWithoutRealloc(1) &&
mStackToFill->AvailableBufferSize() >= len + 1) {
mStackToFill->InfallibleAppendViaBuffer(buffer, len);
return;
}
}
if (mStackToFill->canAppendWithoutRealloc(1)) {
mStackToFill->infallibleAppend(HangStack::Frame("(chrome script)"));
}
}
} // namespace mozilla