Bug 1312883 - Use MozStackWalk to gather native stacks on Windows. r?jchen, gfritzsche draft
authorMike Conley <mconley@mozilla.com>
Thu, 16 Feb 2017 12:05:46 -0500
changeset 489269 36223ad0605823c940f43bbb4f82e773d9d69ba2
parent 489268 eb2003d42bb4f402af91df395c92ce7821f170fe
child 489270 d651cc98a1a41c7cb137eec65122ab226616fb58
push id46781
push usermconley@mozilla.com
push dateFri, 24 Feb 2017 15:47:14 +0000
reviewersjchen, gfritzsche
bugs1312883
milestone54.0a1
Bug 1312883 - Use MozStackWalk to gather native stacks on Windows. r?jchen, gfritzsche MozReview-Commit-ID: A2aZAL03FG2
dom/ipc/ProcessHangMonitor.cpp
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/ThreadHangStats.h
xpcom/threads/ThreadStackHelper.cpp
xpcom/threads/ThreadStackHelper.h
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -278,17 +278,17 @@ HangMonitorChild::HangMonitorChild(Proce
    mShutdownDone(false),
    mIPCOpen(true)
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mContext = danger::GetJSContext();
   mForcePaintMonitor =
     MakeUnique<mozilla::BackgroundHangMonitor>("Gecko_Child_ForcePaint",
                                                128, /* ms timeout for microhangs */
-                                               8192 /* ms timeout for permahangs */,
+                                               1024, /* ms timeout for permahangs */
                                                BackgroundHangMonitor::THREAD_PRIVATE);
 }
 
 HangMonitorChild::~HangMonitorChild()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance == this);
   mForcePaintMonitor = nullptr;
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -2074,19 +2074,28 @@ CreateJSHangHistogram(JSContext* cx, con
       !JS_DefineProperty(cx, ret, "stack", stack, JSPROP_ENUMERATE) ||
       !JS_DefineProperty(cx, ret, "histogram", time, JSPROP_ENUMERATE) ||
       (!hangAnnotations.empty() && // <-- Only define annotations when nonempty
         !JS_DefineProperty(cx, ret, "annotations", annotations, JSPROP_ENUMERATE))) {
     return nullptr;
   }
 
   if (!hang.GetNativeStack().empty()) {
-    JS::RootedObject native(cx, CreateJSHangStack(cx, hang.GetNativeStack()));
-    if (!native ||
-        !JS_DefineProperty(cx, ret, "nativeStack", native, JSPROP_ENUMERATE)) {
+    const Telemetry::HangStack& stack = hang.GetNativeStack();
+    const std::vector<uintptr_t>& frames = stack.GetNativeFrames();
+    Telemetry::ProcessedStack processed = Telemetry::GetStackAndModules(frames);
+
+    CombinedStacks singleStack;
+    singleStack.AddStack(processed);
+    JS::RootedObject fullReportObj(cx, CreateJSStackObject(cx, singleStack));
+    if (!fullReportObj) {
+      return nullptr;
+    }
+
+    if (!JS_DefineProperty(cx, ret, "nativeStack", fullReportObj, JSPROP_ENUMERATE)) {
       return nullptr;
     }
   }
   return ret;
 }
 
 static JSObject*
 CreateJSThreadHangStats(JSContext* cx, const Telemetry::ThreadHangStats& thread)
