Bug 1016629 - d. Add and implement GetNativeStack method in ThreadStackHelper; r=snorp r=jseward
authorJim Chen <nchen@mozilla.com>
Mon, 28 Jul 2014 13:30:21 -0400
changeset 219320 a47ff1dae6d2c564e43f8263611cb3f8127dc5c9
parent 219319 8e34e09d49a12a4f660a54ab5bf87e8b124bcba5
child 219321 96de856b2c82c943167e6eb2d8959b4545241b10
push id583
push userbhearsum@mozilla.com
push dateMon, 24 Nov 2014 19:04:58 +0000
treeherdermozilla-release@c107e74250f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp, jseward
bugs1016629
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1016629 - d. Add and implement GetNativeStack method in ThreadStackHelper; r=snorp r=jseward
xpcom/threads/ThreadStackHelper.cpp
xpcom/threads/ThreadStackHelper.h
xpcom/threads/moz.build
--- a/xpcom/threads/ThreadStackHelper.cpp
+++ b/xpcom/threads/ThreadStackHelper.cpp
@@ -5,42 +5,74 @@
  * 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"
 #include "prprf.h"
+#include "shared-libraries.h"
 
 #include "js/OldDebugAPI.h"
 
 #include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Move.h"
+#include "mozilla/Scoped.h"
+
+#include "google_breakpad/processor/call_stack.h"
+#include "google_breakpad/processor/basic_source_line_resolver.h"
+#include "google_breakpad/processor/stack_frame_cpu.h"
+#include "processor/basic_code_module.h"
+#include "processor/basic_code_modules.h"
+
+#if defined(MOZ_THREADSTACKHELPER_X86)
+#include "processor/stackwalker_x86.h"
+#elif defined(MOZ_THREADSTACKHELPER_X64)
+#include "processor/stackwalker_amd64.h"
+#elif defined(MOZ_THREADSTACKHELPER_ARM)
+#include "processor/stackwalker_arm.h"
+#endif
 
 #include <string.h>
+#include <vector>
 
 #ifdef XP_LINUX
 #include <unistd.h>
 #include <sys/syscall.h>
 #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
 
+#if defined(MOZ_THREADSTACKHELPER_X86) || \
+    defined(MOZ_THREADSTACKHELPER_X64) || \
+    defined(MOZ_THREADSTACKHELPER_ARM)
+// On these architectures, the stack grows downwards (toward lower addresses).
+#define MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
+#else
+#error "Unsupported architecture"
+#endif
+
 namespace mozilla {
 
 void
 ThreadStackHelper::Startup()
 {
 #if defined(XP_LINUX)
   MOZ_ASSERT(NS_IsMainThread());
   if (!sInitialized) {
@@ -75,44 +107,60 @@ ThreadStackHelper::Shutdown()
 #endif
 }
 
 ThreadStackHelper::ThreadStackHelper()
   : mStackToFill(nullptr)
 #ifdef MOZ_THREADSTACKHELPER_PSEUDO
   , mPseudoStack(mozilla_get_pseudo_stack())
 #endif
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  , mContextToFill(nullptr)
+#endif
   , mMaxStackSize(Stack::sMaxInlineStorage)
   , mMaxBufferSize(0)
 {
 #if defined(XP_LINUX)
   MOZ_ALWAYS_TRUE(!::sem_init(&mSem, 0, 0));
   mThreadID = ::syscall(SYS_gettid);
 #elif defined(XP_WIN)
   mInitialized = !!::DuplicateHandle(
     ::GetCurrentProcess(), ::GetCurrentThread(),
     ::GetCurrentProcess(), &mThreadID,
     THREAD_SUSPEND_RESUME, FALSE, 0);
   MOZ_ASSERT(mInitialized);
 #elif defined(XP_MACOSX)
   mThreadID = mach_thread_self();
 #endif
+
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  GetThreadStackBase();
+#endif
 }
 
 ThreadStackHelper::~ThreadStackHelper()
 {
 #if defined(XP_LINUX)
   MOZ_ALWAYS_TRUE(!::sem_destroy(&mSem));
 #elif defined(XP_WIN)
   if (mInitialized) {
     MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID));
   }
 #endif
 }
 
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+void ThreadStackHelper::GetThreadStackBase()
+{
+  mThreadStackBase = 0;
+
+  // TODO get stack base for each platform
+}
+#endif // MOZ_THREADSTACKHELPER_NATIVE
+
 namespace {
 template <typename T>
 class ScopedSetPtr
 {
 private:
   T*& mPtr;
 public:
   ScopedSetPtr(T*& p, T* val) : mPtr(p) { mPtr = val; }
@@ -154,42 +202,231 @@ ThreadStackHelper::GetStack(Stack& aStac
   if (!mInitialized) {
     MOZ_ASSERT(false);
     return;
   }
   if (::SuspendThread(mThreadID) == DWORD(-1)) {
     MOZ_ASSERT(false);
     return;
   }
+
   FillStackBuffer();
+  FillThreadContext();
+
   MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
 
 #elif defined(XP_MACOSX)
   if (::thread_suspend(mThreadID) != KERN_SUCCESS) {
     MOZ_ASSERT(false);
     return;
   }
+
   FillStackBuffer();
+  FillThreadContext();
+
   MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS);
 
 #endif
 }
 
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+class ThreadStackHelper::CodeModulesProvider
+  : public google_breakpad::CodeModules {
+private:
+  typedef google_breakpad::CodeModule CodeModule;
+  typedef google_breakpad::BasicCodeModule BasicCodeModule;
+
+  const SharedLibraryInfo mLibs;
+  mutable ScopedDeletePtr<BasicCodeModule> mModule;
+
+public:
+  CodeModulesProvider() : mLibs(SharedLibraryInfo::GetInfoForSelf()) {}
+  virtual ~CodeModulesProvider() {}
+
+  virtual unsigned int module_count() const {
+    return mLibs.GetSize();
+  }
+
+  virtual const CodeModule* GetModuleForAddress(uint64_t address) const {
+    MOZ_CRASH("Not implemented");
+  }
+
+  virtual const CodeModule* GetMainModule() const {
+    return nullptr;
+  }
+
+  virtual const CodeModule* GetModuleAtSequence(unsigned int sequence) const {
+    MOZ_CRASH("Not implemented");
+  }
+
+  virtual const CodeModule* GetModuleAtIndex(unsigned int index) const {
+    const SharedLibrary& lib = mLibs.GetEntry(index);
+    mModule = new BasicCodeModule(lib.GetStart(), lib.GetEnd() - lib.GetStart(),
+                                  lib.GetName(), lib.GetBreakpadId(),
+                                  lib.GetName(), lib.GetBreakpadId(), "");
+    // Keep mModule valid until the next GetModuleAtIndex call.
+    return mModule;
+  }
+
+  virtual const CodeModules* Copy() const {
+    MOZ_CRASH("Not implemented");
+  }
+};
+
+class ThreadStackHelper::ThreadContext
+  : public google_breakpad::MemoryRegion {
+public:
+#if defined(MOZ_THREADSTACKHELPER_X86)
+  typedef MDRawContextX86 Context;
+#elif defined(MOZ_THREADSTACKHELPER_X64)
+  typedef MDRawContextAMD64 Context;
+#elif defined(MOZ_THREADSTACKHELPER_ARM)
+  typedef MDRawContextARM Context;
+#endif
+  // Limit copied stack to 4kB
+  static const size_t kMaxStackSize = 0x1000;
+  // Limit unwound stack to 32 frames
+  static const unsigned int kMaxStackFrames = 32;
+  // Whether this structure contains valid data
+  bool mValid;
+  // Processor context
+  Context mContext;
+  // Stack area
+  ScopedDeleteArray<uint8_t> mStack;
+  // Start of stack area
+  uintptr_t mStackBase;
+  // Size of stack area
+  size_t mStackSize;
+  // End of stack area
+  const void* mStackEnd;
+
+  ThreadContext() : mValid(false)
+                  , mStackBase(0)
+                  , mStackSize(0)
+                  , mStackEnd(nullptr) {}
+  virtual ~ThreadContext() {}
+
+  virtual uint64_t GetBase() const {
+    return uint64_t(mStackBase);
+  }
+  virtual uint32_t GetSize() const {
+    return mStackSize;
+  }
+  virtual bool GetMemoryAtAddress(uint64_t address, uint8_t*  value) const {
+    return GetMemoryAtAddressInternal(address, value);
+  }
+  virtual bool GetMemoryAtAddress(uint64_t address, uint16_t*  value) const {
+    return GetMemoryAtAddressInternal(address, value);
+  }
+  virtual bool GetMemoryAtAddress(uint64_t address, uint32_t*  value) const {
+    return GetMemoryAtAddressInternal(address, value);
+  }
+  virtual bool GetMemoryAtAddress(uint64_t address, uint64_t*  value) const {
+    return GetMemoryAtAddressInternal(address, value);
+  }
+
+private:
+  template <typename T>
+  bool GetMemoryAtAddressInternal(uint64_t address, T* value) const {
+    const intptr_t offset = intptr_t(address) - intptr_t(GetBase());
+    if (offset < 0 || uintptr_t(offset) > (GetSize() - sizeof(T))) {
+      return false;
+    }
+    *value = *reinterpret_cast<const T*>(&mStack[offset]);
+    return true;
+  }
+};
+#endif // MOZ_THREADSTACKHELPER_NATIVE
+
+void
+ThreadStackHelper::GetNativeStack(Stack& aStack)
+{
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  ThreadContext context;
+  context.mStack = new uint8_t[ThreadContext::kMaxStackSize];
+
+  ScopedSetPtr<ThreadContext> contextPtr(mContextToFill, &context);
+
+  // Get pseudostack first and fill the thread context.
+  GetStack(aStack);
+  NS_ENSURE_TRUE_VOID(context.mValid);
+
+  CodeModulesProvider modulesProvider;
+  google_breakpad::BasicCodeModules modules(&modulesProvider);
+  google_breakpad::BasicSourceLineResolver resolver;
+  google_breakpad::StackFrameSymbolizer symbolizer(nullptr, &resolver);
+
+#if defined(MOZ_THREADSTACKHELPER_X86)
+  google_breakpad::StackwalkerX86 stackWalker(
+    nullptr, &context.mContext, &context, &modules, &symbolizer);
+#elif defined(MOZ_THREADSTACKHELPER_X64)
+  google_breakpad::StackwalkerAMD64 stackWalker(
+    nullptr, &context.mContext, &context, &modules, &symbolizer);
+#elif defined(MOZ_THREADSTACKHELPER_ARM)
+  google_breakpad::StackwalkerARM stackWalker(
+    nullptr, &context.mContext, -1, &context, &modules, &symbolizer);
+#else
+  #error "Unsupported architecture"
+#endif
+
+  google_breakpad::CallStack callStack;
+  std::vector<const google_breakpad::CodeModule*> modules_without_symbols;
+
+  google_breakpad::Stackwalker::set_max_frames(ThreadContext::kMaxStackFrames);
+  google_breakpad::Stackwalker::
+    set_max_frames_scanned(ThreadContext::kMaxStackFrames);
+
+  NS_ENSURE_TRUE_VOID(stackWalker.Walk(&callStack, &modules_without_symbols));
+
+  const std::vector<google_breakpad::StackFrame*>& frames(*callStack.frames());
+  for (intptr_t i = frames.size() - 1; i >= 0; i--) {
+    const google_breakpad::StackFrame& frame = *frames[i];
+    if (!frame.module) {
+      continue;
+    }
+    const string& module = frame.module->code_file();
+#if defined(XP_LINUX) || defined(XP_MACOSX)
+    const char PATH_SEP = '/';
+#elif defined(XP_WIN)
+    const char PATH_SEP = '\\';
+#endif
+    const char* const module_basename = strrchr(module.c_str(), PATH_SEP);
+    const char* const module_name = module_basename ?
+                                    module_basename + 1 : module.c_str();
+
+    char buffer[0x100];
+    size_t len = 0;
+    if (!frame.function_name.empty()) {
+      len = PR_snprintf(buffer, sizeof(buffer), "%s:%s",
+                        module_name, frame.function_name.c_str());
+    } else {
+      len = PR_snprintf(buffer, sizeof(buffer), "%s:0x%p",
+                        module_name, (intptr_t)
+                        (frame.instruction - frame.module->base_address()));
+    }
+    if (len) {
+      aStack.AppendViaBuffer(buffer, len);
+    }
+  }
+#endif // MOZ_THREADSTACKHELPER_NATIVE
+}
+
 #ifdef XP_LINUX
 
 int ThreadStackHelper::sInitialized;
 int ThreadStackHelper::sFillStackSignum;
 
 void
 ThreadStackHelper::FillStackHandler(int aSignal, siginfo_t* aInfo,
                                     void* aContext)
 {
   ThreadStackHelper* const helper =
     reinterpret_cast<ThreadStackHelper*>(aInfo->si_value.sival_ptr);
   helper->FillStackBuffer();
+  helper->FillThreadContext(aContext);
   ::sem_post(&helper->mSem);
 }
 
 #endif // XP_LINUX
 
 bool
 ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
 {
@@ -308,16 +545,21 @@ ThreadStackHelper::FillStackBuffer()
        because we only want static labels in the hang stack. */
     if (entry->isCopyLabel()) {
       continue;
     }
     if (entry->isJs()) {
       prevLabel = AppendJSEntry(entry, availableBufferSize, prevLabel);
       continue;
     }
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+    if (mContextToFill) {
+      mContextToFill->mStackEnd = entry->stackAddress();
+    }
+#endif
     const char* const label = entry->label();
     if (mStackToFill->IsSameAsEntry(prevLabel, label)) {
       continue;
     }
     mStackToFill->infallibleAppend(label);
     prevLabel = label;
   }
 
@@ -328,9 +570,22 @@ ThreadStackHelper::FillStackBuffer()
   // availableBufferSize < 0 if we needed a larger buffer than we reserved.
   // Calculate a new reserve size for next time.
   if (availableBufferSize < 0) {
     mMaxBufferSize = reservedBufferSize - availableBufferSize;
   }
 #endif
 }
 