--- a/toolkit/components/telemetry/ThreadHangStats.h
+++ b/toolkit/components/telemetry/ThreadHangStats.h
@@ -46,27 +46,34 @@ public:
 };
 
 /* HangStack stores an array of const char pointers,
    with optional internal storage for strings. */
 class HangStack
 {
 public:
   static const size_t sMaxInlineStorage = 8;
+  // The maximum depth for the native stack frames that we might collect.
+  static const size_t sMaxNativeFrames = 25;
 
 private:
   typedef mozilla::Vector<const char*, sMaxInlineStorage> Impl;
   Impl mImpl;
 
   // Stack entries can either be a static const char*
   // or a pointer to within this buffer.
   mozilla::Vector<char, 0> mBuffer;
+  // When a native stack is gathered, this vector holds the raw program counter
+  // values that MozStackWalk will return to us after it walks the stack.
+  // When gathering the Telemetry payload, Telemetry will take care of mapping
+  // these program counters to proper addresses within modules.
+  std::vector<uintptr_t> mNativeFrames;
 
 public:
-  HangStack() { }
+  HangStack() {}
 
   HangStack(HangStack&& aOther)
     : mImpl(mozilla::Move(aOther.mImpl))
     , mBuffer(mozilla::Move(aOther.mBuffer))
   {
   }
 
   bool operator==(const HangStack& aOther) const {
@@ -106,16 +113,17 @@ public:
   void erase(const char** aEntry) { mImpl.erase(aEntry); }
   void erase(const char** aBegin, const char** aEnd) {
     mImpl.erase(aBegin, aEnd);
   }
 
   void clear() {
     mImpl.clear();
     mBuffer.clear();
+    mNativeFrames.clear();
   }
 
   bool IsInBuffer(const char* aEntry) const {
     return aEntry >= mBuffer.begin() && aEntry < mBuffer.end();
   }
 
   bool IsSameAsEntry(const char* aEntry, const char* aOther) const {
     // If the entry came from the buffer, we need to compare its content;
@@ -131,16 +139,31 @@ public:
     // aCapacity is the minimal capacity and Vector may make the actual
     // capacity larger, in which case we want to use up all the space.
     return mBuffer.reserve(aCapacity) &&
            mBuffer.reserve(mBuffer.capacity());
   }
 
   const char* InfallibleAppendViaBuffer(const char* aText, size_t aLength);
   const char* AppendViaBuffer(const char* aText, size_t aLength);
+
+  void EnsureNativeFrameCapacity(size_t aCapacity) {
+    mNativeFrames.reserve(aCapacity);
+  }
+
+  void AppendNativeFrame(uintptr_t aPc) {
+    MOZ_ASSERT(mNativeFrames.size() <= sMaxNativeFrames);
+    if (mNativeFrames.size() < sMaxNativeFrames) {
+      mNativeFrames.push_back(aPc);
+    }
+  }
+
+  const std::vector<uintptr_t>& GetNativeFrames() const {
+    return mNativeFrames;
+  }
 };
 
 /* A hang histogram consists of a stack associated with the
    hang, along with a time histogram of the hang times. */
 class HangHistogram : public TimeHistogram
 {
 private:
   static uint32_t GetHash(const HangStack& aStack);
--- a/xpcom/threads/ThreadStackHelper.cpp
+++ b/xpcom/threads/ThreadStackHelper.cpp
@@ -158,16 +158,22 @@ public:
   ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
   ~ScopedSetPtr() { mPtr = nullptr; }
 };
 } // namespace
 
 void
 ThreadStackHelper::GetStack(Stack& aStack)
 {
+  GetStackInternal(aStack, /* aAppendNativeStack */ false);
+}
+
+void
+ThreadStackHelper::GetStackInternal(Stack& aStack, bool aAppendNativeStack)
+{
   // Always run PrepareStackBuffer first to clear aStack
   if (!PrepareStackBuffer(aStack)) {
     // Skip and return empty aStack
     return;
   }
 
   ScopedSetPtr<Stack> stackPtr(mStackToFill, &aStack);
 
@@ -190,30 +196,47 @@ ThreadStackHelper::GetStack(Stack& aStac
   }
   MOZ_ALWAYS_TRUE(!::sem_wait(&mSem));
 
 #elif defined(XP_WIN)
   if (!mInitialized) {
     MOZ_ASSERT(false);
     return;
   }
+
+  if (aAppendNativeStack) {
+    aStack.EnsureNativeFrameCapacity(Telemetry::HangStack::sMaxNativeFrames);
+  }
+
   if (::SuspendThread(mThreadID) == DWORD(-1)) {
     MOZ_ASSERT(false);
     return;
   }
 
   // SuspendThread is asynchronous, so the thread may still be running. Use
   // GetThreadContext to ensure it's really suspended.
   // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
   CONTEXT context;
   context.ContextFlags = CONTEXT_CONTROL;
   if (::GetThreadContext(mThreadID, &context)) {
     FillStackBuffer();
   }
 
+  if (aAppendNativeStack) {
+    auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
+      Stack* stack = static_cast<Stack*>(aClosure);
+      stack->AppendNativeFrame(reinterpret_cast<uintptr_t>(aPC));
+    };
+
+    MozStackWalk(callback, /* skipFrames */ 0,
+                 /* maxFrames */ Telemetry::HangStack::sMaxNativeFrames,
+                 reinterpret_cast<void*>(&aStack),
+                 reinterpret_cast<uintptr_t>(mThreadID), nullptr);
+  }
+
   MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
 
 #elif defined(XP_MACOSX)
 # if defined(MOZ_VALGRIND) && defined(RUNNING_ON_VALGRIND)
   if (RUNNING_ON_VALGRIND) {
     /* thread_suspend and thread_resume sometimes hang runs on Valgrind,
        for unknown reasons.  So, just avoid them.  See bug 1100911. */
     return;
@@ -231,20 +254,17 @@ ThreadStackHelper::GetStack(Stack& aStac
 
 #endif
 }
 
 void
 ThreadStackHelper::GetNativeStack(Stack& aStack)
 {
 #ifdef MOZ_THREADSTACKHELPER_NATIVE
-  // Get pseudostack first and fill the thread context.
-  GetStack(aStack);
-
-  // TODO: walk the saved stack frames.
+  GetStackInternal(aStack, /* aAppendNativeStack */ true);
 #endif // MOZ_THREADSTACKHELPER_NATIVE
 }
 
 #ifdef XP_LINUX
 
 int ThreadStackHelper::sInitialized;
 int ThreadStackHelper::sFillStackSignum;
 
--- a/xpcom/threads/ThreadStackHelper.h
+++ b/xpcom/threads/ThreadStackHelper.h
@@ -104,16 +104,18 @@ public:
   /**
    * Retrieve the current native stack of the thread associated
    * with this ThreadStackHelper.
    *
    * @param aNativeStack Stack instance to be filled.
    */
   void GetNativeStack(Stack& aStack);
 
+private:
+  void GetStackInternal(Stack& aStack, bool aAppendNativeStack);
 #if defined(XP_LINUX)
 private:
   static int sInitialized;
   static int sFillStackSignum;
 
   static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext);
 
   sem_t mSem;