+void
+ThreadStackHelper::FillThreadContext(void* aContext)
+{
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  if (!mContextToFill) {
+    return;
+  }
+
+  // TODO fill context for each platform
+
+#endif // MOZ_THREADSTACKHELPER_NATIVE
+}
+
 } // namespace mozilla
--- a/xpcom/threads/ThreadStackHelper.h
+++ b/xpcom/threads/ThreadStackHelper.h
@@ -25,16 +25,30 @@
 
 // Support pseudostack on these platforms.
 #if defined(XP_LINUX) || defined(XP_WIN) || defined(XP_MACOSX)
 #  ifdef MOZ_ENABLE_PROFILER_SPS
 #    define MOZ_THREADSTACKHELPER_PSEUDO
 #  endif
 #endif
 
+#ifdef MOZ_THREADSTACKHELPER_PSEUDO
+#  define MOZ_THREADSTACKHELPER_NATIVE
+#  if defined(__i386__) || defined(_M_IX86)
+#    define MOZ_THREADSTACKHELPER_X86
+#  elif defined(__x86_64__) || defined(_M_X64)
+#    define MOZ_THREADSTACKHELPER_X64
+#  elif defined(__arm__) || defined(_M_ARM)
+#    define MOZ_THREADSTACKHELPER_ARM
+#  else
+     // Unsupported architecture
+#    undef MOZ_THREADSTACKHELPER_NATIVE
+#  endif
+#endif
+
 namespace mozilla {
 
 /**
  * ThreadStackHelper is used to retrieve the profiler pseudo-stack of a
  * thread, as an alternative of using the profiler to take a profile.
  * The target thread first declares an ThreadStackHelper instance;
  * then another thread can call ThreadStackHelper::GetStack to retrieve
  * the pseudo-stack of the target thread at that instant.
@@ -47,26 +61,37 @@ class ThreadStackHelper
 public:
   typedef Telemetry::HangStack Stack;
 
 private:
   Stack* mStackToFill;
 #ifdef MOZ_THREADSTACKHELPER_PSEUDO
   const PseudoStack* const mPseudoStack;
 #endif
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  class CodeModulesProvider;
+  class ThreadContext;
+  // Set to non-null if GetStack should get the thread context.
+  ThreadContext* mContextToFill;
+  intptr_t mThreadStackBase;
+#endif
   size_t mMaxStackSize;
   size_t mMaxBufferSize;
 
   bool PrepareStackBuffer(Stack& aStack);
   void FillStackBuffer();
+  void FillThreadContext(void* aContext = nullptr);
 #ifdef MOZ_THREADSTACKHELPER_PSEUDO
   const char* AppendJSEntry(const volatile StackEntry* aEntry,
                             intptr_t& aAvailableBufferSize,
                             const char* aPrevLabel);
 #endif
+#ifdef MOZ_THREADSTACKHELPER_NATIVE
+  void GetThreadStackBase();
+#endif
 
 public:
   /**
    * Initialize ThreadStackHelper. Must be called from main thread.
    */
   static void Startup();
   /**
    * Uninitialize ThreadStackHelper. Must be called from main thread.
@@ -83,16 +108,24 @@ public:
   /**
    * Retrieve the current pseudostack of the thread associated
    * with this ThreadStackHelper.
    *
    * @param aStack Stack instance to be filled.
    */
   void GetStack(Stack& aStack);
 
+  /**
+   * Retrieve the current native stack of the thread associated
+   * with this ThreadStackHelper.
+   *
+   * @param aNativeStack Stack instance to be filled.
+   */
+  void GetNativeStack(Stack& aStack);
+
 #if defined(XP_LINUX)
 private:
   static int sInitialized;
   static int sFillStackSignum;
 
   static void FillStackHandler(int aSignal, siginfo_t* aInfo, void* aContext);
 
   sem_t mSem;
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -50,15 +50,17 @@ UNIFIED_SOURCES += [
     'TimerThread.cpp',
 ]
 
 MSVC_ENABLE_PGO = True
 
 LOCAL_INCLUDES += [
     '../build',
     '/caps',
+    '/toolkit/crashreporter/google-breakpad/src',
+    '/tools/profiler',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 
 include('/ipc/chromium/chromium-config.mozbuild')