Bug 779291: Implement SPS stackwalk using the breakpad unwinder. r=bgirard,glandium
authorJulian Seward <jseward@acm.org>
Sun, 10 Mar 2013 23:00:23 +0100
changeset 135738 8b366545161d029c6f172d23d07b0a1c978c7065
parent 135737 71d1bd406c7ec99a921035c66f37030bf25aa45c
child 135739 bb7e29036271e2b428be5c558dc5efdc91031898
push idunknown
push userunknown
push dateunknown
reviewersbgirard, glandium
bugs779291, 850089, 850132
milestone22.0a1
Bug 779291: Implement SPS stackwalk using the breakpad unwinder. r=bgirard,glandium bug 850089 - fix SPS with disable-crashreporter. Patch by Ted Mielczarek <ted@mielczarek.org>, r=glandium Bug 850132 - SPS breakpad unwind (bug 779291) breaks Win64 builds with "Unsupported platform". Patch by Makoto Kato <m_kato@ga2.so-net.ne.jp>, r=ted
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/crashreporter/google-breakpad/src/common/Makefile.in
toolkit/crashreporter/google-breakpad/src/common/dwarf/Makefile.in
toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in
toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
toolkit/crashreporter/google-breakpad/src/processor/Makefile.in
toolkit/moz.build
toolkit/xre/Makefile.in
tools/profiler/Makefile.in
tools/profiler/PlatformMacros.h
tools/profiler/ProfileEntry2.cpp
tools/profiler/ProfileEntry2.h
tools/profiler/TableTicker.cpp
tools/profiler/TableTicker2.cpp
tools/profiler/UnwinderThread2.cpp
tools/profiler/UnwinderThread2.h
tools/profiler/local_debug_info_symbolizer.cc
tools/profiler/local_debug_info_symbolizer.h
tools/profiler/nsProfiler.cpp
tools/profiler/platform-linux.cc
tools/profiler/platform-macos.cc
tools/profiler/platform.h
tools/profiler/shim_mac_dump_syms.h
tools/profiler/shim_mac_dump_syms.mm
tools/profiler/sps_sampler.h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1349,17 +1349,17 @@ XPCJSRuntime::~XPCJSRuntime()
         JS_DestroyRuntime(mJSRuntime);
         JS_ShutDown();
 #ifdef DEBUG_shaver_off
         fprintf(stderr, "nJRSI: destroyed runtime %p\n", (void *)mJSRuntime);
 #endif
     }
 #ifdef MOZ_ENABLE_PROFILER_SPS
     // Tell the profiler that the runtime is gone
-    if (ProfileStack *stack = mozilla_profile_stack())
+    if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(nullptr);
 #endif
 
 #ifdef DEBUG
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
         NS_ASSERTION(!mScratchStrings[i].mInUse, "Uh, string wrapper still in use!");
     }
 #endif
@@ -2556,17 +2556,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
                               xpc::WrapperFactory::Rewrap,
                               xpc::WrapperFactory::WrapForSameCompartment,
                               xpc::WrapperFactory::PrepareForWrapping);
     js::SetPreserveWrapperCallback(mJSRuntime, PreserveWrapper);
 #ifdef MOZ_CRASHREPORTER
     JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
 #endif
 #ifdef MOZ_ENABLE_PROFILER_SPS
-    if (ProfileStack *stack = mozilla_profile_stack())
+    if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(mJSRuntime);
 #endif
     JS_SetAccumulateTelemetryCallback(mJSRuntime, AccumulateTelemetryCallback);
     js::SetActivityCallback(mJSRuntime, ActivityCallback, this);
 
     // The JS engine needs to keep the source code around in order to implement
     // Function.prototype.toSource(). It'd be nice to not have to do this for
     // chrome code and simply stub out requests for source on it. Life is not so
--- a/toolkit/crashreporter/google-breakpad/src/common/Makefile.in
+++ b/toolkit/crashreporter/google-breakpad/src/common/Makefile.in
@@ -7,18 +7,20 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= breakpad_common
 LIBRARY_NAME	= breakpad_common_s
 ifneq (WINNT,$(OS_TARGET))
+ifdef MOZ_CRASHREPORTER
 HOST_LIBRARY_NAME = host_breakpad_common_s
 endif
+endif
 
 LOCAL_INCLUDES 	= -I$(srcdir)/..
 
 CPPSRCS	= \
   string_conversion.cc \
   module.cc \
   unique_string.cc \
   $(NULL)
@@ -29,29 +31,31 @@ CPPSRCS += \
   dwarf/bytereader.cc \
   dwarf_cfi_to_module.cc \
   dwarf/dwarf2reader.cc \
   dwarf_cu_to_module.cc \
   language.cc \
   dwarf/dwarf2diehandler.cc \
   dwarf_line_to_module.cc \
   $(NULL)
+ifdef MOZ_CRASHREPORTER
 HOST_CPPSRCS = \
   string_conversion.cc \
   module.cc \
   unique_string.cc \
   md5.cc \
   dwarf_cfi_to_module.cc \
   dwarf_cu_to_module.cc \
   language.cc \
   stabs_to_module.cc \
   stabs_reader.cc \
   dwarf_line_to_module.cc \
   $(NULL)
 endif
+endif
 
 ifeq ($(OS_ARCH),Linux)
 CPPSRCS += \
   linux/dump_symbols.cc \
   linux/elf_symbols_to_module.cc
 endif
 
 ifeq ($(OS_ARCH),Darwin)
@@ -94,18 +98,20 @@ HOST_CXXFLAGS += -DHAVE_A_OUT_H
 OS_CXXFLAGS += -DHAVE_A_OUT_H
 endif
 ifeq (Darwin,$(OS_ARCH))
 HOST_CXXFLAGS += -DHAVE_MACH_O_NLIST_H
 OS_CXXFLAGS += -DHAVE_MACH_O_NLIST_H
 endif
 
 ifneq (WINNT,$(OS_TARGET))
+ifdef MOZ_CRASHREPORTER
 HOST_CSRCS = $(CSRCS)
 endif
+endif
 
 # need static lib
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 ifneq (WINNT,$(OS_TARGET))
 # Headers from this directory are included as "common/header.h". Having
--- a/toolkit/crashreporter/google-breakpad/src/common/dwarf/Makefile.in
+++ b/toolkit/crashreporter/google-breakpad/src/common/dwarf/Makefile.in
@@ -5,26 +5,29 @@
 DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= breakpad_dwarf
+
+ifdef MOZ_CRASHREPORTER
 HOST_LIBRARY_NAME = host_breakpad_dwarf_s
 
 LOCAL_INCLUDES 	= -I$(srcdir)/../..
 
 HOST_CPPSRCS = \
   bytereader.cc \
   dwarf2diehandler.cc \
   dwarf2reader.cc \
   functioninfo.cc \
   $(NULL)
+endif
 
 # This code is only compiled for build-time tools,
 # so enabling RTTI should be fine.
 HOST_CXXFLAGS := -funsigned-char $(filter-out -fno-rtti,$(HOST_CXXFLAGS))
 
 # need static lib
 FORCE_STATIC_LIB = 1
 
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in
@@ -6,17 +6,19 @@ DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= breakpad_linux_common
 LIBRARY_NAME	= breakpad_linux_common_s
+ifdef MOZ_CRASHREPORTER
 HOST_LIBRARY_NAME = host_breakpad_linux_common_s
+endif
 
 LOCAL_INCLUDES = \
   -I$(topsrcdir)/toolkit/crashreporter/google-breakpad/src \
   $(NULL)
 
 CPPSRCS	= \
   elfutils.cc \
   file_id.cc \
@@ -33,25 +35,27 @@ TARGET_LOCAL_INCLUDES += -I$(topsrcdir)/
 endif
 
 ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 DEFINES += -DELFSIZE=32
 endif
 
 DEFINES += -DNO_STABS_SUPPORT
 
+ifdef MOZ_CRASHREPORTER
 HOST_CPPSRCS = \
   dump_symbols.cc \
   elf_symbols_to_module.cc \
   elfutils.cc \
   file_id.cc \
   guid_creator.cc \
   linux_libc_support.cc \
   memory_mapped_file.cc \
   $(NULL)
+endif
 
 # need static lib
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 # See https://bugzilla.mozilla.org/show_bug.cgi?id=741348#c11
 file_id.$(OBJ_SUFFIX): STL_FLAGS=
--- a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
+++ b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc
@@ -621,17 +621,17 @@ bool LoadSymbols(const string& obj_file,
       bool result =
           LoadDwarfCFI<ElfClass>(obj_file, elf_header, ".eh_frame",
                                  eh_frame_section, true,
                                  got_section, text_section, big_endian, module);
       found_usable_info = found_usable_info || result;
     }
   }
 
-  if (!found_debug_info_section) {
+  if (!found_debug_info_section && symbol_data != ONLY_CFI) {
     fprintf(stderr, "%s: file contains no debugging information"
             " (no \".stab\" or \".debug_info\" sections)\n",
             obj_file.c_str());
 
     // Failed, but maybe there's a .gnu_debuglink section?
     if (read_gnu_debug_link) {
       const Shdr* gnu_debuglink_section
           = FindElfSectionByName<ElfClass>(".gnu_debuglink", SHT_PROGBITS,
--- a/toolkit/crashreporter/google-breakpad/src/processor/Makefile.in
+++ b/toolkit/crashreporter/google-breakpad/src/processor/Makefile.in
@@ -6,17 +6,19 @@ DEPTH		= @DEPTH@
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= breakpad_sps_common
 LIBRARY_NAME	= breakpad_sps_common_s
+ifdef MOZ_CRASHREPORTER
 HOST_LIBRARY_NAME = host_breakpad_sps_common_s
+endif
 
 LOCAL_INCLUDES 	= -I$(srcdir)/../.. -I$(srcdir)/..
 
 CPPSRCS	= \
   stackwalker.cc \
   stackwalker_amd64.cc \
   stackwalker_arm.cc \
   stackwalker_ppc.cc \
@@ -29,17 +31,19 @@ CPPSRCS	= \
   call_stack.cc \
   logging.cc \
   pathname_stripper.cc \
   tokenize.cc \
   source_line_resolver_base.cc \
   stack_frame_symbolizer.cc \
   $(NULL)
 
+ifdef MOZ_CRASHREPORTER
 HOST_CPPSRCS = \
   logging.cc \
   pathname_stripper.cc \
   $(NULL)
+endif
 
 # need static lib
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/moz.build
+++ b/toolkit/moz.build
@@ -36,8 +36,19 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
     PARALLEL_DIRS += ['system/osxproxy']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     PARALLEL_DIRS += ['system/windowsproxy']
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     PARALLEL_DIRS += ['system/androidproxy']
 
 if CONFIG['MOZ_CRASHREPORTER']:
     PARALLEL_DIRS += ['crashreporter']
+elif CONFIG['MOZ_ENABLE_PROFILER_SPS']:
+    # Profiler requires some crashreporter code,
+    # so build it even if crashreporter is disabled.
+    PARALLEL_DIRS += [
+    'crashreporter/google-breakpad/src/common',
+    'crashreporter/google-breakpad/src/processor',
+    ]
+    if CONFIG['OS_ARCH'] == 'Darwin':
+        PARALLEL_DIRS += ['crashreporter/google-breakpad/src/common/mac']
+    elif CONFIG['OS_ARCH'] == 'Linux':
+        PARALLEL_DIRS += ['crashreporter/google-breakpad/src/common/linux']
--- a/toolkit/xre/Makefile.in
+++ b/toolkit/xre/Makefile.in
@@ -122,51 +122,60 @@ SHARED_LIBRARY_LIBS += \
 endif
 endif
 
 ifdef MOZ_ENABLE_XREMOTE
 SHARED_LIBRARY_LIBS += $(DEPTH)/widget/xremoteclient/$(LIB_PREFIX)xremote_client_s.$(LIB_SUFFIX)
 LOCAL_INCLUDES += -I$(topsrcdir)/widget/xremoteclient
 endif
 
-ifdef MOZ_CRASHREPORTER
-SHARED_LIBRARY_LIBS += $(DEPTH)/toolkit/crashreporter/$(LIB_PREFIX)exception_handler_s.$(LIB_SUFFIX)
-ifeq ($(OS_ARCH),WINNT)
-SHARED_LIBRARY_LIBS += \
-  $(DEPTH)/toolkit/crashreporter/breakpad-windows-libxul/$(LIB_PREFIX)google_breakpad_libxul_s.$(LIB_SUFFIX)
-ifdef MOZ_ENABLE_PROFILER_SPS
+ifneq (,$(MOZ_CRASHREPORTER)$(MOZ_ENABLE_PROFILER_SPS))
 SHARED_LIBRARY_LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/$(LIB_PREFIX)breakpad_common_s.$(LIB_SUFFIX) \
   $(NULL)
+
+ifeq ($(OS_ARCH),Darwin)
+SHARED_LIBRARY_LIBS += \
+  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/mac/$(LIB_PREFIX)breakpad_mac_common_s.$(LIB_SUFFIX)
+  $(NULL)
+endif
+ifeq ($(OS_ARCH),Linux)
+SHARED_LIBRARY_LIBS += \
+  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/linux/$(LIB_PREFIX)breakpad_linux_common_s.$(LIB_SUFFIX) \
+  $(NULL)
 endif
 endif
 
 ifdef MOZ_ENABLE_PROFILER_SPS
 SHARED_LIBRARY_LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/processor/$(LIB_PREFIX)breakpad_sps_common_s.$(LIB_SUFFIX) \
   $(NULL)
 endif
 
+ifdef MOZ_CRASHREPORTER
+SHARED_LIBRARY_LIBS += $(DEPTH)/toolkit/crashreporter/$(LIB_PREFIX)exception_handler_s.$(LIB_SUFFIX)
+ifeq ($(OS_ARCH),WINNT)
+SHARED_LIBRARY_LIBS += \
+  $(DEPTH)/toolkit/crashreporter/breakpad-windows-libxul/$(LIB_PREFIX)google_breakpad_libxul_s.$(LIB_SUFFIX)
+endif
+
 ifeq ($(OS_ARCH),Darwin)
 SHARED_LIBRARY_LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/$(LIB_PREFIX)minidump_file_writer_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/$(LIB_PREFIX)crash_generation_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/mac/handler/$(LIB_PREFIX)exception_handler_s.$(LIB_SUFFIX) \
-  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/$(LIB_PREFIX)breakpad_common_s.$(LIB_SUFFIX) \
-  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/mac/$(LIB_PREFIX)breakpad_mac_common_s.$(LIB_SUFFIX)
+  $(NULL)
 endif
 
 ifeq ($(OS_ARCH),Linux)
 SHARED_LIBRARY_LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/$(LIB_PREFIX)crash_generation_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/linux/handler/$(LIB_PREFIX)exception_handler_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/$(LIB_PREFIX)minidump_writer_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/$(LIB_PREFIX)minidump_file_writer_s.$(LIB_SUFFIX) \
-  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/$(LIB_PREFIX)breakpad_common_s.$(LIB_SUFFIX) \
-  $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/linux/$(LIB_PREFIX)breakpad_linux_common_s.$(LIB_SUFFIX) \
   $(NULL)
 endif
 
 ifeq ($(OS_ARCH),SunOS)
 SHARED_LIBRARY_LIBS += \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/$(LIB_PREFIX)exception_handler_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/client/$(LIB_PREFIX)minidump_file_writer_s.$(LIB_SUFFIX) \
   $(DEPTH)/toolkit/crashreporter/google-breakpad/src/common/$(LIB_PREFIX)breakpad_common_s.$(LIB_SUFFIX) \
--- a/tools/profiler/Makefile.in
+++ b/tools/profiler/Makefile.in
@@ -20,16 +20,17 @@ ifdef MOZ_ENABLE_PROFILER_SPS
 EXPORTS += \
   sps_sampler.h \
   shared-libraries.h \
   $(NULL)
 
 LOCAL_INCLUDES += \
   -I$(topsrcdir)/mozglue/linker \
   -I$(topsrcdir)/ipc/chromium/src \
+  -I$(topsrcdir)/toolkit/crashreporter/google-breakpad/src \
   $(NULL)
 
 ifneq (,$(filter armeabi,$(ANDROID_CPU_ARCH)))
 DEFINES += -DARCH_ARMV6
 endif
 
 MODULE          = profiler
 MODULE_NAME     = nsProfilerModule
@@ -40,16 +41,20 @@ IS_COMPONENT    = 1
 ifndef _MSC_VER
 FAIL_ON_WARNINGS = 1
 endif # !_MSC_VER
 
 CPPSRCS		= \
   nsProfilerFactory.cpp \
   nsProfiler.cpp \
   TableTicker.cpp \
+  TableTicker2.cpp \
+  UnwinderThread2.cpp \
+  ProfileEntry2.cpp \
+  local_debug_info_symbolizer.cc \
   JSObjectBuilder.cpp \
   JSCustomObjectBuilder.cpp \
   $(NULL)
 
 XPIDLSRCS = \
   nsIProfiler.idl \
   $(NULL)
 
@@ -64,16 +69,18 @@ CPPSRCS += \
   $(NULL)
 endif
 
 ifeq ($(OS_TARGET),Darwin)
 CPPSRCS += \
   shared-libraries-macos.cc \
   platform-macos.cc \
   $(NULL)
+CMMSRCS += \
+  shim_mac_dump_syms.mm
 endif
 
 ifeq ($(OS_TARGET),WINNT)
 CPPSRCS += \
   shared-libraries-win32.cc \
   platform-win32.cc \
   $(NULL)
 endif
new file mode 100644
--- /dev/null
+++ b/tools/profiler/PlatformMacros.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef SPS_PLATFORM_MACROS_H
+#define SPS_PLATFORM_MACROS_H
+
+/* Define platform selection macros in a consistent way.  Don't add
+   anything else to this file, so it can remain freestanding.  The
+   primary factorisation is on (ARCH,OS) pairs ("PLATforms") but ARCH_
+   and OS_ macros are defined too, since they are sometimes
+   convenient. */
+
+#undef SPS_PLAT_arm_android
+#undef SPS_PLAT_amd64_linux
+#undef SPS_PLAT_x86_linux
+#undef SPS_PLAT_amd64_darwin
+#undef SPS_PLAT_x86_darwin
+#undef SPS_PLAT_x86_windows
+#undef SPS_PLAT_amd64_windows
+
+#undef SPS_ARCH_arm
+#undef SPS_ARCH_x86
+#undef SPS_ARCH_amd64
+
+#undef SPS_OS_android
+#undef SPS_OS_linux
+#undef SPS_OS_darwin
+#undef SPS_OS_windows
+
+#if defined(__linux__) && defined(__x86_64__)
+#  define SPS_PLAT_amd64_linux 1
+#  define SPS_ARCH_amd64 1
+#  define SPS_OS_linux 1
+
+#elif defined(__ANDROID__) && defined(__arm__)
+#  define SPS_PLAT_arm_android 1
+#  define SPS_ARCH_arm 1
+#  define SPS_OS_android 1
+
+#elif defined(__ANDROID__) && defined(__i386__)
+#  define SPS_PLAT_x86_android 1
+#  define SPS_ARCH_x86 1
+#  define SPS_OS_android 1
+
+#elif defined(__linux__) && defined(__i386__)
+#  define SPS_PLAT_x86_linux 1
+#  define SPS_ARCH_x86 1
+#  define SPS_OS_linux 1
+
+#elif defined(__APPLE__) && defined(__x86_64__)
+#  define SPS_PLAT_amd64_darwin 1
+#  define SPS_ARCH_amd64 1
+#  define SPS_OS_darwin 1
+
+#elif defined(__APPLE__) && defined(__i386__)
+#  define SPS_PLAT_x86_darwin 1
+#  define SPS_ARCH_x86 1
+#  define SPS_OS_darwin 1
+
+#elif defined(_MSC_VER) && defined(_M_IX86)
+#  define SPS_PLAT_x86_windows 1
+#  define SPS_ARCH_x86 1
+#  define SPS_OS_windows 1
+
+#elif defined(_MSC_VER) && defined(_M_X64)
+#  define SPS_PLAT_amd64_windows 1
+#  define SPS_ARCH_amd64 1
+#  define SPS_OS_windows 1
+
+#else
+#  error "Unsupported platform"
+#endif
+
+#endif /* ndef SPS_PLATFORM_MACROS_H */
new file mode 100644
--- /dev/null
+++ b/tools/profiler/ProfileEntry2.cpp
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 <iostream>
+#include "sps_sampler.h"
+#include "platform.h"
+#include "nsThreadUtils.h"
+
+// JSON
+#include "JSObjectBuilder.h"
+
+// Self
+#include "ProfileEntry2.h"
+
+#if _MSC_VER
+ #define snprintf _snprintf
+#endif
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN ProfileEntry2
+
+ProfileEntry2::ProfileEntry2()
+  : mTagData(NULL)
+  , mTagName(0)
+{ }
+
+// aTagData must not need release (i.e. be a string from the text segment)
+ProfileEntry2::ProfileEntry2(char aTagName, const char *aTagData)
+  : mTagData(aTagData)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, void *aTagPtr)
+  : mTagPtr(aTagPtr)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, double aTagFloat)
+  : mTagFloat(aTagFloat)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, uintptr_t aTagOffset)
+  : mTagOffset(aTagOffset)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, Address aTagAddress)
+  : mTagAddress(aTagAddress)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, int aTagLine)
+  : mTagLine(aTagLine)
+  , mTagName(aTagName)
+{ }
+
+ProfileEntry2::ProfileEntry2(char aTagName, char aTagChar)
+  : mTagChar(aTagChar)
+  , mTagName(aTagName)
+{ }
+
+bool ProfileEntry2::is_ent_hint(char hintChar) {
+  return mTagName == 'h' && mTagChar == hintChar;
+}
+
+bool ProfileEntry2::is_ent_hint() {
+  return mTagName == 'h';
+}
+
+bool ProfileEntry2::is_ent(char tagChar) {
+  return mTagName == tagChar;
+}
+
+void* ProfileEntry2::get_tagPtr() {
+  // No consistency checking.  Oh well.
+  return mTagPtr;
+}
+
+void ProfileEntry2::log()
+{
+  // There is no compiler enforced mapping between tag chars
+  // and union variant fields, so the following was derived
+  // by looking through all the use points of TableTicker.cpp.
+  //   mTagData   (const char*)  m,c,s
+  //   mTagPtr    (void*)        d,l,L, S(start-of-stack)
+  //   mTagLine   (int)          n,f
+  //   mTagChar   (char)         h
+  //   mTagFloat  (double)       r,t
+  switch (mTagName) {
+    case 'm': case 'c': case 's':
+      LOGF("%c \"%s\"", mTagName, mTagData); break;
+    case 'd': case 'l': case 'L': case 'S':
+      LOGF("%c %p", mTagName, mTagPtr); break;
+    case 'n': case 'f':
+      LOGF("%c %d", mTagName, mTagLine); break;
+    case 'h':
+      LOGF("%c \'%c\'", mTagName, mTagChar); break;
+    case 'r': case 't':
+      LOGF("%c %f", mTagName, mTagFloat); break;
+    default:
+      LOGF("'%c' unknown_tag", mTagName); break;
+  }
+}
+
+// END ProfileEntry2
+////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN ThreadProfile2
+
+#define PROFILE_MAX_ENTRY  100000
+#define DYNAMIC_MAX_STRING 512
+
+ThreadProfile2::ThreadProfile2(int aEntrySize, PseudoStack *aStack)
+  : mWritePos(0)
+  , mLastFlushPos(0)
+  , mReadPos(0)
+  , mEntrySize(aEntrySize)
+  , mPseudoStack(aStack)
+  , mMutex("ThreadProfile2::mMutex")
+{
+  mEntries = new ProfileEntry2[mEntrySize];
+}
+
+ThreadProfile2::~ThreadProfile2()
+{
+  delete[] mEntries;
+}
+
+void ThreadProfile2::addTag(ProfileEntry2 aTag)
+{
+  // Called from signal, call only reentrant functions
+  mEntries[mWritePos] = aTag;
+  mWritePos = (mWritePos + 1) % mEntrySize;
+  if (mWritePos == mReadPos) {
+    // Keep one slot open
+    mEntries[mReadPos] = ProfileEntry2();
+    mReadPos = (mReadPos + 1) % mEntrySize;
+  }
+  // we also need to move the flush pos to ensure we
+  // do not pass it
+  if (mWritePos == mLastFlushPos) {
+    mLastFlushPos = (mLastFlushPos + 1) % mEntrySize;
+  }
+}
+
+// flush the new entries
+void ThreadProfile2::flush()
+{
+  mLastFlushPos = mWritePos;
+}
+
+// discards all of the entries since the last flush()
+// NOTE: that if mWritePos happens to wrap around past
+// mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries
+//
+// r = mReadPos
+// w = mWritePos
+// f = mLastFlushPos
+//
+//     r          f    w
+// |-----------------------------|
+// |   abcdefghijklmnopq         | -> 'abcdefghijklmnopq'
+// |-----------------------------|
+//
+//
+// mWritePos and mReadPos have passed mLastFlushPos
+//                      f
+//                    w r
+// |-----------------------------|
+// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz|
+// |-----------------------------|
+//                       w
+//                       r
+// |-----------------------------|
+// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> ''
+// |-----------------------------|
+//
+//
+// mWritePos will end up the same as mReadPos
+//                r
+//              w f
+// |-----------------------------|
+// |ABCDEFGHIJKLMklmnopqrstuvwxyz|
+// |-----------------------------|
+//                r
+//                w
+// |-----------------------------|
+// |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> ''
+// |-----------------------------|
+//
+//
+// mWritePos has moved past mReadPos
+//      w r       f
+// |-----------------------------|
+// |ABCDEFdefghijklmnopqrstuvwxyz|
+// |-----------------------------|
+//        r       w
+// |-----------------------------|
+// |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl'
+// |-----------------------------|
+
+void ThreadProfile2::erase()
+{
+  mWritePos = mLastFlushPos;
+}
+
+char* ThreadProfile2::processDynamicTag(int readPos,
+                                       int* tagsConsumed, char* tagBuff)
+{
+  int readAheadPos = (readPos + 1) % mEntrySize;
+  int tagBuffPos = 0;
+
+  // Read the string stored in mTagData until the null character is seen
+  bool seenNullByte = false;
+  while (readAheadPos != mLastFlushPos && !seenNullByte) {
+    (*tagsConsumed)++;
+    ProfileEntry2 readAheadEntry = mEntries[readAheadPos];
+    for (size_t pos = 0; pos < sizeof(void*); pos++) {
+      tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos];
+      if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) {
+        seenNullByte = true;
+        break;
+      }
+      tagBuffPos++;
+    }
+    if (!seenNullByte)
+      readAheadPos = (readAheadPos + 1) % mEntrySize;
+  }
+  return tagBuff;
+}
+
+JSCustomObject* ThreadProfile2::ToJSObject(JSContext *aCx)
+{
+  JSObjectBuilder b(aCx);
+
+  JSCustomObject *profile = b.CreateObject();
+  JSCustomArray *samples = b.CreateArray();
+  b.DefineProperty(profile, "samples", samples);
+
+  JSCustomObject *sample = NULL;
+  JSCustomArray *frames = NULL;
+
+  int readPos = mReadPos;
+  while (readPos != mLastFlushPos) {
+    // Number of tag consumed
+    int incBy = 1;
+    ProfileEntry2 entry = mEntries[readPos];
+
+    // Read ahead to the next tag, if it's a 'd' tag process it now
+    const char* tagStringData = entry.mTagData;
+    int readAheadPos = (readPos + 1) % mEntrySize;
+    char tagBuff[DYNAMIC_MAX_STRING];
+    // Make sure the string is always null terminated if it fills up
+    // DYNAMIC_MAX_STRING-2
+    tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
+
+    if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') {
+      tagStringData = processDynamicTag(readPos, &incBy, tagBuff);
+    }
+
+    switch (entry.mTagName) {
+      case 's':
+        sample = b.CreateObject();
+        b.DefineProperty(sample, "name", tagStringData);
+        frames = b.CreateArray();
+        b.DefineProperty(sample, "frames", frames);
+        b.ArrayPush(samples, sample);
+        break;
+      case 'r':
+        {
+          if (sample) {
+            b.DefineProperty(sample, "responsiveness", entry.mTagFloat);
+          }
+        }
+        break;
+      case 'f':
+        {
+          if (sample) {
+            b.DefineProperty(sample, "frameNumber", entry.mTagLine);
+          }
+        }
+        break;
+      case 't':
+        {
+          if (sample) {
+            b.DefineProperty(sample, "time", entry.mTagFloat);
+          }
+        }
+        break;
+      case 'c':
+      case 'l':
+        {
+          if (sample) {
+            JSCustomObject *frame = b.CreateObject();
+            if (entry.mTagName == 'l') {
+              // Bug 753041
+              // We need a double cast here to tell GCC that we don't want to sign
+              // extend 32-bit addresses starting with 0xFXXXXXX.
+              unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
+              snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc);
+              b.DefineProperty(frame, "location", tagBuff);
+            } else {
+              b.DefineProperty(frame, "location", tagStringData);
+              readAheadPos = (readPos + incBy) % mEntrySize;
+              if (readAheadPos != mLastFlushPos &&
+                  mEntries[readAheadPos].mTagName == 'n') {
+                b.DefineProperty(frame, "line",
+                                 mEntries[readAheadPos].mTagLine);
+                incBy++;
+              }
+            }
+            b.ArrayPush(frames, frame);
+          }
+        }
+    }
+    readPos = (readPos + incBy) % mEntrySize;
+  }
+
+  return profile;
+}
+
+PseudoStack* ThreadProfile2::GetPseudoStack()
+{
+  return mPseudoStack;
+}
+
+mozilla::Mutex* ThreadProfile2::GetMutex()
+{
+  return &mMutex;
+}
+
+// END ThreadProfile2
+////////////////////////////////////////////////////////////////////////
new file mode 100644
--- /dev/null
+++ b/tools/profiler/ProfileEntry2.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MOZ_PROFILE_ENTRY_H
+#define MOZ_PROFILE_ENTRY_H
+
+#include "mozilla/Mutex.h"
+
+class ThreadProfile2;
+
+class ProfileEntry2
+{
+public:
+  ProfileEntry2();
+
+  // aTagData must not need release (i.e. be a string from the text segment)
+  ProfileEntry2(char aTagName, const char *aTagData);
+  ProfileEntry2(char aTagName, void *aTagPtr);
+  ProfileEntry2(char aTagName, double aTagFloat);
+  ProfileEntry2(char aTagName, uintptr_t aTagOffset);
+  ProfileEntry2(char aTagName, Address aTagAddress);
+  ProfileEntry2(char aTagName, int aTagLine);
+  ProfileEntry2(char aTagName, char aTagChar);
+  friend std::ostream& operator<<(std::ostream& stream, const ProfileEntry2& entry);
+  bool is_ent_hint(char hintChar);
+  bool is_ent_hint();
+  bool is_ent(char tagName);
+  void* get_tagPtr();
+  void log();
+
+private:
+  friend class ThreadProfile2;
+  union {
+    const char* mTagData;
+    char        mTagChars[sizeof(void*)];
+    void*       mTagPtr;
+    double      mTagFloat;
+    Address     mTagAddress;
+    uintptr_t   mTagOffset;
+    int         mTagLine;
+    char        mTagChar;
+  };
+  char mTagName;
+};
+
+
+class ThreadProfile2
+{
+public:
+  ThreadProfile2(int aEntrySize, PseudoStack *aStack);
+  ~ThreadProfile2();
+  void addTag(ProfileEntry2 aTag);
+  void flush();
+  void erase();
+  char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff);
+  friend std::ostream& operator<<(std::ostream& stream,
+                                  const ThreadProfile2& profile);
+  JSCustomObject *ToJSObject(JSContext *aCx);
+  PseudoStack* GetPseudoStack();
+  mozilla::Mutex* GetMutex();
+private:
+  // Circular buffer 'Keep One Slot Open' implementation
+  // for simplicity
+  ProfileEntry2* mEntries;
+  int            mWritePos; // points to the next entry we will write to
+  int            mLastFlushPos; // points to the next entry since the last flush()
+  int            mReadPos;  // points to the next entry we will read to
+  int            mEntrySize;
+  PseudoStack*   mPseudoStack;
+  mozilla::Mutex mMutex;
+};
+
+#endif /* ndef MOZ_PROFILE_ENTRY_H */
--- a/tools/profiler/TableTicker.cpp
+++ b/tools/profiler/TableTicker.cpp
@@ -26,16 +26,17 @@
 #include "nsIHttpProtocolHandler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsIXULAppInfo.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
+#include "PlatformMacros.h"
 
 // JS
 #include "jsdbgapi.h"
 
 // we eventually want to make this runtime switchable
 #if defined(MOZ_PROFILING) && (defined(XP_UNIX) && !defined(XP_MACOSX))
  #ifndef ANDROID
   #define USE_BACKTRACE
@@ -77,23 +78,17 @@ using namespace mozilla;
 #endif
 
 #if _MSC_VER
  #define snprintf _snprintf
 #endif
 
 static const int DYNAMIC_MAX_STRING = 512;
 
-mozilla::ThreadLocal<ProfileStack *> tlsStack;
-mozilla::ThreadLocal<TableTicker *> tlsTicker;
-// We need to track whether we've been initialized otherwise
-// we end up using tlsStack without initializing it.
-// Because tlsStack is totally opaque to us we can't reuse
-// it as the flag itself.
-bool stack_key_initialized;
+static mozilla::ThreadLocal<TableTicker *> tlsTicker;
 
 TimeStamp sLastTracerEvent;
 int sFrameNumber = 0;
 int sLastFrameNumber = 0;
 
 class ThreadProfile;
 
 class ProfileEntry
@@ -150,17 +145,17 @@ public:
 };
 
 typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData);
 
 #define PROFILE_MAX_ENTRY 100000
 class ThreadProfile
 {
 public:
-  ThreadProfile(int aEntrySize, ProfileStack *aStack)
+  ThreadProfile(int aEntrySize, PseudoStack *aStack)
     : mWritePos(0)
     , mLastFlushPos(0)
     , mReadPos(0)
     , mEntrySize(aEntrySize)
     , mStack(aStack)
   {
     mEntries = new ProfileEntry[mEntrySize];
   }
@@ -411,45 +406,45 @@ public:
               b.ArrayPush(frames, frame);
             }
           }
       }
       readPos = (readPos + incBy) % mEntrySize;
     }
   }
 
-  ProfileStack* GetStack()
+  PseudoStack* GetStack()
   {
     return mStack;
   }
 private:
   // Circular buffer 'Keep One Slot Open' implementation
   // for simplicity
   ProfileEntry *mEntries;
   int mWritePos; // points to the next entry we will write to
   int mLastFlushPos; // points to the next entry since the last flush()
   int mReadPos;  // points to the next entry we will read to
   int mEntrySize;
-  ProfileStack *mStack;
+  PseudoStack *mStack;
 };
 
 class SaveProfileTask;
 
 static bool
 hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) {
   for(size_t i = 0; i < aFeatureCount; i++) {
     if (strcmp(aFeatures[i], aFeature) == 0)
       return true;
   }
   return false;
 }
 
 class TableTicker: public Sampler {
  public:
-  TableTicker(int aInterval, int aEntrySize, ProfileStack *aStack,
+  TableTicker(int aInterval, int aEntrySize, PseudoStack *aStack,
               const char** aFeatures, uint32_t aFeatureCount)
     : Sampler(aInterval, true)
     , mPrimaryThreadProfile(aEntrySize, aStack)
     , mStartTime(TimeStamp::Now())
     , mSaveRequested(false)
   {
     mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
 
@@ -581,17 +576,17 @@ public:
       stream.open(tmpPath.get());
       // Pause the profiler during saving.
       // This will prevent us from recording sampling
       // regarding profile saving. This will also
       // prevent bugs caused by the circular buffer not
       // being thread safe. Bug 750989.
       if (stream.is_open()) {
         JSAutoCompartment autoComp(cx, obj);
-        JSObject* profileObj = mozilla_sampler_get_profile_data(cx);
+        JSObject* profileObj = mozilla_sampler_get_profile_data1(cx);
         jsval val = OBJECT_TO_JSVAL(profileObj);
         JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
         stream.close();
         LOGF("Saved to %s", tmpPath.get());
       } else {
         LOG("Fail to open profile log file.");
       }
     }
@@ -724,17 +719,17 @@ void addDynamicTag(ThreadProfile &aProfi
     j += sizeof(void*)/sizeof(char);
     // Cast to *((void**) to pass the text data to a void*
     aProfile.addTag(ProfileEntry('d', *((void**)(&text[0]))));
   }
 }
 
 static
 void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile,
-                     ProfileStack *stack, void *lastpc)
+                     PseudoStack *stack, void *lastpc)
 {
   int lineno = -1;
 
   // First entry has tagName 's' (start)
   // Check for magic pointer bit 1 to indicate copy
   const char* sampleLabel = entry.label();
   if (entry.isCopyLabel()) {
     // Store the string using 1 or more 'd' (dynamic) tags
@@ -838,17 +833,17 @@ void TableTicker::doBacktrace(ThreadProf
 #endif // XP_WIN
 
   nsresult rv = NS_StackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames,
                              &array, thread, platformData);
 #endif
   if (NS_SUCCEEDED(rv)) {
     aProfile.addTag(ProfileEntry('s', "(root)"));
 
-    ProfileStack* stack = aProfile.GetStack();
+    PseudoStack* stack = aProfile.GetStack();
     uint32_t pseudoStackPos = 0;
 
     /* We have two stacks, the native C stack we extracted from unwinding,
      * and the pseudostack we managed during execution. We want to consolidate
      * the two in order. We do so by merging using the approximate stack address
      * when each entry was push. When pushing JS entry we may not now the stack
      * address in which case we have a NULL stack address in which case we assume
      * that it follows immediatly the previous element.
@@ -877,17 +872,17 @@ void TableTicker::doBacktrace(ThreadProf
 
       aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1]));
     }
   }
 }
 #endif
 
 static
-void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSample *sample)
+void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample *sample)
 {
   // Sample
   // 's' tag denotes the start of a sample block
   // followed by 0 or more 'c' tags.
   aProfile.addTag(ProfileEntry('s', "(root)"));
   for (uint32_t i = 0; i < aStack->stackSize(); i++) {
     addProfileEntry(aStack->mStack[i], aProfile, aStack, nullptr);
   }
@@ -911,17 +906,17 @@ unsigned int sCurrentEventGeneration = 0
 /* we don't need to worry about overflow because we only treat the
  * case of them being the same as special. i.e. we only run into
  * a problem if 2^32 events happen between samples that we need
  * to know are associated with different events */
 
 void TableTicker::Tick(TickSample* sample)
 {
   // Marker(s) come before the sample
-  ProfileStack* stack = mPrimaryThreadProfile.GetStack();
+  PseudoStack* stack = mPrimaryThreadProfile.GetStack();
   for (int i = 0; stack->getMarker(i) != NULL; i++) {
     addDynamicTag(mPrimaryThreadProfile, 'm', stack->getMarker(i));
   }
   stack->mQueueClearMarker = true;
 
   bool recordSample = true;
   if (mJankOnly) {
     // if we are on a different event we can discard any temporary samples
@@ -997,29 +992,72 @@ std::ostream& operator<<(std::ostream& s
   } else if (entry.mTagName == 'd') {
     // TODO implement 'd' tag for text profile
   } else {
     stream << entry.mTagName << "-" << entry.mTagData << "\n";
   }
   return stream;
 }
 
-void mozilla_sampler_init()
+bool sps_version2()
+{
+  static int version = 0; // Raced on, potentially
+
+  if (version == 0) {
+    bool allow2 = false; // Is v2 allowable on this platform?
+#   if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
+       || defined(SPS_PLAT_x86_linux)
+    allow2 = true;
+#   elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin) \
+         || defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_x86_android) \
+         || defined(SPS_PLAT_amd64_windows)
+    allow2 = false;
+#   else
+#     error "Unknown platform"
+#   endif
+
+    bool req2 = PR_GetEnv("MOZ_PROFILER_NEW") != NULL; // Has v2 been requested?
+
+    bool elfhackd = false;
+#   if defined(USE_ELF_HACK)
+    bool elfhackd = true;
+#   endif
+
+    if (req2 && allow2) {
+      version = 2;
+      LOG("------------------- MOZ_PROFILER_NEW set -------------------");
+    } else if (req2 && !allow2) {
+      version = 1;
+      LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
+      LOG("---------- but is not available on this platform -----------");
+    } else if (req2 && elfhackd) {
+      version = 1;
+      LOG("--------------- MOZ_PROFILER_NEW requested, ----------------");
+      LOG("--- but this build was not done with --disable-elf-hack ----");
+    } else {
+      version = 1;
+      LOG("----------------- MOZ_PROFILER_NEW not set -----------------");
+    }
+  }
+  return version == 2;
+}
+
+void mozilla_sampler_init1()
 {
   if (stack_key_initialized)
     return;
 
-  if (!tlsStack.init() || !tlsTicker.init()) {
+  if (!tlsPseudoStack.init() || !tlsTicker.init()) {
     LOG("Failed to init.");
     return;
   }
   stack_key_initialized = true;
 
-  ProfileStack *stack = new ProfileStack();
-  tlsStack.set(stack);
+  PseudoStack *stack = new PseudoStack();
+  tlsPseudoStack.set(stack);
 
   // Allow the profiler to be started using signals
   OS::RegisterStartHandler();
 
   // We can't open pref so we use an environment variable
   // to know if we should trigger the profiler on startup
   // NOTE: Default
   const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
@@ -1027,55 +1065,55 @@ void mozilla_sampler_init()
     return;
   }
 
   const char* features[] = {"js", "leaf"
 #if defined(XP_WIN) || defined(XP_MACOSX)
                          , "stackwalk"
 #endif
                          };
-  mozilla_sampler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
-                        features, sizeof(features)/sizeof(const char*));
+  mozilla_sampler_start1(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
+                         features, sizeof(features)/sizeof(const char*));
 }
 
-void mozilla_sampler_shutdown()
+void mozilla_sampler_shutdown1()
 {
   TableTicker *t = tlsTicker.get();
   if (t) {
     const char *val = PR_GetEnv("MOZ_PROFILER_SHUTDOWN");
     if (val) {
       std::ofstream stream;
       stream.open(val);
       if (stream.is_open()) {
         t->ToStreamAsJSON(stream);
         stream.close();
       }
     }
   }
 
-  mozilla_sampler_stop();
+  mozilla_sampler_stop1();
   // We can't delete the Stack because we can be between a
   // sampler call_enter/call_exit point.
   // TODO Need to find a safe time to delete Stack
 }
 
-void mozilla_sampler_save()
+void mozilla_sampler_save1()
 {
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return;
   }
 
   t->RequestSave();
   // We're on the main thread already so we don't
   // have to wait to handle the save request.
   t->HandleSaveRequest();
 }
 
-char* mozilla_sampler_get_profile()
+char* mozilla_sampler_get_profile1()
 {
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return NULL;
   }
 
   std::stringstream profile;
   t->SetPaused(true);
@@ -1083,28 +1121,28 @@ char* mozilla_sampler_get_profile()
   t->SetPaused(false);
 
   std::string profileString = profile.str();
   char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
   strcpy(rtn, profileString.c_str());
   return rtn;
 }
 
-JSObject *mozilla_sampler_get_profile_data(JSContext *aCx)
+JSObject *mozilla_sampler_get_profile_data1(JSContext *aCx)
 {
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return NULL;
   }
 
   return t->ToJSObject(aCx);
 }
 
 
-const char** mozilla_sampler_get_features()
+const char** mozilla_sampler_get_features1()
 {
   static const char* features[] = {
 #if defined(MOZ_PROFILING) && (defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK))
     "stackwalk",
 #endif
 #if defined(ENABLE_SPS_LEAF_DATA)
     "leaf",
 #endif
@@ -1112,146 +1150,145 @@ const char** mozilla_sampler_get_feature
     "js",
     NULL
   };
 
   return features;
 }
 
 // Values are only honored on the first start
-void mozilla_sampler_start(int aProfileEntries, int aInterval,
-                           const char** aFeatures, uint32_t aFeatureCount)
+void mozilla_sampler_start1(int aProfileEntries, int aInterval,
+                            const char** aFeatures, uint32_t aFeatureCount)
 {
   if (!stack_key_initialized)
-    mozilla_sampler_init();
+    mozilla_sampler_init1();
 
-  ProfileStack *stack = tlsStack.get();
+  PseudoStack *stack = tlsPseudoStack.get();
   if (!stack) {
     ASSERT(false);
     return;
   }
 
-  mozilla_sampler_stop();
+  mozilla_sampler_stop1();
 
   TableTicker *t = new TableTicker(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
                                    aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
                                    stack, aFeatures, aFeatureCount);
   tlsTicker.set(t);
   t->Start();
   if (t->ProfileJS())
       stack->enableJSSampling();
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-started", nullptr);
 }
 
-void mozilla_sampler_stop()
+void mozilla_sampler_stop1()
 {
   if (!stack_key_initialized)
-    mozilla_sampler_init();
+    mozilla_sampler_init1();
 
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return;
   }
 
   bool disableJS = t->ProfileJS();
 
   t->Stop();
   delete t;
   tlsTicker.set(NULL);
-  ProfileStack *stack = tlsStack.get();
+  PseudoStack *stack = tlsPseudoStack.get();
   ASSERT(stack != NULL);
 
   if (disableJS)
     stack->disableJSSampling();
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
 }
 
-bool mozilla_sampler_is_active()
+bool mozilla_sampler_is_active1()
 {
   if (!stack_key_initialized)
-    mozilla_sampler_init();
+    mozilla_sampler_init1();
 
   TableTicker *t = tlsTicker.get();
   if (!t) {
     return false;
   }
 
   return t->IsActive();
 }
 
-double sResponsivenessTimes[100];
-double sCurrResponsiveness = 0.f;
-unsigned int sResponsivenessLoc = 0;
-void mozilla_sampler_responsiveness(TimeStamp aTime)
+static double sResponsivenessTimes[100];
+static unsigned int sResponsivenessLoc = 0;
+void mozilla_sampler_responsiveness1(TimeStamp aTime)
 {
   if (!sLastTracerEvent.IsNull()) {
     if (sResponsivenessLoc == 100) {
       for(size_t i = 0; i < 100-1; i++) {
         sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
       }
       sResponsivenessLoc--;
     }
     TimeDuration delta = aTime - sLastTracerEvent;
     sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
   }
   sCurrentEventGeneration++;
 
   sLastTracerEvent = aTime;
 }
 
-const double* mozilla_sampler_get_responsiveness()
+const double* mozilla_sampler_get_responsiveness1()
 {
   return sResponsivenessTimes;
 }
 
-void mozilla_sampler_frame_number(int frameNumber)
+void mozilla_sampler_frame_number1(int frameNumber)
 {
   sFrameNumber = frameNumber;
 }
 
-void print_callback(const ProfileEntry& entry, const char* tagStringData) {
+static void print_callback(const ProfileEntry& entry, const char* tagStringData) {
   switch (entry.mTagName) {
     case 's':
     case 'c':
       printf_stderr("  %s\n", tagStringData);
   }
 }
 
-void mozilla_sampler_print_location()
+void mozilla_sampler_print_location1()
 {
   if (!stack_key_initialized)
-    mozilla_sampler_init();
+    mozilla_sampler_init1();
 
-  ProfileStack *stack = tlsStack.get();
+  PseudoStack *stack = tlsPseudoStack.get();
   if (!stack) {
     MOZ_ASSERT(false);
     return;
   }
 
   ThreadProfile threadProfile(1000, stack);
   doSampleStackTrace(stack, threadProfile, NULL);
 
   threadProfile.flush();
 
   printf_stderr("Backtrace:\n");
   threadProfile.IterateTags(print_callback);
 }
 
-void mozilla_sampler_lock()
+void mozilla_sampler_lock1()
 {
-  mozilla_sampler_stop();
+  mozilla_sampler_stop1();
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-locked", nullptr);
 }
 
-void mozilla_sampler_unlock()
+void mozilla_sampler_unlock1()
 {
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-unlocked", nullptr);
 }
new file mode 100644
--- /dev/null
+++ b/tools/profiler/TableTicker2.cpp
@@ -0,0 +1,1007 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+// System
+#include <string>
+#include <stdio.h>
+#include <errno.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#if defined(ANDROID)
+# include "android-signal-defs.h"
+#endif
+
+// Profiler
+#include "PlatformMacros.h"
+#include "sps_sampler.h"
+#include "platform.h"
+#include "nsXULAppAPI.h"
+#include "nsThreadUtils.h"
+#include "prenv.h"
+#include "shared-libraries.h"
+#include "mozilla/StackWalk.h"
+#include "ProfileEntry2.h"
+#include "UnwinderThread2.h"
+
+// JSON
+#include "JSObjectBuilder.h"
+#include "nsIJSRuntimeService.h"
+
+// Meta
+#include "nsXPCOM.h"
+#include "nsXPCOMCID.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIXULRuntime.h"
+#include "nsIXULAppInfo.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+
+// JS
+#include "jsdbgapi.h"
+
+// This file's exports are listed in sps_sampler.h.
+
+// Pseudo backtraces are available on all platforms.  Native
+// backtraces are available only on selected platforms.  Breakpad is
+// the only supported native unwinder.  HAVE_NATIVE_UNWIND is set at
+// build time to indicate whether native unwinding is possible on this
+// platform.  The actual unwind mode currently in use is stored in
+// sUnwindMode.
+
+#undef HAVE_NATIVE_UNWIND
+#if defined(MOZ_PROFILING) \
+    && (defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
+        || defined(SPS_PLAT_x86_linux))
+# define HAVE_NATIVE_UNWIND
+#endif
+
+#if 0 && defined(MOZ_PROFILING) && !defined(SPS_PLAT_x86_windows)
+# warning MOZ_PROFILING
+#endif
+
+#if 0 && defined(HAVE_NATIVE_UNWIND)
+# warning HAVE_NATIVE_UNWIND
+#endif
+
+typedef  enum { UnwINVALID, UnwNATIVE, UnwPSEUDO, UnwCOMBINED }  UnwMode;
+
+/* These will be set to something sensible before we take the first
+   sample. */
+static UnwMode sUnwindMode     = UnwINVALID;
+static int     sUnwindInterval = 0;
+
+
+using std::string;
+using namespace mozilla;
+
+#ifdef XP_WIN
+ #include <windows.h>
+ #define getpid GetCurrentProcessId
+#else
+ #include <unistd.h>
+#endif
+
+#ifndef MAXPATHLEN
+ #ifdef PATH_MAX
+  #define MAXPATHLEN PATH_MAX
+ #elif defined(MAX_PATH)
+  #define MAXPATHLEN MAX_PATH
+ #elif defined(_MAX_PATH)
+  #define MAXPATHLEN _MAX_PATH
+ #elif defined(CCHMAXPATH)
+  #define MAXPATHLEN CCHMAXPATH
+ #else
+  #define MAXPATHLEN 1024
+ #endif
+#endif
+
+#if _MSC_VER
+ #define snprintf _snprintf
+#endif
+
+class TableTicker2;
+
+mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
+static mozilla::ThreadLocal<TableTicker2 *> tlsTicker;
+// We need to track whether we've been initialized otherwise
+// we end up using tlsStack without initializing it.
+// Because tlsStack is totally opaque to us we can't reuse
+// it as the flag itself.
+bool stack_key_initialized;
+
+static TimeStamp sLastTracerEvent; // is raced on
+static int       sFrameNumber = 0;
+static int       sLastFrameNumber = 0;
+
+
+static bool
+hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature)
+{
+  for (size_t i = 0; i < aFeatureCount; i++) {
+    if (0) LOGF("hasFeature %s: %lu %s", aFeature, (unsigned long)i, aFeatures[i]);
+    if (strcmp(aFeatures[i], aFeature) == 0)
+      return true;
+  }
+  return false;
+}
+
+class TableTicker2: public Sampler {
+ public:
+  TableTicker2(int aInterval, int aEntrySize, PseudoStack *aStack,
+              const char** aFeatures, uint32_t aFeatureCount)
+    : Sampler(aInterval, true)
+    , mPrimaryThreadProfile(aEntrySize, aStack)
+    , mStartTime(TimeStamp::Now())
+    , mSaveRequested(false)
+  {
+    mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk");
+
+    //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
+    mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
+    mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
+    mPrimaryThreadProfile.addTag(ProfileEntry2('m', "Start"));
+  }
+
+  ~TableTicker2() { if (IsActive()) Stop(); }
+
+  virtual void SampleStack(TickSample* sample) {}
+
+  // Called within a signal. This function must be reentrant
+  virtual void Tick(TickSample* sample);
+
+  // Called within a signal. This function must be reentrant
+  virtual void RequestSave()
+  {
+    mSaveRequested = true;
+  }
+
+  virtual void HandleSaveRequest();
+
+  ThreadProfile2* GetPrimaryThreadProfile()
+  {
+    return &mPrimaryThreadProfile;
+  }
+
+  JSObject *ToJSObject(JSContext *aCx);
+  JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
+
+  const bool ProfileJS() { return mProfileJS; }
+
+private:
+  // Unwind, using a "native" backtrace scheme (non-PseudoStack).
+  // Not implemented on platforms which do not support native backtraces
+  void doNativeBacktrace(ThreadProfile2 &aProfile, TickSample* aSample);
+
+private:
+  // This represent the application's main thread (SAMPLER_INIT)
+  ThreadProfile2 mPrimaryThreadProfile;
+  TimeStamp mStartTime;
+  bool mSaveRequested;
+  bool mUseStackWalk;
+  bool mJankOnly;
+  bool mProfileJS;
+};
+
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN SaveProfileTask et al
+
+std::string GetSharedLibraryInfoString();
+
+static JSBool
+WriteCallback(const jschar *buf, uint32_t len, void *data)
+{
+  std::ofstream& stream = *static_cast<std::ofstream*>(data);
+  nsAutoCString profile = NS_ConvertUTF16toUTF8(buf, len);
+  stream << profile.Data();
+  return JS_TRUE;
+}
+
+/**
+ * This is an event used to save the profile on the main thread
+ * to be sure that it is not being modified while saving.
+ */
+class SaveProfileTask : public nsRunnable {
+public:
+  SaveProfileTask() {}
+
+  NS_IMETHOD Run() {
+    TableTicker2 *t = tlsTicker.get();
+
+    // Pause the profiler during saving.
+    // This will prevent us from recording sampling
+    // regarding profile saving. This will also
+    // prevent bugs caused by the circular buffer not
+    // being thread safe. Bug 750989.
+    t->SetPaused(true);
+
+    // Get file path
+#   if defined(SPS_PLAT_arm_android)
+    nsCString tmpPath;
+    tmpPath.AppendPrintf("/sdcard/profile_%i_%i.txt", XRE_GetProcessType(), getpid());
+#   else
+    nsCOMPtr<nsIFile> tmpFile;
+    nsAutoCString tmpPath;
+    if (NS_FAILED(NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)))) {
+      LOG("Failed to find temporary directory.");
+      return NS_ERROR_FAILURE;
+    }
+    tmpPath.AppendPrintf("profile_%i_%i.txt", XRE_GetProcessType(), getpid());
+
+    nsresult rv = tmpFile->AppendNative(tmpPath);
+    if (NS_FAILED(rv))
+      return rv;
+
+    rv = tmpFile->GetNativePath(tmpPath);
+    if (NS_FAILED(rv))
+      return rv;
+#   endif
+
+    // Create a JSContext to run a JSObjectBuilder :(
+    // Based on XPCShellEnvironment
+    JSRuntime *rt;
+    JSContext *cx;
+    nsCOMPtr<nsIJSRuntimeService> rtsvc 
+      = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
+    if (!rtsvc || NS_FAILED(rtsvc->GetRuntime(&rt)) || !rt) {
+      LOG("failed to get RuntimeService");
+      return NS_ERROR_FAILURE;;
+    }
+
+    cx = JS_NewContext(rt, 8192);
+    if (!cx) {
+      LOG("Failed to get context");
+      return NS_ERROR_FAILURE;
+    }
+
+    {
+      JSAutoRequest ar(cx);
+      static JSClass c = {
+          "global", JSCLASS_GLOBAL_FLAGS,
+          JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
+          JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub
+      };
+      JSObject *obj = JS_NewGlobalObject(cx, &c, NULL);
+
+      std::ofstream stream;
+      stream.open(tmpPath.get());
+      // Pause the profiler during saving.
+      // This will prevent us from recording sampling
+      // regarding profile saving. This will also
+      // prevent bugs caused by the circular buffer not
+      // being thread safe. Bug 750989.
+      t->SetPaused(true);
+      if (stream.is_open()) {
+        JSAutoCompartment autoComp(cx, obj);
+        JSObject* profileObj = mozilla_sampler_get_profile_data2(cx);
+        jsval val = OBJECT_TO_JSVAL(profileObj);
+        JS_Stringify(cx, &val, nullptr, JSVAL_NULL, WriteCallback, &stream);
+        stream.close();
+        LOGF("Saved to %s", tmpPath.get());
+      } else {
+        LOG("Fail to open profile log file.");
+      }
+    }
+    JS_EndRequest(cx);
+    JS_DestroyContext(cx);
+
+    t->SetPaused(false);
+
+    return NS_OK;
+  }
+};
+
+void TableTicker2::HandleSaveRequest()
+{
+  if (!mSaveRequested)
+    return;
+  mSaveRequested = false;
+
+  // TODO: Use use the ipc/chromium Tasks here to support processes
+  // without XPCOM.
+  nsCOMPtr<nsIRunnable> runnable = new SaveProfileTask();
+  NS_DispatchToMainThread(runnable);
+}
+
+JSCustomObject* TableTicker2::GetMetaJSCustomObject(JSAObjectBuilder& b)
+{
+  JSCustomObject *meta = b.CreateObject();
+
+  b.DefineProperty(meta, "version", 2);
+  b.DefineProperty(meta, "interval", interval());
+  b.DefineProperty(meta, "stackwalk", mUseStackWalk);
+  b.DefineProperty(meta, "jank", mJankOnly);
+  b.DefineProperty(meta, "processType", XRE_GetProcessType());
+
+  nsresult res;
+  nsCOMPtr<nsIHttpProtocolHandler> http
+    = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &res);
+  if (!NS_FAILED(res)) {
+    nsAutoCString string;
+
+    res = http->GetPlatform(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "platform", string.Data());
+
+    res = http->GetOscpu(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "oscpu", string.Data());
+
+    res = http->GetMisc(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "misc", string.Data());
+  }
+
+  nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
+  if (runtime) {
+    nsAutoCString string;
+
+    res = runtime->GetXPCOMABI(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "abi", string.Data());
+
+    res = runtime->GetWidgetToolkit(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "toolkit", string.Data());
+  }
+
+  nsCOMPtr<nsIXULAppInfo> appInfo = do_GetService("@mozilla.org/xre/app-info;1");
+  if (appInfo) {
+    nsAutoCString string;
+
+    res = appInfo->GetName(string);
+    if (!NS_FAILED(res))
+      b.DefineProperty(meta, "product", string.Data());
+  }
+
+  return meta;
+}
+
+JSObject* TableTicker2::ToJSObject(JSContext *aCx)
+{
+  JSObjectBuilder b(aCx);
+
+  JSCustomObject *profile = b.CreateObject();
+
+  // Put shared library info
+  b.DefineProperty(profile, "libs", GetSharedLibraryInfoString().c_str());
+
+  // Put meta data
+  JSCustomObject *meta = GetMetaJSCustomObject(b);
+  b.DefineProperty(profile, "meta", meta);
+
+  // Lists the samples for each ThreadProfile2
+  JSCustomArray *threads = b.CreateArray();
+  b.DefineProperty(profile, "threads", threads);
+
+  // For now we only have one thread
+  SetPaused(true);
+  ThreadProfile2* prof = GetPrimaryThreadProfile();
+  prof->GetMutex()->Lock();
+  JSCustomObject* threadSamples = prof->ToJSObject(aCx);
+  b.ArrayPush(threads, threadSamples);
+  prof->GetMutex()->Unlock();
+  SetPaused(false);
+
+  return b.GetJSObject(profile);
+}
+
+// END SaveProfileTask et al
+////////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN take samples.
+// Everything in this section RUNS IN SIGHANDLER CONTEXT
+
+// RUNS IN SIGHANDLER CONTEXT
+static
+void genProfileEntry2(/*MODIFIED*/UnwinderThreadBuffer* utb,
+                     volatile StackEntry &entry,
+                     PseudoStack *stack, void *lastpc)
+{
+  int lineno = -1;
+
+  // Add a pseudostack-entry start label
+  utb__addEntry( utb, ProfileEntry2('h', 'P') );
+  // And the SP value, if it is non-zero
+  if (entry.stackAddress() != 0) {
+    utb__addEntry( utb, ProfileEntry2('S', entry.stackAddress()) );
+  }
+
+  // First entry has tagName 's' (start)
+  // Check for magic pointer bit 1 to indicate copy
+  const char* sampleLabel = entry.label();
+  if (entry.isCopyLabel()) {
+    // Store the string using 1 or more 'd' (dynamic) tags
+    // that will happen to the preceding tag
+
+    utb__addEntry( utb, ProfileEntry2('c', "") );
+    // Add one to store the null termination
+    size_t strLen = strlen(sampleLabel) + 1;
+    for (size_t j = 0; j < strLen;) {
+      // Store as many characters in the void* as the platform allows
+      char text[sizeof(void*)];
+      for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) {
+        text[pos] = sampleLabel[j+pos];
+      }
+      j += sizeof(void*)/sizeof(char);
+      // Cast to *((void**) to pass the text data to a void*
+      utb__addEntry( utb, ProfileEntry2('d', *((void**)(&text[0]))) );
+    }
+    if (entry.js()) {
+      if (!entry.pc()) {
+        // The JIT only allows the top-most entry to have a NULL pc
+        MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]);
+        // If stack-walking was disabled, then that's just unfortunate
+        if (lastpc) {
+          jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(),
+                                                lastpc);
+          if (jspc) {
+            lineno = JS_PCToLineNumber(NULL, entry.script(), jspc);
+          }
+        }
+      } else {
+        lineno = JS_PCToLineNumber(NULL, entry.script(), entry.pc());
+      }
+    } else {
+      lineno = entry.line();
+    }
+  } else {
+    utb__addEntry( utb, ProfileEntry2('c', sampleLabel) );
+    lineno = entry.line();
+  }
+  if (lineno != -1) {
+    utb__addEntry( utb, ProfileEntry2('n', lineno) );
+  }
+
+  // Add a pseudostack-entry end label
+  utb__addEntry( utb, ProfileEntry2('h', 'Q') );
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+// Generate pseudo-backtrace entries and put them in |utb|, with
+// the order outermost frame first.
+void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb,
+                               PseudoStack *aStack, TickSample *sample)
+{
+  // Call genProfileEntry2 to generate tags for each profile
+  // entry.  Each entry will be bounded by a 'h' 'P' tag to
+  // mark the start and a 'h' 'Q' tag to mark the end.
+  uint32_t nInStack = aStack->stackSize();
+  for (uint32_t i = 0; i < nInStack; i++) {
+    genProfileEntry2(utb, aStack->mStack[i], aStack, nullptr);
+  }
+# ifdef ENABLE_SPS_LEAF_DATA
+  if (sample) {
+    utb__addEntry( utb, ProfileEntry2('l', (void*)sample->pc) );
+#   ifdef ENABLE_ARM_LR_SAVING
+    utb__addEntry( utb, ProfileEntry2('L', (void*)sample->lr) );
+#   endif
+  }
+# endif
+}
+
+/* used to keep track of the last event that we sampled during */
+static unsigned int sLastSampledEventGeneration = 0;
+
+/* a counter that's incremented everytime we get responsiveness event
+ * note: it might also be worth tracking everytime we go around
+ * the event loop */
+static unsigned int sCurrentEventGeneration = 0;
+/* we don't need to worry about overflow because we only treat the
+ * case of them being the same as special. i.e. we only run into
+ * a problem if 2^32 events happen between samples that we need
+ * to know are associated with different events */
+
+// RUNS IN SIGHANDLER CONTEXT
+void TableTicker2::Tick(TickSample* sample)
+{
+  /* Get hold of an empty inter-thread buffer into which to park
+     the ProfileEntries for this sample. */
+  UnwinderThreadBuffer* utb = uwt__acquire_empty_buffer();
+
+  /* This could fail, if no buffers are currently available, in which
+     case we must give up right away.  We cannot wait for a buffer to
+     become available, as that risks deadlock. */
+  if (!utb)
+    return;
+
+  /* Manufacture the ProfileEntries that we will give to the unwinder
+     thread, and park them in |utb|. */
+
+  // Marker(s) come before the sample
+  PseudoStack* stack = mPrimaryThreadProfile.GetPseudoStack();
+  for (int i = 0; stack->getMarker(i) != NULL; i++) {
+    utb__addEntry( utb, ProfileEntry2('m', stack->getMarker(i)) );
+  }
+  stack->mQueueClearMarker = true;
+
+  bool recordSample = true;
+  if (mJankOnly) {
+    // if we are on a different event we can discard any temporary samples
+    // we've kept around
+    if (sLastSampledEventGeneration != sCurrentEventGeneration) {
+      // XXX: we also probably want to add an entry to the profile to help
+      // distinguish which samples are part of the same event. That, or record
+      // the event generation in each sample
+      mPrimaryThreadProfile.erase();
+    }
+    sLastSampledEventGeneration = sCurrentEventGeneration;
+
+    recordSample = false;
+    // only record the events when we have a we haven't seen a tracer
+    // event for 100ms
+    if (!sLastTracerEvent.IsNull()) {
+      TimeDuration delta = sample->timestamp - sLastTracerEvent;
+      if (delta.ToMilliseconds() > 100.0) {
+          recordSample = true;
+      }
+    }
+  }
+
+  // JRS 2012-Sept-27: this logic used to involve mUseStackWalk.
+  // That should be reinstated, but for the moment, use the
+  // settings in sUnwindMode and sUnwindInterval.
+  // Add a native-backtrace request, or add pseudo backtrace entries,
+  // or both.
+  switch (sUnwindMode) {
+    case UnwNATIVE: /* Native only */
+      // add a "do native stack trace now" hint.  This will be actioned
+      // by the unwinder thread as it processes the entries in this
+      // sample.
+      utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'N'/*native-trace*/) );
+      break;
+    case UnwPSEUDO: /* Pseudo only */
+      /* Add into |utb|, the pseudo backtrace entries */
+      genPseudoBacktraceEntries(utb, stack, sample);
+      break;
+    case UnwCOMBINED: /* Both Native and Pseudo */
+      utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'N'/*native-trace*/) );
+      genPseudoBacktraceEntries(utb, stack, sample);
+      break;
+    case UnwINVALID:
+    default:
+      MOZ_CRASH();
+  }
+
+  if (recordSample) {    
+    // add a "flush now" hint
+    utb__addEntry( utb, ProfileEntry2('h'/*hint*/, 'F'/*flush*/) );
+  }
+
+  // Add any extras
+  if (!sLastTracerEvent.IsNull() && sample) {
+    TimeDuration delta = sample->timestamp - sLastTracerEvent;
+    utb__addEntry( utb, ProfileEntry2('r', delta.ToMilliseconds()) );
+  }
+
+  if (sample) {
+    TimeDuration delta = sample->timestamp - mStartTime;
+    utb__addEntry( utb, ProfileEntry2('t', delta.ToMilliseconds()) );
+  }
+
+  if (sLastFrameNumber != sFrameNumber) {
+    utb__addEntry( utb, ProfileEntry2('f', sFrameNumber) );
+    sLastFrameNumber = sFrameNumber;
+  }
+
+  /* So now we have, in |utb|, the complete set of entries we want to
+     push into the circular buffer.  This may also include a 'h' 'F'
+     entry, which is "flush now" hint, and/or a 'h' 'N' entry, which
+     is a "generate a native backtrace and add it to the buffer right
+     now" hint.  Hand them off to the helper thread, together with
+     stack and register context needed to do a native unwind, if that
+     is currently enabled. */
+
+  /* If a native unwind has been requested, we'll start it off using
+     the context obtained from the signal handler, to avoid the
+     problem of having to unwind through the signal frame itself. */
+
+  /* On Linux and Android, the initial register state is in the
+     supplied sample->context.  But on MacOS it's not, so we have to
+     fake it up here (sigh). */
+  if (sUnwindMode == UnwNATIVE || sUnwindMode == UnwCOMBINED) {
+#   if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
+       || defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
+    void* ucV = (void*)sample->context;
+#   elif defined(SPS_PLAT_amd64_darwin)
+    struct __darwin_mcontext64 mc;
+    memset(&mc, 0, sizeof(mc));
+    ucontext_t uc;
+    memset(&uc, 0, sizeof(uc));
+    uc.uc_mcontext = &mc;
+    mc.__ss.__rip = (uint64_t)sample->pc;
+    mc.__ss.__rsp = (uint64_t)sample->sp;
+    mc.__ss.__rbp = (uint64_t)sample->fp;
+    void* ucV = (void*)&uc;
+#   elif defined(SPS_PLAT_x86_darwin)
+    struct __darwin_mcontext32 mc;
+    memset(&mc, 0, sizeof(mc));
+    ucontext_t uc;
+    memset(&uc, 0, sizeof(uc));
+    uc.uc_mcontext = &mc;
+    mc.__ss.__eip = (uint32_t)sample->pc;
+    mc.__ss.__esp = (uint32_t)sample->sp;
+    mc.__ss.__ebp = (uint32_t)sample->fp;
+    void* ucV = (void*)&uc;
+#   elif defined(SPS_OS_windows)
+    /* Totally fake this up so it at least builds.  No idea if we can
+       even ever get here on Windows. */
+    void* ucV = NULL;
+#   else
+#     error "Unsupported platform"
+#   endif
+    uwt__release_full_buffer(&mPrimaryThreadProfile, utb, ucV);
+  } else {
+    uwt__release_full_buffer(&mPrimaryThreadProfile, utb, NULL);
+  }
+}
+
+// END take samples
+////////////////////////////////////////////////////////////////////////
+
+
+std::ostream& operator<<(std::ostream& stream, const ThreadProfile2& profile)
+{
+  int readPos = profile.mReadPos;
+  while (readPos != profile.mLastFlushPos) {
+    stream << profile.mEntries[readPos];
+    readPos = (readPos + 1) % profile.mEntrySize;
+  }
+  return stream;
+}
+
+std::ostream& operator<<(std::ostream& stream, const ProfileEntry2& entry)
+{
+  if (entry.mTagName == 'r' || entry.mTagName == 't') {
+    stream << entry.mTagName << "-" << std::fixed << entry.mTagFloat << "\n";
+  } else if (entry.mTagName == 'l' || entry.mTagName == 'L') {
+    // Bug 739800 - Force l-tag addresses to have a "0x" prefix on all platforms
+    // Additionally, stringstream seemed to be ignoring formatter flags.
+    char tagBuff[1024];
+    unsigned long long pc = (unsigned long long)(uintptr_t)entry.mTagPtr;
+    snprintf(tagBuff, 1024, "%c-%#llx\n", entry.mTagName, pc);
+    stream << tagBuff;
+  } else if (entry.mTagName == 'd') {
+    // TODO implement 'd' tag for text profile
+  } else {
+    stream << entry.mTagName << "-" << entry.mTagData << "\n";
+  }
+  return stream;
+}
+
+static const char* name_UnwMode(UnwMode m)
+{
+  switch (m) {
+    case UnwINVALID:  return "invalid";
+    case UnwNATIVE:   return "native";
+    case UnwPSEUDO:   return "pseudo";
+    case UnwCOMBINED: return "combined";
+    default:          return "??name_UnwMode??";
+  }
+}
+
+// Read env vars at startup, so as to set sUnwindMode and sInterval.
+static void read_env_vars()
+{
+  bool nativeAvail = false;
+# if defined(HAVE_NATIVE_UNWIND)
+  nativeAvail = true;
+# endif
+
+  MOZ_ASSERT(sUnwindMode     == UnwINVALID);
+  MOZ_ASSERT(sUnwindInterval == 0);
+
+  /* Set defaults */
+  sUnwindMode     = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
+  sUnwindInterval = 0;  /* We'll have to look elsewhere */
+
+  const char* strM = PR_GetEnv("MOZ_PROFILER_MODE");
+  const char* strI = PR_GetEnv("MOZ_PROFILER_INTERVAL");
+
+  if (strM) {
+    if (0 == strcmp(strM, "pseudo"))
+      sUnwindMode = UnwPSEUDO;
+    else if (0 == strcmp(strM, "native") && nativeAvail)
+      sUnwindMode = UnwNATIVE;
+    else if (0 == strcmp(strM, "combined") && nativeAvail)
+      sUnwindMode = UnwCOMBINED;
+    else goto usage;
+  }
+
+  if (strI) {
+    errno = 0;
+    long int n = strtol(strI, (char**)NULL, 10);
+    if (errno == 0 && n >= 1 && n <= 1000) {
+      sUnwindInterval = n;
+    }
+    else goto usage;
+  }
+
+  goto out;
+
+ usage:
+  LOG( "SPS: ");
+  LOG( "SPS: Environment variable usage:");
+  LOG( "SPS: ");
+  LOG( "SPS:   MOZ_PROFILER_MODE=native    for native unwind only");
+  LOG( "SPS:   MOZ_PROFILER_MODE=pseudo    for pseudo unwind only");
+  LOG( "SPS:   MOZ_PROFILER_MODE=combined  for combined native & pseudo unwind");
+  LOG( "SPS:   If unset, default is 'combined' on native-capable");
+  LOG( "SPS:     platforms, 'pseudo' on others.");
+  LOG( "SPS: ");
+  LOG( "SPS:   MOZ_PROFILER_INTERVAL=<number>   (milliseconds, 1 to 1000)");
+  LOG( "SPS:   If unset, platform default is used.");
+  LOG( "SPS: ");
+  LOGF("SPS:   This platform %s native unwinding.",
+       nativeAvail ? "supports" : "does not support");
+  LOG( "SPS: ");
+  /* Re-set defaults */
+  sUnwindMode       = nativeAvail ? UnwCOMBINED : UnwPSEUDO;
+  sUnwindInterval   = 0;  /* We'll have to look elsewhere */
+
+ out:
+  LOG( "SPS:");
+  LOGF("SPS: Unwind mode       = %s", name_UnwMode(sUnwindMode));
+  LOGF("SPS: Sampling interval = %d ms (zero means \"platform default\")",
+       (int)sUnwindInterval);
+  LOG( "SPS: Use env var MOZ_PROFILER_MODE=help for further information.");
+  LOG( "SPS:");
+
+  return;
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// BEGIN externally visible functions
+
+void mozilla_sampler_init2()
+{
+  if (stack_key_initialized)
+    return;
+
+  LOG("BEGIN mozilla_sampler_init2");
+  if (!tlsPseudoStack.init() || !tlsTicker.init()) {
+    LOG("Failed to init.");
+    return;
+  }
+  stack_key_initialized = true;
+
+  PseudoStack *stack = new PseudoStack();
+  tlsPseudoStack.set(stack);
+
+  // Read mode settings from MOZ_PROFILER_MODE and interval
+  // settings from MOZ_PROFILER_INTERVAL.
+  read_env_vars();
+
+  // Create the unwinder thread.  ATM there is only one.
+  uwt__init();
+
+# if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
+     || defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) \
+     || defined(SPS_PLAT_x86_windows) || defined(SPS_PLAT_amd64_windows) /* no idea if windows is correct */
+  // On Linuxes, register this thread (temporarily) for profiling
+  int aLocal;
+  uwt__register_thread_for_profiling( &aLocal );
+# elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin)
+  // Registration is done in platform-macos.cc
+# else
+#   error "Unknown plat"
+# endif
+
+  // Allow the profiler to be started using signals
+  OS::RegisterStartHandler();
+
+  // We can't open pref so we use an environment variable
+  // to know if we should trigger the profiler on startup
+  // NOTE: Default
+  const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
+  if (!val || !*val) {
+    return;
+  }
+
+  const char* features[] = {"js"
+#if defined(XP_WIN) || defined(XP_MACOSX)
+                         , "stackwalk"
+#endif
+                         };
+  mozilla_sampler_start2(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
+                         features, sizeof(features)/sizeof(const char*));
+  LOG("END   mozilla_sampler_init2");
+}
+
+void mozilla_sampler_shutdown2()
+{
+  // Shut down and reap the unwinder thread.  We have to do this
+  // before stopping the sampler, so as to guarantee that the unwinder
+  // thread doesn't try to access memory that the subsequent call to
+  // mozilla_sampler_stop2 causes to be freed.
+  uwt__deinit();
+
+  mozilla_sampler_stop2();
+  // We can't delete the Stack because we can be between a
+  // sampler call_enter/call_exit point.
+  // TODO Need to find a safe time to delete Stack
+}
+
+void mozilla_sampler_save2()
+{
+  TableTicker2 *t = tlsTicker.get();
+  if (!t) {
+    return;
+  }
+
+  t->RequestSave();
+  // We're on the main thread already so we don't
+  // have to wait to handle the save request.
+  t->HandleSaveRequest();
+}
+
+char* mozilla_sampler_get_profile2()
+{
+  TableTicker2 *t = tlsTicker.get();
+  if (!t) {
+    return NULL;
+  }
+
+  std::stringstream profile;
+  t->SetPaused(true);
+  profile << *(t->GetPrimaryThreadProfile());
+  t->SetPaused(false);
+
+  std::string profileString = profile.str();
+  char *rtn = (char*)malloc( (profileString.length() + 1) * sizeof(char) );
+  strcpy(rtn, profileString.c_str());
+  return rtn;
+}
+
+JSObject *mozilla_sampler_get_profile_data2(JSContext *aCx)
+{
+  TableTicker2 *t = tlsTicker.get();
+  if (!t) {
+    return NULL;
+  }
+
+  return t->ToJSObject(aCx);
+}
+
+
+const char** mozilla_sampler_get_features2()
+{
+  static const char* features[] = {
+#if defined(MOZ_PROFILING) && defined(HAVE_NATIVE_UNWIND)
+    "stackwalk",
+#endif
+    "jank",
+    "js",
+    NULL
+  };
+
+  return features;
+}
+
+// Values are only honored on the first start
+void mozilla_sampler_start2(int aProfileEntries, int aInterval,
+                            const char** aFeatures, uint32_t aFeatureCount)
+{
+  if (!stack_key_initialized)
+    mozilla_sampler_init2();
+
+  /* If the sampling interval was set using env vars, use that
+     in preference to anything else. */
+  if (sUnwindInterval > 0)
+    aInterval = sUnwindInterval;
+
+  PseudoStack *stack = tlsPseudoStack.get();
+  if (!stack) {
+    ASSERT(false);
+    return;
+  }
+
+  mozilla_sampler_stop2();
+  TableTicker2 *t
+    = new TableTicker2(aInterval ? aInterval : PROFILE_DEFAULT_INTERVAL,
+                      aProfileEntries ? aProfileEntries : PROFILE_DEFAULT_ENTRY,
+                      stack, aFeatures, aFeatureCount);
+  tlsTicker.set(t);
+  t->Start();
+  if (t->ProfileJS())
+      stack->enableJSSampling();
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os)
+    os->NotifyObservers(nullptr, "profiler-started", nullptr);
+}
+
+void mozilla_sampler_stop2()
+{
+  if (!stack_key_initialized)
+    mozilla_sampler_init2();
+
+  TableTicker2 *t = tlsTicker.get();
+  if (!t) {
+    return;
+  }
+
+  bool disableJS = t->ProfileJS();
+
+  t->Stop();
+  delete t;
+  tlsTicker.set(NULL);
+  PseudoStack *stack = tlsPseudoStack.get();
+  ASSERT(stack != NULL);
+
+  if (disableJS)
+    stack->disableJSSampling();
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os)
+    os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
+}
+
+bool mozilla_sampler_is_active2()
+{
+  if (!stack_key_initialized)
+    mozilla_sampler_init2();
+
+  TableTicker2 *t = tlsTicker.get();
+  if (!t) {
+    return false;
+  }
+
+  return t->IsActive();
+}
+
+static double sResponsivenessTimes[100];
+static unsigned int sResponsivenessLoc = 0;
+void mozilla_sampler_responsiveness2(TimeStamp aTime)
+{
+  if (!sLastTracerEvent.IsNull()) {
+    if (sResponsivenessLoc == 100) {
+      for(size_t i = 0; i < 100-1; i++) {
+        sResponsivenessTimes[i] = sResponsivenessTimes[i+1];
+      }
+      sResponsivenessLoc--;
+    }
+    TimeDuration delta = aTime - sLastTracerEvent;
+    sResponsivenessTimes[sResponsivenessLoc++] = delta.ToMilliseconds();
+  }
+  sCurrentEventGeneration++;
+
+  sLastTracerEvent = aTime;
+}
+
+const double* mozilla_sampler_get_responsiveness2()
+{
+  return sResponsivenessTimes;
+}
+
+void mozilla_sampler_frame_number2(int frameNumber)
+{
+  sFrameNumber = frameNumber;
+}
+
+void mozilla_sampler_print_location2()
+{
+  // FIXME
+}
+
+void mozilla_sampler_lock2()
+{
+  // FIXME!
+}
+
+void mozilla_sampler_unlock2()
+{
+  // FIXME!
+}
+
+// END externally visible functions
+////////////////////////////////////////////////////////////////////////
new file mode 100644
--- /dev/null
+++ b/tools/profiler/UnwinderThread2.cpp
@@ -0,0 +1,1766 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 <stdio.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef MOZ_VALGRIND
+# include <valgrind/helgrind.h>
+# include <valgrind/memcheck.h>
+#else
+# define VALGRIND_HG_MUTEX_LOCK_PRE(_mx,_istry)  /* */
+# define VALGRIND_HG_MUTEX_LOCK_POST(_mx)        /* */
+# define VALGRIND_HG_MUTEX_UNLOCK_PRE(_mx)       /* */
+# define VALGRIND_HG_MUTEX_UNLOCK_POST(_mx)      /* */
+# define VALGRIND_MAKE_MEM_DEFINED(_addr,_len)   ((void)0)
+# define VALGRIND_MAKE_MEM_UNDEFINED(_addr,_len) ((void)0)
+#endif
+
+#include "mozilla/arm.h"
+#include "mozilla/StandardInteger.h"
+#include "PlatformMacros.h"
+
+#include "sps_sampler.h"
+#include "platform.h"
+#include <iostream>
+
+#include "ProfileEntry2.h"
+#include "UnwinderThread2.h"
+
+#if !defined(SPS_OS_windows)
+# include <sys/time.h>
+# include <unistd.h>
+# include <pthread.h>
+  // mmap
+# include <sys/mman.h>
+#endif
+
+#if defined(SPS_OS_android)
+# include "android-signal-defs.h"
+#endif
+
+#if defined(SPS_OS_darwin)
+# include <mach-o/dyld.h>
+#endif
+
+
+/* Verbosity of this module, for debugging:
+     0  silent
+     1  adds info about debuginfo load success/failure
+     2  adds slow-summary stats for buffer fills/misses (RECOMMENDED)
+     3  adds per-sample summary lines
+     4  adds per-sample frame listing
+   Note that level 3 and above produces risk of deadlock, and 
+   are not recommended for extended use.
+*/
+#define LOGLEVEL 2
+
+
+// The 'else' of this covers the entire rest of the file
+#if defined(SPS_OS_windows)
+
+//////////////////////////////////////////////////////////
+//// BEGIN externally visible functions (WINDOWS STUBS)
+
+// On Windows this will all need reworking.  sps_sampler.h
+// will ensure these functions are never actually called,
+// so just provide no-op stubs for now.
+
+void uwt__init()
+{
+}
+
+void uwt__deinit()
+{
+}
+
+void uwt__register_thread_for_profiling ( void* stackTop )
+{
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+UnwinderThreadBuffer* uwt__acquire_empty_buffer()
+{
+  return NULL;
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+void
+uwt__release_full_buffer(ThreadProfile2* aProfile,
+                         UnwinderThreadBuffer* utb,
+                         void* /* ucontext_t*, really */ ucV )
+{
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+void
+utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
+{
+}
+
+//// END externally visible functions (WINDOWS STUBS)
+//////////////////////////////////////////////////////////
+
+#else // a supported target
+
+//////////////////////////////////////////////////////////
+//// BEGIN externally visible functions
+
+// Forward references
+// the unwinder thread ID, its fn, and a stop-now flag
+static void* unwind_thr_fn ( void* exit_nowV );
+static pthread_t unwind_thr;
+static int       unwind_thr_exit_now = 0; // RACED ON
+
+// Threads must be registered with this file before they can be
+// sampled.  So that we know the max safe stack address for each
+// registered thread.
+static void thread_register_for_profiling ( void* stackTop );
+
+// RUNS IN SIGHANDLER CONTEXT
+// Acquire an empty buffer and mark it as FILLING
+static UnwinderThreadBuffer* acquire_empty_buffer();
+
+// RUNS IN SIGHANDLER CONTEXT
+// Put this buffer in the queue of stuff going to the unwinder
+// thread, and mark it as FULL.  Before doing that, fill in stack
+// chunk and register fields if a native unwind is requested.
+// APROFILE is where the profile data should be added to.  UTB
+// is the partially-filled-in buffer, containing ProfileEntries.
+// UCV is the ucontext_t* from the signal handler.  If non-NULL, is
+// taken as a cue to request native unwind.
+static void release_full_buffer(ThreadProfile2* aProfile,
+                                UnwinderThreadBuffer* utb,
+                                void* /* ucontext_t*, really */ ucV );
+
+// RUNS IN SIGHANDLER CONTEXT
+static void utb_add_prof_ent(UnwinderThreadBuffer* utb, ProfileEntry2 ent);
+
+// Do a store memory barrier.
+static void do_MBAR();
+
+
+void uwt__init()
+{
+  // Create the unwinder thread.
+  MOZ_ASSERT(unwind_thr_exit_now == 0);
+  int r = pthread_create( &unwind_thr, NULL,
+                          unwind_thr_fn, (void*)&unwind_thr_exit_now );
+  MOZ_ALWAYS_TRUE(r==0);
+}
+
+void uwt__deinit()
+{
+  // Shut down the unwinder thread.
+  MOZ_ASSERT(unwind_thr_exit_now == 0);
+  unwind_thr_exit_now = 1;
+  do_MBAR();
+  int r = pthread_join(unwind_thr, NULL); MOZ_ALWAYS_TRUE(r==0);
+}
+
+void uwt__register_thread_for_profiling(void* stackTop)
+{
+  thread_register_for_profiling(stackTop);
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+UnwinderThreadBuffer* uwt__acquire_empty_buffer()
+{
+  return acquire_empty_buffer();
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+void
+uwt__release_full_buffer(ThreadProfile2* aProfile,
+                         UnwinderThreadBuffer* utb,
+                         void* /* ucontext_t*, really */ ucV )
+{
+  release_full_buffer( aProfile, utb, ucV );
+}
+
+// RUNS IN SIGHANDLER CONTEXT
+void
+utb__addEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
+{
+  utb_add_prof_ent(utb, ent);
+}
+
+//// END externally visible functions
+//////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////
+//// BEGIN type UnwindThreadBuffer
+
+MOZ_STATIC_ASSERT(sizeof(uint32_t) == 4, "uint32_t size incorrect");
+MOZ_STATIC_ASSERT(sizeof(uint64_t) == 8, "uint64_t size incorrect");
+MOZ_STATIC_ASSERT(sizeof(uintptr_t) == sizeof(void*),
+                  "uintptr_t size incorrect");
+
+typedef
+  struct { 
+    uint64_t rsp;
+    uint64_t rbp;
+    uint64_t rip; 
+  }
+  AMD64Regs;
+
+typedef
+  struct {
+    uint32_t r15;
+    uint32_t r14;
+    uint32_t r13;
+    uint32_t r12;
+    uint32_t r11;
+    uint32_t r7;
+  }
+  ARMRegs;
+
+typedef
+  struct {
+    uint32_t esp;
+    uint32_t ebp;
+    uint32_t eip;
+  }
+  X86Regs;
+
+#if defined(SPS_ARCH_amd64)
+typedef  AMD64Regs  ArchRegs;
+#elif defined(SPS_ARCH_arm)
+typedef  ARMRegs  ArchRegs;
+#elif defined(SPS_ARCH_x86)
+typedef  X86Regs  ArchRegs;
+#else
+# error "Unknown plat"
+#endif
+
+#if defined(SPS_ARCH_amd64) || defined(SPS_ARCH_arm) || defined(SPS_ARCH_x86)
+# define SPS_PAGE_SIZE 4096
+#else
+# error "Unknown plat"
+#endif
+
+typedef  enum { S_EMPTY, S_FILLING, S_EMPTYING, S_FULL }  State;
+
+typedef  struct { uintptr_t val; }  SpinLock;
+
+/* CONFIGURABLE */
+/* The maximum number of bytes in a stack snapshot */
+#define N_STACK_BYTES 32768
+
+/* CONFIGURABLE */
+/* The number of fixed ProfileEntry2 slots.  If more are required, they
+   are placed in mmap'd pages. */
+#define N_FIXED_PROF_ENTS 20
+
+/* CONFIGURABLE */
+/* The number of extra pages of ProfileEntries.  If (on arm) each
+   ProfileEntry2 is 8 bytes, then a page holds 512, and so 100 pages
+   is enough to hold 51200. */
+#define N_PROF_ENT_PAGES 100
+
+/* DERIVATIVE */
+#define N_PROF_ENTS_PER_PAGE (SPS_PAGE_SIZE / sizeof(ProfileEntry2))
+
+/* A page of ProfileEntry2s.  This might actually be slightly smaller
+   than a page if SPS_PAGE_SIZE is not an exact multiple of
+   sizeof(ProfileEntry2). */
+typedef
+  struct { ProfileEntry2 ents[N_PROF_ENTS_PER_PAGE]; }
+  ProfEntsPage;
+
+#define ProfEntsPage_INVALID ((ProfEntsPage*)1)
+
+
+/* Fields protected by the spinlock are marked SL */
+
+struct _UnwinderThreadBuffer {
+  /*SL*/ State  state;
+  /* The rest of these are protected, in some sense, by ::state.  If
+     ::state is S_FILLING, they are 'owned' by the sampler thread
+     that set the state to S_FILLING.  If ::state is S_EMPTYING,
+     they are 'owned' by the unwinder thread that set the state to
+     S_EMPTYING.  If ::state is S_EMPTY or S_FULL, the buffer isn't
+     owned by any thread, and so no thread may access these
+     fields. */
+  /* Sample number, needed to process samples in order */
+  uint64_t       seqNo;
+  /* The ThreadProfile2 into which the results are eventually to be
+     dumped. */
+  ThreadProfile2* aProfile;
+  /* Pseudostack and other info, always present */
+  ProfileEntry2   entsFixed[N_FIXED_PROF_ENTS];
+  ProfEntsPage*  entsPages[N_PROF_ENT_PAGES];
+  uintptr_t      entsUsed;
+  /* Do we also have data to do a native unwind? */
+  bool           haveNativeInfo;
+  /* If so, here is the register state and stack.  Unset if
+     .haveNativeInfo is false. */
+  ArchRegs       regs;
+  unsigned char  stackImg[N_STACK_BYTES];
+  unsigned int   stackImgUsed;
+  void*          stackImgAddr; /* VMA corresponding to stackImg[0] */
+  void*          stackMaxSafe; /* VMA for max safe stack reading */
+};
+/* Indexing scheme for ents:
+     0 <= i < N_FIXED_PROF_ENTS
+       is at entsFixed[i]
+
+     i >= N_FIXED_PROF_ENTS
+       is at let j = i - N_FIXED_PROF_ENTS
+             in  entsPages[j / N_PROFENTS_PER_PAGE]
+                  ->ents[j % N_PROFENTS_PER_PAGE]
+     
+   entsPages[] are allocated on demand.  Because zero can
+   theoretically be a valid page pointer, use 
+   ProfEntsPage_INVALID == (ProfEntsPage*)1 to mark invalid pages.
+
+   It follows that the max entsUsed value is N_FIXED_PROF_ENTS +
+   N_PROFENTS_PER_PAGE * N_PROFENTS_PAGES, and at that point no more
+   ProfileEntries can be storedd.
+*/
+
+
+typedef
+  struct {
+    pthread_t thrId;
+    void*     stackTop;
+    uint64_t  nSamples; 
+  }
+  StackLimit;
+
+/* Globals -- the buffer array */
+#define N_UNW_THR_BUFFERS 10
+/*SL*/ static UnwinderThreadBuffer** g_buffers     = NULL;
+/*SL*/ static uint64_t               g_seqNo       = 0;
+/*SL*/ static SpinLock               g_spinLock    = { 0 };
+
+/* Globals -- the thread array */
+#define N_SAMPLING_THREADS 10
+/*SL*/ static StackLimit g_stackLimits[N_SAMPLING_THREADS];
+/*SL*/ static int        g_stackLimitsUsed = 0;
+
+/* Stats -- atomically incremented, no lock needed */
+static uintptr_t g_stats_totalSamples = 0; // total # sample attempts
+static uintptr_t g_stats_noBuffAvail  = 0; // # failed due to no buffer avail
+
+/* We must be VERY CAREFUL what we do with the spinlock held.  The
+   only thing it is safe to do with it held is modify (viz, read or
+   write) g_buffers, g_buffers[], g_seqNo, g_buffers[]->state,
+   g_stackLimits[] and g_stackLimitsUsed.  No arbitrary computations,
+   no syscalls, no printfs, no file IO, and absolutely no dynamic
+   memory allocation (else we WILL eventually deadlock).
+
+   This applies both to the signal handler and to the unwinder thread.
+*/
+
+//// END type UnwindThreadBuffer
+//////////////////////////////////////////////////////////
+
+// fwds
+// the interface to breakpad
+typedef  struct { u_int64_t pc; u_int64_t sp; }  PCandSP;
+
+static
+void do_breakpad_unwind_Buffer(/*OUT*/PCandSP** pairs,
+                               /*OUT*/unsigned int* nPairs,
+                               UnwinderThreadBuffer* buff,
+                               int buffNo /* for debug printing only */);
+
+static bool is_page_aligned(void* v)
+{
+  uintptr_t w = (uintptr_t) v;
+  return (w & (SPS_PAGE_SIZE-1)) == 0  ? true  : false;
+}
+
+
+/* Implement machine-word sized atomic compare-and-swap.  Returns true
+   if success, false if failure. */
+static bool do_CASW(uintptr_t* addr, uintptr_t expected, uintptr_t nyu)
+{
+#if defined(__GNUC__)
+  return __sync_bool_compare_and_swap(addr, expected, nyu);
+#else
+# error "Unhandled compiler"
+#endif
+}
+
+/* Hint to the CPU core that we are in a spin-wait loop, and that
+   other processors/cores/threads-running-on-the-same-core should be
+   given priority on execute resources, if that is possible.  Not
+   critical if this is a no-op on some targets. */
+static void do_SPINLOOP_RELAX()
+{
+#if (defined(SPS_ARCH_amd64) || defined(SPS_ARCH_x86)) && defined(__GNUC__)
+  __asm__ __volatile__("rep; nop");
+#elif defined(SPS_PLAT_arm_android) && MOZILLA_ARM_ARCH >= 7
+  __asm__ __volatile__("wfe");
+#endif
+}
+
+/* Tell any cores snoozing in spin loops to wake up. */
+static void do_SPINLOOP_NUDGE()
+{
+#if (defined(SPS_ARCH_amd64) || defined(SPS_ARCH_x86)) && defined(__GNUC__)
+  /* this is a no-op */
+#elif defined(SPS_PLAT_arm_android) && MOZILLA_ARM_ARCH >= 7
+  __asm__ __volatile__("sev");
+#endif
+}
+
+/* Perform a full memory barrier. */
+static void do_MBAR()
+{
+#if defined(__GNUC__)
+  __sync_synchronize();
+#else
+# error "Unhandled compiler"
+#endif
+}
+
+static void spinLock_acquire(SpinLock* sl)
+{
+  uintptr_t* val = &sl->val;
+  VALGRIND_HG_MUTEX_LOCK_PRE(sl, 0/*!isTryLock*/);
+  while (1) {
+    bool ok = do_CASW( val, 0, 1 );
+    if (ok) break;
+    do_SPINLOOP_RELAX();
+  }
+  do_MBAR();
+  VALGRIND_HG_MUTEX_LOCK_POST(sl);
+}
+
+static void spinLock_release(SpinLock* sl)
+{
+  uintptr_t* val = &sl->val;
+  VALGRIND_HG_MUTEX_UNLOCK_PRE(sl);
+  do_MBAR();
+  bool ok = do_CASW( val, 1, 0 );
+  /* This must succeed at the first try.  To fail would imply that
+     the lock was unheld. */
+  MOZ_ALWAYS_TRUE(ok);
+  do_SPINLOOP_NUDGE();
+  VALGRIND_HG_MUTEX_UNLOCK_POST(sl);
+}
+
+static void sleep_ms(unsigned int ms)
+{
+  struct timespec req;
+  req.tv_sec = ((time_t)ms) / 1000;
+  req.tv_nsec = 1000 * 1000 * (((unsigned long)ms) % 1000);
+  nanosleep(&req, NULL);
+}
+
+/* Use CAS to implement standalone atomic increment. */
+static void atomic_INC(uintptr_t* loc)
+{
+  while (1) {
+    uintptr_t old = *loc;
+    uintptr_t nyu = old + 1;
+    bool ok = do_CASW( loc, old, nyu );
+    if (ok) break;
+  }
+}
+
+/* Register a thread for profiling.  It must not be allowed to receive
+   signals before this is done, else the signal handler will
+   MOZ_ASSERT. */
+static void thread_register_for_profiling(void* stackTop)
+{
+  int i;
+  /* Minimal sanity check on stackTop */
+  MOZ_ASSERT( (void*)&i < stackTop );
+
+  spinLock_acquire(&g_spinLock);
+
+  pthread_t me = pthread_self();
+  for (i = 0; i < g_stackLimitsUsed; i++) {
+    /* check for duplicate registration */
+    MOZ_ASSERT(g_stackLimits[i].thrId != me);
+  }
+  if (!(g_stackLimitsUsed < N_SAMPLING_THREADS))
+    MOZ_CRASH();  // Don't continue -- we'll get memory corruption.
+  g_stackLimits[g_stackLimitsUsed].thrId    = me;
+  g_stackLimits[g_stackLimitsUsed].stackTop = stackTop;
+  g_stackLimits[g_stackLimitsUsed].nSamples = 0;
+  g_stackLimitsUsed++;
+
+  spinLock_release(&g_spinLock);
+  LOGF("BPUnw: thread_register_for_profiling(stacktop %p, me %p)", 
+       stackTop, (void*)me);
+}
+
+
+__attribute__((unused))
+static void show_registered_threads()
+{
+  int i;
+  spinLock_acquire(&g_spinLock);
+  for (i = 0; i < g_stackLimitsUsed; i++) {
+    LOGF("[%d]  pthread_t=%p  nSamples=%lld",
+         i, (void*)g_stackLimits[i].thrId, 
+            (unsigned long long int)g_stackLimits[i].nSamples);
+  }
+  spinLock_release(&g_spinLock);
+}
+
+
+// RUNS IN SIGHANDLER CONTEXT
+static UnwinderThreadBuffer* acquire_empty_buffer()
+{
+  /* acq lock
+     if buffers == NULL { rel lock; exit }
+     scan to find a free buff; if none { rel lock; exit }
+     set buff state to S_FILLING
+     fillseqno++; and remember it
+     rel lock
+  */
+  int i;
+
+  atomic_INC( &g_stats_totalSamples );
+
+  /* This code is critical.  We are in a signal handler and possibly
+     with the malloc lock held.  So we can't allocate any heap, and
+     can't safely call any C library functions, not even the pthread_
+     functions.  And we certainly can't do any syscalls.  In short,
+     this function needs to be self contained, not do any allocation,
+     and not hold on to the spinlock for any significant length of
+     time. */
+
+  spinLock_acquire(&g_spinLock);
+
+  /* First of all, look for this thread's entry in g_stackLimits[].
+     We need to find it in order to figure out how much stack we can
+     safely copy into the sample.  This assumes that pthread_self()
+     is safe to call in a signal handler, which strikes me as highly
+     likely. */
+  pthread_t me = pthread_self();
+  MOZ_ASSERT(g_stackLimitsUsed >= 0 && g_stackLimitsUsed <= N_SAMPLING_THREADS);
+  for (i = 0; i < g_stackLimitsUsed; i++) {
+    if (g_stackLimits[i].thrId == me)
+      break;
+  }
+  /* "this thread is registered for profiling" */
+  MOZ_ASSERT(i < g_stackLimitsUsed);
+
+  /* The furthest point that we can safely scan back up the stack. */
+  void* myStackTop = g_stackLimits[i].stackTop;
+  g_stackLimits[i].nSamples++;
+
+  /* Try to find a free buffer to use. */
+  if (g_buffers == NULL) {
+    /* The unwinder thread hasn't allocated any buffers yet.
+       Nothing we can do. */
+    spinLock_release(&g_spinLock);
+    atomic_INC( &g_stats_noBuffAvail );
+    return NULL;
+  }
+
+  for (i = 0; i < N_UNW_THR_BUFFERS; i++) {
+    if (g_buffers[i]->state == S_EMPTY)
+      break;
+  }
+  MOZ_ASSERT(i >= 0 && i <= N_UNW_THR_BUFFERS);
+
+  if (i == N_UNW_THR_BUFFERS) {
+    /* Again, no free buffers .. give up. */
+    spinLock_release(&g_spinLock);
+    atomic_INC( &g_stats_noBuffAvail );
+    if (LOGLEVEL >= 3)
+      LOG("BPUnw: handler:  no free buffers");
+    return NULL;
+  }
+
+  /* So we can use this one safely.  Whilst still holding the lock,
+     mark the buffer as belonging to us, and increment the sequence
+     number. */
+  UnwinderThreadBuffer* buff = g_buffers[i];
+  MOZ_ASSERT(buff->state == S_EMPTY);
+  buff->state = S_FILLING;
+  buff->seqNo = g_seqNo;
+  g_seqNo++;
+
+  /* And drop the lock.  We own the buffer, so go on and fill it. */
+  spinLock_release(&g_spinLock);
+
+  /* Now we own the buffer, initialise it. */
+  buff->aProfile       = NULL;
+  buff->entsUsed       = 0;
+  buff->haveNativeInfo = false;
+  buff->stackImgUsed   = 0;
+  buff->stackImgAddr   = 0;
+  buff->stackMaxSafe   = myStackTop; /* We will need this in
+                                        release_full_buffer() */
+  for (i = 0; i < N_PROF_ENT_PAGES; i++)
+    buff->entsPages[i] = ProfEntsPage_INVALID;
+  return buff;
+}
+
+
+// RUNS IN SIGHANDLER CONTEXT
+/* The calling thread owns the buffer, as denoted by its state being
+   S_FILLING.  So we can mess with it without further locking. */
+static void release_full_buffer(ThreadProfile2* aProfile,
+                                UnwinderThreadBuffer* buff,
+                                void* /* ucontext_t*, really */ ucV )
+{
+  MOZ_ASSERT(buff->state == S_FILLING);
+
+  ////////////////////////////////////////////////////
+  // BEGIN fill
+
+  /* The buffer already will have some of its ProfileEntries filled
+     in, but everything else needs to be filled in at this point. */
+  //LOGF("Release full buffer: %lu ents", buff->entsUsed);
+  /* Where the resulting info is to be dumped */
+  buff->aProfile = aProfile;
+
+  /* And, if we have register state, that and the stack top */
+  buff->haveNativeInfo = ucV != NULL;
+  if (buff->haveNativeInfo) {
+#   if defined(SPS_PLAT_amd64_linux)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    mcontext_t* mc = &(uc->uc_mcontext);
+    buff->regs.rip = mc->gregs[REG_RIP];
+    buff->regs.rsp = mc->gregs[REG_RSP];
+    buff->regs.rbp = mc->gregs[REG_RBP];
+#   elif defined(SPS_PLAT_amd64_darwin)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    struct __darwin_mcontext64* mc = uc->uc_mcontext;
+    struct __darwin_x86_thread_state64* ss = &mc->__ss;
+    buff->regs.rip = ss->__rip;
+    buff->regs.rsp = ss->__rsp;
+    buff->regs.rbp = ss->__rbp;
+#   elif defined(SPS_PLAT_arm_android)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    mcontext_t* mc = &(uc->uc_mcontext);
+    buff->regs.r15 = mc->arm_pc; //gregs[R15];
+    buff->regs.r14 = mc->arm_lr; //gregs[R14];
+    buff->regs.r13 = mc->arm_sp; //gregs[R13];
+    buff->regs.r12 = mc->arm_ip; //gregs[R12];
+    buff->regs.r11 = mc->arm_fp; //gregs[R11];
+    buff->regs.r7  = mc->arm_r7; //gregs[R7];
+#   elif defined(SPS_PLAT_x86_linux)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    mcontext_t* mc = &(uc->uc_mcontext);
+    buff->regs.eip = mc->gregs[REG_EIP];
+    buff->regs.esp = mc->gregs[REG_ESP];
+    buff->regs.ebp = mc->gregs[REG_EBP];
+#   elif defined(SPS_PLAT_x86_darwin)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    struct __darwin_mcontext32* mc = uc->uc_mcontext;
+    struct __darwin_i386_thread_state* ss = &mc->__ss;
+    buff->regs.eip = ss->__eip;
+    buff->regs.esp = ss->__esp;
+    buff->regs.ebp = ss->__ebp;
+#   elif defined(SPS_PLAT_x86_android)
+    ucontext_t* uc = (ucontext_t*)ucV;
+    mcontext_t* mc = &(uc->uc_mcontext);
+    buff->regs.eip = mc->eip;
+    buff->regs.esp = mc->esp;
+    buff->regs.ebp = mc->ebp;
+#   else
+#     error "Unknown plat"
+#   endif
+
+    /* Copy up to N_STACK_BYTES from rsp-REDZONE upwards, but not
+       going past the stack's registered top point.  Do some basic
+       sanity checks too. */
+    { 
+#     if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_amd64_darwin)
+      uintptr_t rEDZONE_SIZE = 128;
+      uintptr_t start = buff->regs.rsp - rEDZONE_SIZE;
+#     elif defined(SPS_PLAT_arm_android)
+      uintptr_t rEDZONE_SIZE = 0;
+      uintptr_t start = buff->regs.r13 - rEDZONE_SIZE;
+#     elif defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_darwin) \
+           || defined(SPS_PLAT_x86_android)
+      uintptr_t rEDZONE_SIZE = 0;
+      uintptr_t start = buff->regs.esp - rEDZONE_SIZE;
+#     else
+#       error "Unknown plat"
+#     endif
+      uintptr_t end   = (uintptr_t)buff->stackMaxSafe;
+      uintptr_t ws    = sizeof(void*);
+      start &= ~(ws-1);
+      end   &= ~(ws-1);
+      uintptr_t nToCopy = 0;
+      if (start < end) {
+        nToCopy = end - start;
+        if (nToCopy > N_STACK_BYTES)
+          nToCopy = N_STACK_BYTES;
+      }
+      MOZ_ASSERT(nToCopy <= N_STACK_BYTES);
+      buff->stackImgUsed = nToCopy;
+      buff->stackImgAddr = (void*)start;
+      if (nToCopy > 0) {
+        memcpy(&buff->stackImg[0], (void*)start, nToCopy);
+        (void)VALGRIND_MAKE_MEM_DEFINED(&buff->stackImg[0], nToCopy);
+      }
+    }
+  } /* if (buff->haveNativeInfo) */
+  // END fill
+  ////////////////////////////////////////////////////
+
+  /* And now relinquish ownership of the buff, so that an unwinder
+     thread can pick it up. */
+  spinLock_acquire(&g_spinLock);
+  buff->state = S_FULL;
+  spinLock_release(&g_spinLock);
+}
+
+
+// RUNS IN SIGHANDLER CONTEXT
+// Allocate a ProfEntsPage, without using malloc, or return
+// ProfEntsPage_INVALID if we can't for some reason.
+static ProfEntsPage* mmap_anon_ProfEntsPage()
+{
+# if defined(SPS_OS_darwin)
+  void* v = ::mmap(NULL, sizeof(ProfEntsPage), PROT_READ|PROT_WRITE, 
+                   MAP_PRIVATE|MAP_ANON,      -1, 0);
+# else
+  void* v = ::mmap(NULL, sizeof(ProfEntsPage), PROT_READ|PROT_WRITE, 
+                   MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+# endif
+  if (v == MAP_FAILED) {
+    return ProfEntsPage_INVALID;
+  } else {
+    return (ProfEntsPage*)v;
+  }
+}
+
+// Runs in the unwinder thread
+// Free a ProfEntsPage as allocated by mmap_anon_ProfEntsPage
+static void munmap_ProfEntsPage(ProfEntsPage* pep)
+{
+  MOZ_ALWAYS_TRUE(is_page_aligned(pep));
+  ::munmap(pep, sizeof(ProfEntsPage));
+}
+
+
+// RUNS IN SIGHANDLER CONTEXT
+void
+utb_add_prof_ent(/*MODIFIED*/UnwinderThreadBuffer* utb, ProfileEntry2 ent)
+{
+  uintptr_t limit
+    = N_FIXED_PROF_ENTS + (N_PROF_ENTS_PER_PAGE * N_PROF_ENT_PAGES);
+  if (utb->entsUsed == limit) {
+    /* We're full.  Now what? */
+    LOG("BPUnw: utb__addEntry: NO SPACE for ProfileEntry2; ignoring.");
+    return;
+  }
+  MOZ_ASSERT(utb->entsUsed < limit);
+
+  /* Will it fit in the fixed array? */
+  if (utb->entsUsed < N_FIXED_PROF_ENTS) {
+    utb->entsFixed[utb->entsUsed] = ent;
+    utb->entsUsed++;
+    return;
+  }
+
+  /* No.  Put it in the extras. */
+  uintptr_t i     = utb->entsUsed;
+  uintptr_t j     = i - N_FIXED_PROF_ENTS;
+  uintptr_t j_div = j / N_PROF_ENTS_PER_PAGE; /* page number */
+  uintptr_t j_mod = j % N_PROF_ENTS_PER_PAGE; /* page offset */
+  ProfEntsPage* pep = utb->entsPages[j_div];
+  if (pep == ProfEntsPage_INVALID) {
+    pep = mmap_anon_ProfEntsPage();
+    if (pep == ProfEntsPage_INVALID) {
+      /* Urr, we ran out of memory.  Now what? */
+      LOG("BPUnw: utb__addEntry: MMAP FAILED for ProfileEntry2; ignoring.");
+      return;
+    }
+    utb->entsPages[j_div] = pep;
+  }
+  pep->ents[j_mod] = ent;
+  utb->entsUsed++;
+}
+
+
+// misc helper
+static ProfileEntry2 utb_get_profent(UnwinderThreadBuffer* buff, uintptr_t i)
+{
+  MOZ_ASSERT(i < buff->entsUsed);
+  if (i < N_FIXED_PROF_ENTS) {
+    return buff->entsFixed[i];
+  } else {
+    uintptr_t j     = i - N_FIXED_PROF_ENTS;
+    uintptr_t j_div = j / N_PROF_ENTS_PER_PAGE; /* page number */
+    uintptr_t j_mod = j % N_PROF_ENTS_PER_PAGE; /* page offset */
+    MOZ_ASSERT(buff->entsPages[j_div] != ProfEntsPage_INVALID);
+    return buff->entsPages[j_div]->ents[j_mod];
+  }
+}
+
+
+// Runs in the unwinder thread -- well, this _is_ the unwinder thread.
+static void* unwind_thr_fn(void* exit_nowV)
+{
+  /* If we're the first thread in, we'll need to allocate the buffer
+     array g_buffers plus the Buffer structs that it points at. */
+  spinLock_acquire(&g_spinLock);
+  if (g_buffers == NULL) {
+    /* Drop the lock, make a complete copy in memory, reacquire the
+       lock, and try to install it -- which might fail, if someone
+       else beat us to it. */
+    spinLock_release(&g_spinLock);
+    UnwinderThreadBuffer** buffers
+      = (UnwinderThreadBuffer**)malloc(N_UNW_THR_BUFFERS
+                                        * sizeof(UnwinderThreadBuffer*));
+    MOZ_ASSERT(buffers);
+    int i;
+    for (i = 0; i < N_UNW_THR_BUFFERS; i++) {
+      buffers[i] = (UnwinderThreadBuffer*)
+                   calloc(sizeof(UnwinderThreadBuffer), 1);
+      MOZ_ASSERT(buffers[i]);
+      buffers[i]->state = S_EMPTY;
+    }
+    /* Try to install it */
+    spinLock_acquire(&g_spinLock);
+    if (g_buffers == NULL) {
+      g_buffers = buffers;
+      spinLock_release(&g_spinLock);
+    } else {
+      /* Someone else beat us to it.  Release what we just allocated
+         so as to avoid a leak. */
+      spinLock_release(&g_spinLock);
+      for (i = 0; i < N_UNW_THR_BUFFERS; i++) {
+        free(buffers[i]);
+      }
+      free(buffers);
+    }
+  } else {
+    /* They are already allocated, so just drop the lock and continue. */
+    spinLock_release(&g_spinLock);
+  }
+
+  /* 
+    while (1) {
+      acq lock
+      scan to find oldest full
+         if none { rel lock; sleep; continue }
+      set buff state to emptying
+      rel lock
+      acq MLock // implicitly
+      process buffer
+      rel MLock // implicitly
+      acq lock
+      set buff state to S_EMPTY
+      rel lock
+    }
+  */
+  int* exit_now = (int*)exit_nowV;
+  int ms_to_sleep_if_empty = 1;
+  while (1) {
+
+    if (*exit_now != 0) break;
+
+    spinLock_acquire(&g_spinLock);
+
+    /* Find the oldest filled buffer, if any. */
+    uint64_t oldest_seqNo = ~0ULL; /* infinity */
+    int      oldest_ix    = -1;
+    int      i;
+    for (i = 0; i < N_UNW_THR_BUFFERS; i++) {
+      UnwinderThreadBuffer* buff = g_buffers[i];
+      if (buff->state != S_FULL) continue;
+      if (buff->seqNo < oldest_seqNo) {
+        oldest_seqNo = buff->seqNo;
+        oldest_ix    = i;
+      }
+    }
+    if (oldest_ix == -1) {
+      /* We didn't find a full buffer.  Snooze and try again later. */
+      MOZ_ASSERT(oldest_seqNo == ~0ULL);
+      spinLock_release(&g_spinLock);
+      if (ms_to_sleep_if_empty > 100 && LOGLEVEL >= 2) {
+        LOGF("BPUnw: unwinder: sleep for %d ms", ms_to_sleep_if_empty);
+      }
+      sleep_ms(ms_to_sleep_if_empty);
+      if (ms_to_sleep_if_empty < 20) {
+        ms_to_sleep_if_empty += 2;
+      } else {
+        ms_to_sleep_if_empty = (15 * ms_to_sleep_if_empty) / 10;
+        if (ms_to_sleep_if_empty > 1000)
+          ms_to_sleep_if_empty = 1000;
+      }
+      continue;
+    }
+
+    /* We found a full a buffer.  Mark it as 'ours' and drop the
+       lock; then we can safely throw breakpad at it. */
+    UnwinderThreadBuffer* buff = g_buffers[oldest_ix];
+    MOZ_ASSERT(buff->state == S_FULL);
+    buff->state = S_EMPTYING;
+    spinLock_release(&g_spinLock);
+
+    /* unwind .. in which we can do anything we like, since any
+       resource stalls that we may encounter (eg malloc locks) in
+       competition with signal handler instances, will be short
+       lived since the signal handler is guaranteed nonblocking. */
+    if (0) LOGF("BPUnw: unwinder: seqNo %llu: emptying buf %d\n",
+                (unsigned long long int)oldest_seqNo, oldest_ix);
+
+    /* Copy ProfileEntries presented to us by the sampling thread.
+       Most of them are copied verbatim into |buff->aProfile|,
+       except for 'hint' tags, which direct us to do something
+       different. */
+
+    /* Need to lock |aProfile| so nobody tries to copy out entries
+       whilst we are putting them in. */
+    buff->aProfile->GetMutex()->Lock();
+
+    /* The buff is a sequence of ProfileEntries (ents).  It has
+       this grammar:
+
+       | --pre-tags-- | (h 'P' .. h 'Q')* | --post-tags-- |
+                        ^               ^
+                        ix_first_hP     ix_last_hQ
+
+       Each (h 'P' .. h 'Q') subsequence represents one pseudostack
+       entry.  These, if present, are in the order
+       outermost-frame-first, and that is the order that they should
+       be copied into aProfile.  The --pre-tags-- and --post-tags--
+       are to be copied into the aProfile verbatim, except that they
+       may contain the hints "h 'F'" for a flush and "h 'N'" to
+       indicate that a native unwind is also required, and must be
+       interleaved with the pseudostack entries.
+
+       The hint tags that bound each pseudostack entry, "h 'P'" and "h
+       'Q'", are not to be copied into the aProfile -- they are
+       present only to make parsing easy here.  Also, the pseudostack
+       entries may contain an "'S' (void*)" entry, which is the stack
+       pointer value for that entry, and these are also not to be
+       copied.
+    */
+    /* The first thing to do is therefore to find the pseudostack
+       entries, if any, and to find out also whether a native unwind
+       has been requested. */
+    const uintptr_t infUW = ~(uintptr_t)0; // infinity
+    bool  need_native_unw = false;
+    uintptr_t ix_first_hP = infUW; // "not found"
+    uintptr_t ix_last_hQ  = infUW; // "not found"
+
+    uintptr_t k;
+    for (k = 0; k < buff->entsUsed; k++) {
+      ProfileEntry2 ent = utb_get_profent(buff, k);
+      if (ent.is_ent_hint('N')) {
+        need_native_unw = true;
+      }
+      else if (ent.is_ent_hint('P') && ix_first_hP == ~(uintptr_t)0) {
+        ix_first_hP = k;
+      }
+      else if (ent.is_ent_hint('Q')) {
+        ix_last_hQ = k;
+      }
+    }
+
+    if (0) LOGF("BPUnw: ix_first_hP %llu  ix_last_hQ %llu  need_native_unw %llu",
+                (unsigned long long int)ix_first_hP,
+                (unsigned long long int)ix_last_hQ,
+                (unsigned long long int)need_native_unw);
+
+    /* There are four possibilities: native-only, pseudostack-only,
+       combined (both), and neither.  We handle all four cases. */
+
+    MOZ_ASSERT( (ix_first_hP == infUW && ix_last_hQ == infUW) ||
+                (ix_first_hP != infUW && ix_last_hQ != infUW) );
+    bool have_P = ix_first_hP != infUW;
+    if (have_P) {
+      MOZ_ASSERT(ix_first_hP < ix_last_hQ);
+      MOZ_ASSERT(ix_last_hQ <= buff->entsUsed);
+    }
+
+    /* Neither N nor P.  This is very unusual but has been observed to happen.
+       Just copy to the output. */
+    if (!need_native_unw && !have_P) {
+      for (k = 0; k < buff->entsUsed; k++) {
+        ProfileEntry2 ent = utb_get_profent(buff, k);
+        // action flush-hints
+        if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
+        // skip ones we can't copy
+        if (ent.is_ent_hint() || ent.is_ent('S')) { continue; }
+        // and copy everything else
+        buff->aProfile->addTag( ent );
+      }
+    }
+    else /* Native only-case. */
+    if (need_native_unw && !have_P) {
+      for (k = 0; k < buff->entsUsed; k++) {
+        ProfileEntry2 ent = utb_get_profent(buff, k);
+        // action a native-unwind-now hint
+        if (ent.is_ent_hint('N')) {
+          MOZ_ASSERT(buff->haveNativeInfo);
+          PCandSP* pairs = NULL;
+          unsigned int nPairs = 0;
+          do_breakpad_unwind_Buffer(&pairs, &nPairs, buff, oldest_ix);
+          buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
+          for (unsigned int i = 0; i < nPairs; i++) {
+            buff->aProfile
+                ->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[i].pc)) );
+          }
+          if (pairs)
+            free(pairs);
+          continue;
+        }
+        // action flush-hints
+        if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
+        // skip ones we can't copy
+        if (ent.is_ent_hint() || ent.is_ent('S')) { continue; }
+        // and copy everything else
+        buff->aProfile->addTag( ent );
+      }
+    }
+    else /* Pseudostack-only case */
+    if (!need_native_unw && have_P) {
+      /* If there's no request for a native stack, it's easy: just
+         copy the tags verbatim into aProfile, skipping the ones that
+         can't be copied -- 'h' (hint) tags, and "'S' (void*)"
+         stack-pointer tags.  Except, insert a sample-start tag when
+         we see the start of the first pseudostack frame. */
+      for (k = 0; k < buff->entsUsed; k++) {
+        ProfileEntry2 ent = utb_get_profent(buff, k);
+        // We need to insert a sample-start tag before the first frame
+        if (k == ix_first_hP) {
+          buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
+        }
+        // action flush-hints
+        if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
+        // skip ones we can't copy
+        if (ent.is_ent_hint() || ent.is_ent('S')) { continue; }
+        // and copy everything else
+        buff->aProfile->addTag( ent );
+      }
+    }
+    else /* Combined case */
+    if (need_native_unw && have_P)
+    {
+      /* We need to get a native stacktrace and merge it with the
+         pseudostack entries.  This isn't too simple.  First, copy all
+         the tags up to the start of the pseudostack tags.  Then
+         generate a combined set of tags by native unwind and
+         pseudostack.  Then, copy all the stuff after the pseudostack
+         tags. */
+      MOZ_ASSERT(buff->haveNativeInfo);
+
+      // Get native unwind info
+      PCandSP* pairs = NULL;
+      unsigned int n_pairs = 0;
+      do_breakpad_unwind_Buffer(&pairs, &n_pairs, buff, oldest_ix);
+
+      // Entries before the pseudostack frames
+      for (k = 0; k < ix_first_hP; k++) {
+        ProfileEntry2 ent = utb_get_profent(buff, k);
+        // action flush-hints
+        if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
+        // skip ones we can't copy
+        if (ent.is_ent_hint() || ent.is_ent('S')) { continue; }
+        // and copy everything else
+        buff->aProfile->addTag( ent );
+      }
+
+      // BEGIN merge
+      buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
+      unsigned int next_N = 0; // index in pairs[]
+      unsigned int next_P = ix_first_hP; // index in buff profent array
+      bool last_was_P = false;
+      if (0) LOGF("at mergeloop: n_pairs %llu ix_last_hQ %llu",
+                  (unsigned long long int)n_pairs,
+                  (unsigned long long int)ix_last_hQ);
+      while (true) {
+        if (next_P <= ix_last_hQ) {
+          // Assert that next_P points at the start of an P entry
+          MOZ_ASSERT(utb_get_profent(buff, next_P).is_ent_hint('P'));
+        }
+        if (next_N >= n_pairs && next_P > ix_last_hQ) {
+          // both stacks empty
+          break;
+        }
+        /* Decide which entry to use next:
+           If N is empty, must use P, and vice versa
+           else
+           If the last was P and current P has zero SP, use P
+           else
+           we assume that both P and N have valid SP, in which case
+              use the one with the larger value
+        */
+        bool use_P = true;
+        if (next_N >= n_pairs) {
+          // N empty, use P
+          use_P = true;
+          if (0) LOG("  P  <=  no remaining N entries");
+        }
+        else if (next_P > ix_last_hQ) {
+          // P empty, use N
+          use_P = false;
+          if (0) LOG("  N  <=  no remaining P entries");
+        }
+        else {
+          // We have at least one N and one P entry available.
+          // Scan forwards to find the SP of the current P entry
+          u_int64_t sp_cur_P = 0;
+          unsigned int m = next_P + 1;
+          while (1) {
+            /* This assertion should hold because in a well formed
+               input, we must eventually find the hint-Q that marks
+               the end of this frame's entries. */
+            MOZ_ASSERT(m < buff->entsUsed);
+            ProfileEntry2 ent = utb_get_profent(buff, m);
+            if (ent.is_ent_hint('Q'))
+              break;
+            if (ent.is_ent('S')) {
+              sp_cur_P = reinterpret_cast<u_int64_t>(ent.get_tagPtr());
+              break;
+            }
+            m++;
+          }
+          if (last_was_P && sp_cur_P == 0) {
+            if (0) LOG("  P  <=  last_was_P && sp_cur_P == 0");
+            use_P = true;
+          } else {
+            u_int64_t sp_cur_N = pairs[next_N].sp;
+            use_P = (sp_cur_P > sp_cur_N);
+            if (0) LOGF("  %s  <=  sps P %p N %p",
+                        use_P ? "P" : "N", (void*)(intptr_t)sp_cur_P, 
+                                           (void*)(intptr_t)sp_cur_N);
+          }
+        }
+        /* So, we know which we are going to use. */
+        if (use_P) {
+          unsigned int m = next_P + 1;
+          while (true) {
+            MOZ_ASSERT(m < buff->entsUsed);
+            ProfileEntry2 ent = utb_get_profent(buff, m);
+            if (ent.is_ent_hint('Q')) {
+              next_P = m + 1;
+              break;
+            }
+            // we don't expect a flush-hint here
+            MOZ_ASSERT(!ent.is_ent_hint('F'));
+            // skip ones we can't copy
+            if (ent.is_ent_hint() || ent.is_ent('S')) { m++; continue; }
+            // and copy everything else
+            buff->aProfile->addTag( ent );
+            m++;
+          }
+        } else {
+          buff->aProfile
+              ->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[next_N].pc)) );
+          next_N++;
+        }
+        /* Remember what we chose, for next time. */
+        last_was_P = use_P;
+      }
+
+      MOZ_ASSERT(next_P == ix_last_hQ + 1);
+      MOZ_ASSERT(next_N == n_pairs);
+      // END merge
+
+      // Entries after the pseudostack frames
+      for (k = ix_last_hQ+1; k < buff->entsUsed; k++) {
+        ProfileEntry2 ent = utb_get_profent(buff, k);
+        // action flush-hints
+        if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; }
+        // skip ones we can't copy
+        if (ent.is_ent_hint() || ent.is_ent('S')) { continue; }
+        // and copy everything else
+        buff->aProfile->addTag( ent );
+      }
+
+      // free native unwind info
+      if (pairs)
+        free(pairs);
+    }
+
+#if 0
+    bool show = true;
+    if (show) LOG("----------------");
+    for (k = 0; k < buff->entsUsed; k++) {
+      ProfileEntry2 ent = utb_get_profent(buff, k);
+      if (show) ent.log();
+      if (ent.is_ent_hint('F')) {
+        /* This is a flush-hint */
+        buff->aProfile->flush();
+      } 
+      else if (ent.is_ent_hint('N')) {
+        /* This is a do-a-native-unwind-right-now hint */
+        MOZ_ASSERT(buff->haveNativeInfo);
+        PCandSP* pairs = NULL;
+        unsigned int nPairs = 0;
+        do_breakpad_unwind_Buffer(&pairs, &nPairs, buff, oldest_ix);
+        buff->aProfile->addTag( ProfileEntry2('s', "(root)") );
+        for (unsigned int i = 0; i < nPairs; i++) {
+          buff->aProfile
+              ->addTag( ProfileEntry2('l', reinterpret_cast<void*>(pairs[i].pc)) );
+        }
+        if (pairs)
+          free(pairs);
+      } else {
+        /* Copy in verbatim */
+        buff->aProfile->addTag( ent );
+      }
+    }
+#endif
+
+    buff->aProfile->GetMutex()->Unlock();
+
+    /* And .. we're done.  Mark the buffer as empty so it can be
+       reused.  First though, unmap any of the entsPages that got
+       mapped during filling. */
+    for (i = 0; i < N_PROF_ENT_PAGES; i++) {
+      if (buff->entsPages[i] == ProfEntsPage_INVALID)
+        continue;
+      munmap_ProfEntsPage(buff->entsPages[i]);
+      buff->entsPages[i] = ProfEntsPage_INVALID;
+    }
+
+    (void)VALGRIND_MAKE_MEM_UNDEFINED(&buff->stackImg[0], N_STACK_BYTES);
+    spinLock_acquire(&g_spinLock);
+    MOZ_ASSERT(buff->state == S_EMPTYING);
+    buff->state = S_EMPTY;
+    spinLock_release(&g_spinLock);
+    ms_to_sleep_if_empty = 1;
+  }
+  return NULL;
+}
+
+
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////
+
+/* After this point, we have some classes that interface with
+   breakpad, that allow us to pass in a Buffer and get an unwind of
+   it. */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+#include <fstream>
+#include <sstream>
+
+#include "google_breakpad/common/minidump_format.h"
+#include "google_breakpad/processor/call_stack.h"
+#include "google_breakpad/processor/stack_frame_cpu.h"
+#include "local_debug_info_symbolizer.h"
+#include "processor/stackwalker_amd64.h"
+#include "processor/stackwalker_arm.h"
+#include "processor/stackwalker_x86.h"
+#include "processor/logging.h"
+#include "common/linux/dump_symbols.h"
+
+#include "google_breakpad/processor/memory_region.h"
+#include "google_breakpad/processor/code_modules.h"
+
+google_breakpad::MemoryRegion* foo = NULL;
+
+using std::string;
+
+///////////////////////////////////////////////////////////////////
+/* Implement MemoryRegion, so that it hauls stack image data out of
+   the stack top snapshots that the signal handler has so carefully
+   snarfed. */
+
+// BEGIN: DERIVED FROM src/processor/stackwalker_selftest.cc
+//
+class BufferMemoryRegion : public google_breakpad::MemoryRegion {
+ public:
+  // We just keep hold of the Buffer* we're given, but make no attempt
+  // to take allocation-ownership of it.
+  BufferMemoryRegion(UnwinderThreadBuffer* buff) : buff_(buff) { }
+  ~BufferMemoryRegion() { }
+
+  u_int64_t GetBase() const { return (uintptr_t)buff_->stackImgAddr; }
+  u_int32_t GetSize() const { return (uintptr_t)buff_->stackImgUsed; }
+
+  bool GetMemoryAtAddress(u_int64_t address, u_int8_t*  value) const {
+      return GetMemoryAtAddressInternal(address, value); }
+  bool GetMemoryAtAddress(u_int64_t address, u_int16_t* value) const {
+      return GetMemoryAtAddressInternal(address, value); }
+  bool GetMemoryAtAddress(u_int64_t address, u_int32_t* value) const {
+      return GetMemoryAtAddressInternal(address, value); }
+  bool GetMemoryAtAddress(u_int64_t address, u_int64_t* value) const {
+      return GetMemoryAtAddressInternal(address, value); }
+
+ private:
+  template<typename T> bool GetMemoryAtAddressInternal (
+                               u_int64_t address, T* value) const {
+    /* Range check .. */
+    if ( buff_->stackImgUsed >= sizeof(T)
+         && ((uintptr_t)address) >= ((uintptr_t)buff_->stackImgAddr)
+         && ((uintptr_t)address) <= ((uintptr_t)buff_->stackImgAddr)
+                                     + buff_->stackImgUsed
+                                     - sizeof(T) ) {
+      uintptr_t offset = (uintptr_t)address - (uintptr_t)buff_->stackImgAddr;
+      if (0) LOGF("GMAA %llx ok", (unsigned long long int)address);
+      *value = *reinterpret_cast<const T*>(&buff_->stackImg[offset]);
+      return true;
+    } else {
+      if (0) LOGF("GMAA %llx failed", (unsigned long long int)address);
+      return false;
+    }
+  }
+
+  // where this all comes from
+  UnwinderThreadBuffer* buff_;
+};
+//
+// END: DERIVED FROM src/processor/stackwalker_selftest.cc
+
+
+///////////////////////////////////////////////////////////////////
+/* Implement MyCodeModule and MyCodeModules, so they pull the relevant
+   information about which modules are loaded where out of
+   /proc/self/maps. */
+
+class MyCodeModule : public google_breakpad::CodeModule {
+public:
+  MyCodeModule(u_int64_t x_start, u_int64_t x_len, string filename)
+    : x_start_(x_start), x_len_(x_len), filename_(filename) {
+    MOZ_ASSERT(x_len > 0);
+  }
+
+  ~MyCodeModule() {}
+
+  // The base address of this code module as it was loaded by the process.
+  // (u_int64_t)-1 on error.
+  u_int64_t base_address() const { return x_start_; }
+
+  // The size of the code module.  0 on error.
+  u_int64_t size() const { return x_len_; }
+
+  // The path or file name that the code module was loaded from.  Empty on
+  // error.
+  string code_file() const { return filename_; }
+
+  // An identifying string used to discriminate between multiple versions and
+  // builds of the same code module.  This may contain a uuid, timestamp,
+  // version number, or any combination of this or other information, in an
+  // implementation-defined format.  Empty on error.
+  string code_identifier() const { MOZ_CRASH(); return ""; }
+
+  // The filename containing debugging information associated with the code
+  // module.  If debugging information is stored in a file separate from the
+  // code module itself (as is the case when .pdb or .dSYM files are used),
+  // this will be different from code_file.  If debugging information is
+  // stored in the code module itself (possibly prior to stripping), this
+  // will be the same as code_file.  Empty on error.
+  string debug_file() const { MOZ_CRASH(); return ""; }
+
+  // An identifying string similar to code_identifier, but identifies a
+  // specific version and build of the associated debug file.  This may be
+  // the same as code_identifier when the debug_file and code_file are
+  // identical or when the same identifier is used to identify distinct
+  // debug and code files.
+  string debug_identifier() const { MOZ_CRASH(); return ""; }
+
+  // A human-readable representation of the code module's version.  Empty on
+  // error.
+  string version() const { MOZ_CRASH(); return ""; }
+
+  // Creates a new copy of this CodeModule object, which the caller takes
+  // ownership of.  The new CodeModule may be of a different concrete class
+  // than the CodeModule being copied, but will behave identically to the
+  // copied CodeModule as far as the CodeModule interface is concerned.
+  const CodeModule* Copy() const { MOZ_CRASH(); return NULL; }
+
+ private:
+  // record info for a file backed executable mapping
+  // snarfed from /proc/self/maps
+  u_int64_t x_start_;
+  u_int64_t x_len_;    // may not be zero
+  string    filename_; // of the mapped file
+};
+
+
+/* Find out, in a platform-dependent way, where the code modules got
+   mapped in the process' virtual address space, and add them to
+   |mods_|. */
+static void read_procmaps(std::vector<MyCodeModule*>& mods_)
+{
+  MOZ_ASSERT(mods_.size() == 0);
+
+#if defined(SPS_OS_linux) || defined(SPS_OS_android)
+  // read /proc/self/maps and create a vector of CodeModule*
+  FILE* f = fopen("/proc/self/maps", "r");
+  MOZ_ASSERT(f);
+  while (!feof(f)) {
+    unsigned long long int start = 0;
+    unsigned long long int end   = 0;
+    char rr = ' ', ww = ' ', xx = ' ', pp = ' ';
+    unsigned long long int offset = 0, inode = 0;
+    unsigned int devMaj = 0, devMin = 0;
+    int nItems = fscanf(f, "%llx-%llx %c%c%c%c %llx %x:%x %llu",
+                        &start, &end, &rr, &ww, &xx, &pp,
+                        &offset, &devMaj, &devMin, &inode);
+    if (nItems == EOF && feof(f)) break;
+    MOZ_ASSERT(nItems == 10);
+    MOZ_ASSERT(start < end);
+    // read the associated file name, if it is present
+    int ch;
+    // find '/' or EOL
+    while (1) {
+      ch = fgetc(f);
+      MOZ_ASSERT(ch != EOF);
+      if (ch == '\n' || ch == '/') break;
+    }
+    string fname("");
+    if (ch == '/') {
+      fname += (char)ch;
+      while (1) {
+        ch = fgetc(f);
+        MOZ_ASSERT(ch != EOF);
+        if (ch == '\n') break;
+        fname += (char)ch;
+      }
+    }
+    MOZ_ASSERT(ch == '\n');
+    if (0) LOGF("SEG %llx %llx %c %c %c %c %s",
+                start, end, rr, ww, xx, pp, fname.c_str() );
+    if (xx == 'x' && fname != "") {
+      MyCodeModule* cm = new MyCodeModule( start, end-start, fname );
+      mods_.push_back(cm);
+    }
+  }
+  fclose(f);
+
+#elif defined(SPS_OS_darwin)
+
+# if defined(SPS_PLAT_amd64_darwin)
+  typedef mach_header_64 breakpad_mach_header;
+  typedef segment_command_64 breakpad_mach_segment_command;
+  const int LC_SEGMENT_XX = LC_SEGMENT_64;
+# else // SPS_PLAT_x86_darwin
+  typedef mach_header breakpad_mach_header;
+  typedef segment_command breakpad_mach_segment_command;
+  const int LC_SEGMENT_XX = LC_SEGMENT;
+# endif
+
+  uint32_t n_images = _dyld_image_count();
+  for (uint32_t ix = 0; ix < n_images; ix++) {
+
+    MyCodeModule* cm = NULL;
+    unsigned long slide = _dyld_get_image_vmaddr_slide(ix);
+    const char* name = _dyld_get_image_name(ix);
+    const breakpad_mach_header* header
+      = (breakpad_mach_header*)_dyld_get_image_header(ix);
+    if (!header)
+      continue;
+
+    const struct load_command *cmd =
+      reinterpret_cast<const struct load_command *>(header + 1);
+
+    /* Look through the MachO headers to find out the module's stated
+       VMA, so we can add it to the slide to find its actual VMA.
+       Copied from MinidumpGenerator::WriteModuleStream
+       src/client/mac/handler/minidump_generator.cc. */
+    for (unsigned int i = 0; cmd && (i < header->ncmds); i++) {
+      if (cmd->cmd == LC_SEGMENT_XX) {
+
+        const breakpad_mach_segment_command *seg =
+          reinterpret_cast<const breakpad_mach_segment_command *>(cmd);
+
+        if (!strcmp(seg->segname, "__TEXT")) {
+          cm = new MyCodeModule( seg->vmaddr + slide,
+                                 seg->vmsize, string(name) );
+          break;
+        }
+      }
+      cmd = reinterpret_cast<struct load_command*>((char *)cmd + cmd->cmdsize);
+    }
+    if (cm) {
+      mods_.push_back(cm);
+      if (0) LOGF("SEG %llx %llx %s",
+                  cm->base_address(), cm->base_address() + cm->size(),
+                  cm->code_file().c_str());
+    }
+  }
+
+#else
+# error "Unknown platform"
+#endif
+
+  if (0) LOGF("got %d mappings\n", (int)mods_.size());
+}
+
+
+class MyCodeModules : public google_breakpad::CodeModules
+{
+ public:
+  MyCodeModules() {
+    read_procmaps(mods_);
+  }
+
+  ~MyCodeModules() {
+    std::vector<MyCodeModule*>::const_iterator it;
+    for (it = mods_.begin(); it < mods_.end(); it++) {
+      MyCodeModule* cm = *it;
+      delete cm;
+    }
+  }
+
+ private:
+  std::vector<MyCodeModule*> mods_;
+
+  unsigned int module_count() const { MOZ_CRASH(); return 1; }
+
+  const google_breakpad::CodeModule*
+                GetModuleForAddress(u_int64_t address) const
+  {
+    if (0) printf("GMFA %llx\n", (unsigned long long int)address);
+    std::vector<MyCodeModule*>::const_iterator it;
+    for (it = mods_.begin(); it < mods_.end(); it++) {
+       MyCodeModule* cm = *it;
+       if (0) printf("considering %p  %llx +%llx\n",
+                     (void*)cm, (unsigned long long int)cm->base_address(),
+                                (unsigned long long int)cm->size());
+       if (cm->base_address() <= address
+           && address < cm->base_address() + cm->size())
+          return cm;
+    }
+    return NULL;
+  }
+
+  const google_breakpad::CodeModule* GetMainModule() const {
+    MOZ_CRASH(); return NULL; return NULL;
+  }
+
+  const google_breakpad::CodeModule* GetModuleAtSequence(
+                unsigned int sequence) const {
+    MOZ_CRASH(); return NULL;
+  }
+
+  const google_breakpad::CodeModule* GetModuleAtIndex(unsigned int index) const {
+    MOZ_CRASH(); return NULL;
+  }
+
+  const CodeModules* Copy() const {
+    MOZ_CRASH(); return NULL;
+  }
+};
+
+///////////////////////////////////////////////////////////////////
+/* Top level interface to breakpad.  Given a Buffer* as carefully
+   acquired by the signal handler and later handed to this thread,
+   unwind it.
+
+   The first time in, read /proc/self/maps.  TODO: what about if it
+   changes as we go along?
+
+   Dump the result (PC, SP) pairs in a malloc-allocated array of
+   PCandSPs, and return that and its length to the caller.  Caller is
+   responsible for deallocating it.
+
+   The first pair is for the outermost frame, the last for the
+   innermost frame.
+*/
+
+MyCodeModules* sModules = NULL;
+google_breakpad::LocalDebugInfoSymbolizer* sSymbolizer = NULL;
+
+void do_breakpad_unwind_Buffer(/*OUT*/PCandSP** pairs,
+                               /*OUT*/unsigned int* nPairs,
+                               UnwinderThreadBuffer* buff,
+                               int buffNo /* for debug printing only */)
+{
+# if defined(SPS_ARCH_amd64)
+  MDRawContextAMD64* context = new MDRawContextAMD64();
+  memset(context, 0, sizeof(*context));
+
+  context->rip = buff->regs.rip;
+  context->rbp = buff->regs.rbp;
+  context->rsp = buff->regs.rsp;
+
+  if (0) {
+    LOGF("Initial RIP = 0x%llx", (unsigned long long int)context->rip);
+    LOGF("Initial RSP = 0x%llx", (unsigned long long int)context->rsp);
+    LOGF("Initial RBP = 0x%llx", (unsigned long long int)context->rbp);
+  }
+
+# elif defined(SPS_ARCH_arm)
+  MDRawContextARM* context = new MDRawContextARM();
+  memset(context, 0, sizeof(*context));
+
+  context->iregs[7]                     = buff->regs.r7;
+  context->iregs[12]                    = buff->regs.r12;
+  context->iregs[MD_CONTEXT_ARM_REG_PC] = buff->regs.r15;
+  context->iregs[MD_CONTEXT_ARM_REG_LR] = buff->regs.r14;
+  context->iregs[MD_CONTEXT_ARM_REG_SP] = buff->regs.r13;
+  context->iregs[MD_CONTEXT_ARM_REG_FP] = buff->regs.r11;
+
+  if (0) {
+    LOGF("Initial R15 = 0x%x",
+         context->iregs[MD_CONTEXT_ARM_REG_PC]);
+    LOGF("Initial R13 = 0x%x",
+         context->iregs[MD_CONTEXT_ARM_REG_SP]);
+  }
+
+# elif defined(SPS_ARCH_x86)
+  MDRawContextX86* context = new MDRawContextX86();
+  memset(context, 0, sizeof(*context));
+
+  context->eip = buff->regs.eip;
+  context->ebp = buff->regs.ebp;
+  context->esp = buff->regs.esp;
+
+  if (0) {
+    LOGF("Initial EIP = 0x%x", context->eip);
+    LOGF("Initial ESP = 0x%x", context->esp);
+    LOGF("Initial EBP = 0x%x", context->ebp);
+  }
+
+# else
+#   error "Unknown plat"
+# endif
+
+  BufferMemoryRegion* memory = new BufferMemoryRegion(buff);
+
+  if (!sModules) {
+     sModules = new MyCodeModules();
+  }
+
+  if (!sSymbolizer) {
+    /* Make up a list of places where the debug objects might be. */
+    std::vector<std::string> debug_dirs;
+#   if defined(SPS_OS_linux)
+    debug_dirs.push_back("/usr/lib/debug/lib");
+    debug_dirs.push_back("/usr/lib/debug/usr/lib");
+    debug_dirs.push_back("/usr/lib/debug/lib/x86_64-linux-gnu");
+    debug_dirs.push_back("/usr/lib/debug/usr/lib/x86_64-linux-gnu");
+#   elif defined(SPS_OS_android)
+    debug_dirs.push_back("/sdcard/symbols/system/lib");
+    debug_dirs.push_back("/sdcard/symbols/system/bin");
+#   elif defined(SPS_OS_darwin)
+    /* Nothing */
+#   else
+#     error "Unknown plat"
+#   endif
+    sSymbolizer = new google_breakpad::LocalDebugInfoSymbolizer(debug_dirs);
+  }
+
+# if defined(SPS_ARCH_amd64)
+  google_breakpad::StackwalkerAMD64* sw
+   = new google_breakpad::StackwalkerAMD64(NULL, context,
+                                           memory, sModules,
+                                           sSymbolizer);
+# elif defined(SPS_ARCH_arm)
+  google_breakpad::StackwalkerARM* sw
+   = new google_breakpad::StackwalkerARM(NULL, context,
+                                         -1/*FP reg*/,
+                                         memory, sModules,
+                                         sSymbolizer);
+# elif defined(SPS_ARCH_x86)
+  google_breakpad::StackwalkerX86* sw
+   = new google_breakpad::StackwalkerX86(NULL, context,
+                                         memory, sModules,
+                                         sSymbolizer);
+# else
+#   error "Unknown plat"
+# endif
+
+  google_breakpad::CallStack* stack = new google_breakpad::CallStack();
+
+  std::vector<const google_breakpad::CodeModule*>* modules_without_symbols
+    = new std::vector<const google_breakpad::CodeModule*>();
+  bool b = sw->Walk(stack, modules_without_symbols);
+  (void)b;
+  delete modules_without_symbols;
+
+  unsigned int n_frames = stack->frames()->size();
+  unsigned int n_frames_good = 0;
+
+  *pairs  = (PCandSP*)malloc(n_frames * sizeof(PCandSP));
+  *nPairs = n_frames;
+  if (*pairs == NULL) {
+    *nPairs = 0;
+    return;
+  }
+
+  if (n_frames > 0) {
+    for (unsigned int frame_index = 0; 
+         frame_index < n_frames; ++frame_index) {
+      google_breakpad::StackFrame *frame = stack->frames()->at(frame_index);
+
+      if (frame->trust == google_breakpad::StackFrame::FRAME_TRUST_CFI
+          || frame->trust == google_breakpad::StackFrame::FRAME_TRUST_CONTEXT) {
+        n_frames_good++;
+      }
+
+#     if defined(SPS_ARCH_amd64)
+      google_breakpad::StackFrameAMD64* frame_amd64
+        = reinterpret_cast<google_breakpad::StackFrameAMD64*>(frame);
+      if (LOGLEVEL >= 4) {
+        LOGF("frame %d   rip=0x%016llx rsp=0x%016llx    %s", 
+             frame_index,
+             (unsigned long long int)frame_amd64->context.rip, 
+             (unsigned long long int)frame_amd64->context.rsp, 
+             frame_amd64->trust_description().c_str());
+      }
+      (*pairs)[n_frames-1-frame_index].pc = frame_amd64->context.rip;
+      (*pairs)[n_frames-1-frame_index].sp = frame_amd64->context.rsp;
+
+#     elif defined(SPS_ARCH_arm)
+      google_breakpad::StackFrameARM* frame_arm
+        = reinterpret_cast<google_breakpad::StackFrameARM*>(frame);
+      if (LOGLEVEL >= 4) {
+        LOGF("frame %d   0x%08x   %s",
+             frame_index,
+             frame_arm->context.iregs[MD_CONTEXT_ARM_REG_PC],
+             frame_arm->trust_description().c_str());
+      }
+      (*pairs)[n_frames-1-frame_index].pc
+        = frame_arm->context.iregs[MD_CONTEXT_ARM_REG_PC];
+      (*pairs)[n_frames-1-frame_index].sp
+        = frame_arm->context.iregs[MD_CONTEXT_ARM_REG_SP];
+
+#     elif defined(SPS_ARCH_x86)
+      google_breakpad::StackFrameX86* frame_x86
+        = reinterpret_cast<google_breakpad::StackFrameX86*>(frame);
+      if (LOGLEVEL >= 4) {
+        LOGF("frame %d   eip=0x%08x rsp=0x%08x    %s", 
+             frame_index,
+             frame_x86->context.eip, frame_x86->context.esp, 
+             frame_x86->trust_description().c_str());
+      }
+      (*pairs)[n_frames-1-frame_index].pc = frame_x86->context.eip;
+      (*pairs)[n_frames-1-frame_index].sp = frame_x86->context.esp;
+
+#     else
+#       error "Unknown plat"
+#     endif
+    }
+  }
+
+  if (LOGLEVEL >= 3) {
+    LOGF("BPUnw: unwinder: seqNo %llu, buf %d: got %u frames "
+         "(%u trustworthy)", 
+         (unsigned long long int)buff->seqNo, buffNo, n_frames, n_frames_good);
+  }
+
+  if (LOGLEVEL >= 2) {
+    if (0 == (g_stats_totalSamples % 1000))
+      LOGF("BPUnw: %llu total samples, %llu failed due to buffer unavail",
+           (unsigned long long int)g_stats_totalSamples,
+           (unsigned long long int)g_stats_noBuffAvail);
+  }
+
+  delete stack;
+  delete sw;
+  delete memory;
+  delete context;
+}
+
+#endif /* defined(SPS_OS_windows) */
new file mode 100644
--- /dev/null
+++ b/tools/profiler/UnwinderThread2.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MOZ_UNWINDER_THREAD_2_H
+#define MOZ_UNWINDER_THREAD_2_H
+
+#include "sps_sampler.h"
+#include "ProfileEntry2.h"
+
+/* Top level exports of UnwinderThread.cpp. */
+
+// Abstract type.  A buffer which is used to transfer information between
+// the sampled thread(s) and the unwinder thread(s).
+typedef
+  struct _UnwinderThreadBuffer 
+  UnwinderThreadBuffer;
+
+// RUNS IN SIGHANDLER CONTEXT
+// Called in the sampled thread (signal) context.  Adds a ProfileEntry2
+// into an UnwinderThreadBuffer that the thread has previously obtained
+// by a call to utb__acquire_empty_buffer.
+void utb__addEntry(/*MOD*/UnwinderThreadBuffer* utb,
+                   ProfileEntry2 ent);
+
+// Create the unwinder thread.  At the moment there can be only one.
+void uwt__init();
+
+// Request the unwinder thread to exit, and wait until it has done so.
+void uwt__deinit();
+
+// Registers a sampler thread for profiling.  Threads must be registered
+// before they are allowed to call utb__acquire_empty_buffer or
+// utb__release_full_buffer.
+void uwt__register_thread_for_profiling(void* stackTop);
+
+// RUNS IN SIGHANDLER CONTEXT
+// Called in the sampled thread (signal) context.  Get an empty buffer
+// into which ProfileEntries can be put.  It may return NULL if no
+// empty buffers can be found, which will be the case if the unwinder
+// thread(s) have fallen behind for some reason.  In this case the
+// sampled thread must simply give up and return from the signal handler
+// immediately, else it risks deadlock.
+UnwinderThreadBuffer* uwt__acquire_empty_buffer();
+
+// RUNS IN SIGHANDLER CONTEXT
+// Called in the sampled thread (signal) context.  Release a buffer
+// that the sampled thread has acquired, handing the contents to
+// the unwinder thread, and, if necessary, passing sufficient
+// information (stack top chunk, + registers) to also do a native
+// unwind.  If 'ucV' is NULL, no native unwind is done.  If non-NULL,
+// it is assumed to point to a ucontext_t* that holds the initial 
+// register state for the unwind.  The results of all of this are
+// dumped into |aProfile| (by the unwinder thread, not the calling thread).
+void uwt__release_full_buffer(ThreadProfile2* aProfile,
+                              UnwinderThreadBuffer* utb,
+                              void* /* ucontext_t*, really */ ucV);
+
+#endif /* ndef MOZ_UNWINDER_THREAD_2_H */
new file mode 100644
--- /dev/null
+++ b/tools/profiler/local_debug_info_symbolizer.cc
@@ -0,0 +1,160 @@
+
+#include "PlatformMacros.h"
+
+#if !defined(SPS_OS_windows)
+# include "common/module.h"
+# include "processor/cfi_frame_info.h"
+#endif
+#include "google_breakpad/processor/code_module.h"
+#include "google_breakpad/processor/code_modules.h"
+#include "google_breakpad/processor/stack_frame.h"
+#include "processor/logging.h"
+#include "common/scoped_ptr.h"
+
+#if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \
+    || defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android)
+# include "common/linux/dump_symbols.h"
+#elif defined(SPS_PLAT_amd64_darwin) || defined(SPS_PLAT_x86_darwin)
+# include "shim_mac_dump_syms.h"
+#elif defined(SPS_OS_windows)
+  /* This is all stubbed out anyway, so don't do anything. */
+#else
+# error "Unknown platform"
+#endif
+
+#include "platform.h"
+#include "local_debug_info_symbolizer.h"
+
+namespace google_breakpad {
+
+LocalDebugInfoSymbolizer::~LocalDebugInfoSymbolizer() {
+# if !defined(SPS_OS_windows)
+  for (SymbolMap::iterator it = symbols_.begin();
+       it != symbols_.end();
+       ++it) {
+    delete it->second;
+  }
+# endif
+}
+
+StackFrameSymbolizer::SymbolizerResult
+LocalDebugInfoSymbolizer::FillSourceLineInfo(const CodeModules* modules,
+                                             const SystemInfo* system_info,
+                                             StackFrame* frame) {
+  if (!modules) {
+    return kError;
+  }
+  const CodeModule* module = modules->GetModuleForAddress(frame->instruction);
+  if (!module) {
+    return kError;
+  }
+  frame->module = module;
+
+# if !defined(SPS_OS_windows)
+  Module* debug_info_module = NULL;
+  SymbolMap::const_iterator it = symbols_.find(module->code_file());
+  if (it == symbols_.end()) {
+    if (no_symbol_modules_.find(module->code_file()) !=
+        no_symbol_modules_.end()) {
+      return kNoError;
+    }
+    LOG("BPUnw:");
+    LOGF("BPUnw: ReadSymbolData: BEGIN   %s", module->code_file().c_str());
+    if (!ReadSymbolData(module->code_file(),
+                        debug_dirs_,
+                        ONLY_CFI,
+                        &debug_info_module)) {
+      BPLOG(ERROR) << "ReadSymbolData failed for " << module->code_file();
+      LOGF("BPUnw: ReadSymbolData: FAIL    %s", module->code_file().c_str());
+      if (debug_info_module)
+        delete debug_info_module;
+      no_symbol_modules_.insert(module->code_file());
+      return kNoError;
+    }
+
+    LOGF("BPUnw: ReadSymbolData: SUCCESS %s", module->code_file().c_str());
+    symbols_[module->code_file()] = debug_info_module;
+  } else {
+    debug_info_module = it->second;
+  }
+
+  u_int64_t address = frame->instruction - frame->module->base_address();
+  Module::Function* function =
+      debug_info_module->FindFunctionByAddress(address);
+  if (function) {
+    frame->function_name = function->name;
+    //TODO: line info: function->lines
+  } else {
+    Module::Extern* ex = debug_info_module->FindExternByAddress(address);
+    if (ex) {
+      frame->function_name = ex->name;
+    }
+  }
+# endif /* !defined(SPS_OS_windows) */
+  return kNoError;
+}
+
+
+WindowsFrameInfo* LocalDebugInfoSymbolizer::FindWindowsFrameInfo(
+    const StackFrame* frame) {
+  // Not currently implemented, would require PDBSourceLineWriter to
+  // implement an API to return symbol data.
+  return NULL;
+}
+
+#if !defined(SPS_OS_windows)
+// Taken wholesale from source_line_resolver_base.cc
+bool ParseCFIRuleSet(const string& rule_set, CFIFrameInfo* frame_info) {
+  CFIFrameInfoParseHandler handler(frame_info);
+  CFIRuleParser parser(&handler);
+  return parser.Parse(rule_set);
+}
+
+static void ConvertCFI(const UniqueString* name, const Module::Expr& rule,
+                       CFIFrameInfo* frame_info) {
+  if (name == ustr__ZDcfa()) frame_info->SetCFARule(rule);
+  else if (name == ustr__ZDra()) frame_info->SetRARule(rule);
+  else frame_info->SetRegisterRule(name, rule);
+}
+
+
+static void ConvertCFI(const Module::RuleMap& rule_map,
+                       CFIFrameInfo* frame_info) {
+  for (Module::RuleMap::const_iterator it = rule_map.begin();
+       it != rule_map.end(); ++it) {
+    ConvertCFI(it->first, it->second, frame_info);
+  }
+}
+#endif
+
+CFIFrameInfo* LocalDebugInfoSymbolizer::FindCFIFrameInfo(
+    const StackFrame* frame) {
+#if defined(SPS_OS_windows)
+  return NULL;
+#else
+  if (!frame || !frame->module) return NULL;
+
+  SymbolMap::const_iterator it = symbols_.find(frame->module->code_file());
+  if (it == symbols_.end()) return NULL;
+
+  Module* module = it->second;
+  u_int64_t address = frame->instruction - frame->module->base_address();
+  Module::StackFrameEntry* entry =
+      module->FindStackFrameEntryByAddress(address);
+  if (!entry)
+    return NULL;
+
+  //TODO: can we cache this data per-address? does that make sense?
+  scoped_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
+  ConvertCFI(entry->initial_rules, rules.get());
+  for (Module::RuleChangeMap::const_iterator delta_it =
+           entry->rule_changes.begin();
+       delta_it != entry->rule_changes.end() && delta_it->first < address;
+       ++delta_it) {
+    ConvertCFI(delta_it->second, rules.get());
+  }
+  return rules.release();
+#endif /* defined(SPS_OS_windows) */
+}
+
+}  // namespace google_breakpad
new file mode 100644
--- /dev/null
+++ b/tools/profiler/local_debug_info_symbolizer.h
@@ -0,0 +1,40 @@
+#ifndef PROCESSOR_LOCAL_DEBUG_INFO_SYMBOLIZER_H_
+#define PROCESSOR_LOCAL_DEBUG_INFO_SYMBOLIZER_H_
+
+#include "google_breakpad/processor/stack_frame_symbolizer.h"
+
+#include <map>
+#include <vector>
+
+namespace google_breakpad {
+
+class Module;
+
+class LocalDebugInfoSymbolizer : public StackFrameSymbolizer {
+ public:
+  using StackFrameSymbolizer::SymbolizerResult;
+  LocalDebugInfoSymbolizer(const std::vector<string>& debug_dirs) :
+      StackFrameSymbolizer(NULL, NULL),
+      debug_dirs_(debug_dirs) {}
+  virtual ~LocalDebugInfoSymbolizer();
+
+  virtual SymbolizerResult FillSourceLineInfo(const CodeModules* modules,
+                                              const SystemInfo* system_info,
+                                              StackFrame* stack_frame);
+
+  virtual WindowsFrameInfo* FindWindowsFrameInfo(const StackFrame* frame);
+
+  virtual CFIFrameInfo* FindCFIFrameInfo(const StackFrame* frame);
+
+  // Lie to the stackwalker to short-circuit stack-scanning heuristics.
+  virtual bool HasImplementation() { return false; }
+
+ private:
+  typedef std::map<string, Module*> SymbolMap;
+  SymbolMap symbols_;
+  std::vector<string> debug_dirs_;
+};
+
+}  // namespace google_breakpad
+
+#endif  // PROCESSOR_LOCAL_DEBUG_INFO_SYMBOLIZER_H_
--- a/tools/profiler/nsProfiler.cpp
+++ b/tools/profiler/nsProfiler.cpp
@@ -55,21 +55,21 @@ nsProfiler::Observe(nsISupports *aSubjec
                     const PRUnichar *aData)
 {
   if (strcmp(aTopic, "chrome-document-global-created") == 0) {
     nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aSubject);
     nsCOMPtr<nsIWebNavigation> parentWebNav = do_GetInterface(requestor);
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(parentWebNav);
     if (loadContext && loadContext->UsePrivateBrowsing() && !mLockedForPrivateBrowsing) {
       mLockedForPrivateBrowsing = true;
-      mozilla_sampler_lock();
+      SAMPLER_LOCK();
     }
   } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
     mLockedForPrivateBrowsing = false;
-    mozilla_sampler_unlock();
+    SAMPLER_UNLOCK();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsProfiler::StartProfiler(uint32_t aEntries, uint32_t aInterval,
                           const char** aFeatures, uint32_t aFeatureCount)
 {
--- a/tools/profiler/platform-linux.cc
+++ b/tools/profiler/platform-linux.cc
@@ -219,17 +219,17 @@ Sampler::Sampler(int interval, bool prof
 
 Sampler::~Sampler() {
   ASSERT(!data_->signal_sender_launched_);
   delete data_;
 }
 
 
 void Sampler::Start() {
-  LOG("Sampler Started");
+  LOG("Sampler started");
   if (sActiveSampler != NULL) return;
 
   // Request profiling signals.
   LOG("Request signal");
   struct sigaction sa;
   sa.sa_sigaction = ProfilerSignalHandler;
   sigemptyset(&sa.sa_mask);
   sa.sa_flags = SA_RESTART | SA_SIGINFO;
@@ -286,18 +286,18 @@ void Sampler::Stop() {
   sActiveSampler = NULL;
 }
 
 #ifdef ANDROID
 static struct sigaction old_sigstart_signal_handler;
 const int SIGSTART = SIGUSR1;
 
 static void StartSignalHandler(int signal, siginfo_t* info, void* context) {
-  mozilla_sampler_start(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
-                        PROFILE_DEFAULT_FEATURES, PROFILE_DEFAULT_FEATURE_COUNT);
+  SAMPLER_START(PROFILE_DEFAULT_ENTRY, PROFILE_DEFAULT_INTERVAL,
+                PROFILE_DEFAULT_FEATURES, PROFILE_DEFAULT_FEATURE_COUNT);
 }
 
 void OS::RegisterStartHandler()
 {
   LOG("Registering start signal");
   struct sigaction sa;
   sa.sa_sigaction = StartSignalHandler;
   sigemptyset(&sa.sa_mask);
--- a/tools/profiler/platform-macos.cc
+++ b/tools/profiler/platform-macos.cc
@@ -23,18 +23,18 @@
 #include <sys/resource.h>
 #include <sys/types.h>
 #include <sys/sysctl.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 
-
 #include "platform.h"
+#include "UnwinderThread2.h"  /* uwt__register_thread_for_profiling */
 
 // this port is based off of v8 svn revision 9837
 
 // XXX: this is a very stubbed out implementation
 // that only supports a single Sampler
 struct SamplerRegistry {
   static void AddActiveSampler(Sampler *sampler) {
     ASSERT(!SamplerRegistry::sampler);
@@ -124,16 +124,26 @@ static void SetThreadName(const char* na
 }
 
 
 static void* ThreadEntry(void* arg) {
   Thread* thread = reinterpret_cast<Thread*>(arg);
   // This is also initialized by the first argument to pthread_create() but we
   // don't know which thread will run first (the original thread or the new
   // one) so we initialize it here too.
+
+  // BEGIN temp hack for SPS v1-vs-v2
+  extern bool sps_version2();
+  if (sps_version2()) {
+    // Register this thread for profiling.
+    int aLocal;
+    uwt__register_thread_for_profiling( &aLocal );
+  }
+  // END temp hack for SPS v1-vs-v2
+
   thread->data()->thread_ = pthread_self();
   SetThreadName(thread->name());
   ASSERT(thread->data()->thread_ != kNoThread);
   thread->Run();
   return NULL;
 }
 
 
--- a/tools/profiler/platform.h
+++ b/tools/profiler/platform.h
@@ -21,16 +21,19 @@
 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 // SUCH DAMAGE.
 
+#ifndef TOOLS_PLATFORM_H_
+#define TOOLS_PLATFORM_H_
+
 #ifdef ANDROID
 #include <android/log.h>
 #else
 #define __android_log_print(a, ...)
 #endif
 
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Util.h"
@@ -39,21 +42,21 @@
 #include "v8-support.h"
 #include <vector>
 #define ASSERT(a) MOZ_ASSERT(a)
 #ifdef ANDROID
 #if defined(__arm__) || defined(__thumb__)
 #define ENABLE_SPS_LEAF_DATA
 #define ENABLE_ARM_LR_SAVING
 #endif
-#define LOG(text) __android_log_write(ANDROID_LOG_ERROR, "profiler", text)
-#define LOGF(format, ...) __android_log_print(ANDROID_LOG_ERROR, "profiler", format, __VA_ARGS__)
+#define LOG(text) __android_log_write(ANDROID_LOG_ERROR, "Profiler", text)
+#define LOGF(format, ...) __android_log_print(ANDROID_LOG_ERROR, "Profiler", format, __VA_ARGS__)
 #else
-#define LOG(text) printf("Profiler: %s\n", text)
-#define LOGF(format, ...) printf("Profiler: " format "\n", __VA_ARGS__)
+#define LOG(text) fprintf(stderr, "Profiler: %s\n", text)
+#define LOGF(format, ...) fprintf(stderr, "Profiler: " format "\n", __VA_ARGS__)
 #endif
 
 #if defined(XP_MACOSX) || defined(XP_WIN)
 #define ENABLE_SPS_LEAF_DATA
 #endif
 
 typedef uint8_t* Address;
 
@@ -271,8 +274,9 @@ class Sampler {
 
   const int interval_;
   const bool profiling_;
   Atomic32 paused_;
   Atomic32 active_;
   PlatformData* data_;  // Platform specific data.
 };
 
+#endif /* ndef TOOLS_PLATFORM_H_ */
new file mode 100644
--- /dev/null
+++ b/tools/profiler/shim_mac_dump_syms.h
@@ -0,0 +1,8 @@
+
+// Read debug info from |obj_file| and park it in a Module, returned
+// via |module|.  Caller owns the Module and is responsible for
+// deallocating it.  Note that |debug_dirs| is ignored.
+bool ReadSymbolData(const string& obj_file,
+                    const std::vector<string> &debug_dirs,
+                    SymbolData symbol_data,
+                    google_breakpad::Module** module);
new file mode 100644
--- /dev/null
+++ b/tools/profiler/shim_mac_dump_syms.mm
@@ -0,0 +1,20 @@
+// -*- mode: c++ -*-
+
+#include "common/mac/dump_syms.h"
+#include "shim_mac_dump_syms.h"
+
+bool ReadSymbolData(const string& obj_file,
+                    const std::vector<string> &debug_dirs,
+                    SymbolData symbol_data,
+                    google_breakpad::Module** module)
+{
+  google_breakpad::DumpSymbols ds(symbol_data);
+
+  NSString* obj_file_ns = [NSString stringWithUTF8String:obj_file.c_str()];
+  // TODO: remember to [obj_file_ns release] this at the exit points
+
+  if (!ds.Read(obj_file_ns))
+    return false;
+
+  return ds.ReadSymbolData(module);
+}
--- a/tools/profiler/sps_sampler.h
+++ b/tools/profiler/sps_sampler.h
@@ -1,13 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
+#ifndef TOOLS_SPS_SAMPLER_H_
+#define TOOLS_SPS_SAMPLER_H_
+
 #include <stdlib.h>
 #include <signal.h>
 #include <stdarg.h>
 #include "mozilla/ThreadLocal.h"
 #include "nscore.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Util.h"
@@ -21,61 +24,131 @@
 #ifdef MOZ_WIDGET_QT
 #undef slots
 #endif
 #include "jsfriendapi.h"
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
-struct ProfileStack;
+struct PseudoStack;
 class TableTicker;
 class JSCustomObject;
 
-extern mozilla::ThreadLocal<ProfileStack *> tlsStack;
-extern mozilla::ThreadLocal<TableTicker *> tlsTicker;
+extern mozilla::ThreadLocal<PseudoStack *> tlsPseudoStack;
 extern bool stack_key_initialized;
 
 #ifndef SAMPLE_FUNCTION_NAME
 # ifdef __GNUC__
 #  define SAMPLE_FUNCTION_NAME __FUNCTION__
 # elif defined(_MSC_VER)
 #  define SAMPLE_FUNCTION_NAME __FUNCTION__
 # else
 #  define SAMPLE_FUNCTION_NAME __func__  // defined in C99, supported in various C++ compilers. Just raw function name.
 # endif
 #endif
 
-#define SAMPLER_INIT() mozilla_sampler_init()
-#define SAMPLER_SHUTDOWN() mozilla_sampler_shutdown()
-#define SAMPLER_START(entries, interval, features, featureCount) mozilla_sampler_start(entries, interval, features, featureCount)
-#define SAMPLER_STOP() mozilla_sampler_stop()
-#define SAMPLER_IS_ACTIVE() mozilla_sampler_is_active()
-#define SAMPLER_RESPONSIVENESS(time) mozilla_sampler_responsiveness(time)
-#define SAMPLER_GET_RESPONSIVENESS() mozilla_sampler_get_responsiveness()
-#define SAMPLER_FRAME_NUMBER(frameNumber) mozilla_sampler_frame_number(frameNumber)
-#define SAMPLER_SAVE() mozilla_sampler_save()
-#define SAMPLER_GET_PROFILE() mozilla_sampler_get_profile()
-#define SAMPLER_GET_PROFILE_DATA(ctx) mozilla_sampler_get_profile_data(ctx)
-#define SAMPLER_GET_FEATURES() mozilla_sampler_get_features()
+/* Returns true if env var SPS_NEW is set to anything, else false. */
+extern bool sps_version2();
+
+#define SAMPLER_INIT() \
+  do { \
+    if (!sps_version2()) mozilla_sampler_init1(); \
+                    else mozilla_sampler_init2(); \
+  } while (0)
+
+#define SAMPLER_SHUTDOWN() \
+  do { \
+    if (!sps_version2()) mozilla_sampler_shutdown1(); \
+                    else mozilla_sampler_shutdown2(); \
+  } while (0)
+
+#define SAMPLER_START(entries, interval, features, featureCount) \
+  do { \
+    if (!sps_version2()) \
+      mozilla_sampler_start1(entries, interval, features, featureCount); \
+    else \
+      mozilla_sampler_start2(entries, interval, features, featureCount); \
+  } while (0)
+
+#define SAMPLER_STOP() \
+  do { \
+    if (!sps_version2()) mozilla_sampler_stop1(); \
+                    else mozilla_sampler_stop2(); \
+  } while (0)
+
+#define SAMPLER_IS_ACTIVE() \
+    (!sps_version2() ? mozilla_sampler_is_active1() \
+                     : mozilla_sampler_is_active2() )
+
+#define SAMPLER_RESPONSIVENESS(time) \
+  do { \
+    if (!sps_version2()) mozilla_sampler_responsiveness1(time); \
+                    else mozilla_sampler_responsiveness2(time); \
+  } while (0)
+
+#define SAMPLER_GET_RESPONSIVENESS() \
+    (!sps_version2() ? mozilla_sampler_get_responsiveness1() \
+                     : mozilla_sampler_get_responsiveness2() )
+
+#define SAMPLER_FRAME_NUMBER(frameNumber) \
+  do { \
+    if (!sps_version2()) mozilla_sampler_frame_number1(frameNumber); \
+                    else mozilla_sampler_frame_number2(frameNumber); \
+  } while (0)
+
+#define SAMPLER_SAVE() \
+  do { \
+    if (!sps_version2()) mozilla_sampler_save1(); \
+                    else mozilla_sampler_save2(); \
+  } while (0)
+
+#define SAMPLER_GET_PROFILE() \
+   (!sps_version2() ? mozilla_sampler_get_profile1() \
+                    : mozilla_sampler_get_profile2() )
+
+#define SAMPLER_GET_PROFILE_DATA(ctx) \
+   (!sps_version2() ? mozilla_sampler_get_profile_data1(ctx) \
+                    : mozilla_sampler_get_profile_data2(ctx) )
+
+#define SAMPLER_GET_FEATURES() \
+   (!sps_version2() ? mozilla_sampler_get_features1() \
+                    : mozilla_sampler_get_features2() )
+
 // we want the class and function name but can't easily get that using preprocessor macros
 // __func__ doesn't have the class name and __PRETTY_FUNCTION__ has the parameters
 
 #define SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line) id ## line
 #define SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, line) SAMPLER_APPEND_LINE_NUMBER_PASTE(id, line)
 #define SAMPLER_APPEND_LINE_NUMBER(id) SAMPLER_APPEND_LINE_NUMBER_EXPAND(id, __LINE__)
 
 #define SAMPLE_LABEL(name_space, info) mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__)
 #define SAMPLE_LABEL_PRINTF(name_space, info, ...) mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__, __VA_ARGS__)
 #define SAMPLE_MARKER(info) mozilla_sampler_add_marker(info)
 #define SAMPLE_MAIN_THREAD_LABEL(name_space, info)  MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFrameRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__)
 #define SAMPLE_MAIN_THREAD_LABEL_PRINTF(name_space, info, ...)  MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla::SamplerStackFramePrintfRAII SAMPLER_APPEND_LINE_NUMBER(sampler_raii)(name_space "::" info, __LINE__, __VA_ARGS__)
 #define SAMPLE_MAIN_THREAD_MARKER(info)  MOZ_ASSERT(NS_IsMainThread(), "This can only be called on the main thread"); mozilla_sampler_add_marker(info)
 
-#define SAMPLER_PRINT_LOCATION() mozilla_sampler_print_location()
+#define SAMPLER_PRINT_LOCATION() \
+    do { \
+      if (!sps_version2()) mozilla_sampler_print_location1(); \
+                      else mozilla_sampler_print_location2(); \
+    } while (0)
+
+#define SAMPLER_LOCK() \
+    do { \
+      if (!sps_version2()) mozilla_sampler_lock1(); \
+                      else mozilla_sampler_lock2(); \
+    } while (0)
+
+#define SAMPLER_UNLOCK() \
+    do { \
+      if (!sps_version2()) mozilla_sampler_unlock1(); \
+                      else mozilla_sampler_unlock2(); \
+    } while (0)
 
 /* we duplicate this code here to avoid header dependencies
  * which make it more difficult to include in other places */
 #if defined(_M_X64) || defined(__x86_64__)
 #define V8_HOST_ARCH_X64 1
 #elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
 #define V8_HOST_ARCH_IA32 1
 #elif defined(__ARMEL__)
@@ -160,41 +233,73 @@ LinuxKernelMemoryBarrierFunc pLinuxKerne
 #  define STORE_SEQUENCER() asm volatile("" ::: "memory");
 # else
 #  error "Memory clobber not supported for your compiler."
 # endif
 #else
 # error "Memory clobber not supported for your platform."
 #endif
 
-// Returns a handdle to pass on exit. This can check that we are popping the
+// Returns a handle to pass on exit. This can check that we are popping the
 // correct callstack.
-inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress = NULL, bool aCopy = false, uint32_t line = 0);
+inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress = NULL,
+                                        bool aCopy = false, uint32_t line = 0);
 inline void  mozilla_sampler_call_exit(void* handle);
 inline void  mozilla_sampler_add_marker(const char *aInfo);
 
-void mozilla_sampler_start(int aEntries, int aInterval, const char** aFeatures, uint32_t aFeatureCount);
-void mozilla_sampler_stop();
-bool mozilla_sampler_is_active();
-void mozilla_sampler_responsiveness(TimeStamp time);
-void mozilla_sampler_frame_number(int frameNumber);
-const double* mozilla_sampler_get_responsiveness();
-void mozilla_sampler_save();
-char* mozilla_sampler_get_profile();
-JSObject *mozilla_sampler_get_profile_data(JSContext *aCx);
-const char** mozilla_sampler_get_features();
-void mozilla_sampler_init();
-void mozilla_sampler_shutdown();
-void mozilla_sampler_print_location();
+void mozilla_sampler_start1(int aEntries, int aInterval, const char** aFeatures,
+                            uint32_t aFeatureCount);
+void mozilla_sampler_start2(int aEntries, int aInterval, const char** aFeatures,
+                            uint32_t aFeatureCount);
+
+void mozilla_sampler_stop1();
+void mozilla_sampler_stop2();
+
+bool mozilla_sampler_is_active1();
+bool mozilla_sampler_is_active2();
+
+void mozilla_sampler_responsiveness1(TimeStamp time);
+void mozilla_sampler_responsiveness2(TimeStamp time);
+
+void mozilla_sampler_frame_number1(int frameNumber);
+void mozilla_sampler_frame_number2(int frameNumber);
+
+const double* mozilla_sampler_get_responsiveness1();
+const double* mozilla_sampler_get_responsiveness2();
+
+void mozilla_sampler_save1();
+void mozilla_sampler_save2();
+
+char* mozilla_sampler_get_profile1();
+char* mozilla_sampler_get_profile2();
+
+JSObject *mozilla_sampler_get_profile_data1(JSContext *aCx);
+JSObject *mozilla_sampler_get_profile_data2(JSContext *aCx);
+
+const char** mozilla_sampler_get_features1();
+const char** mozilla_sampler_get_features2();
+
+void mozilla_sampler_init1();
+void mozilla_sampler_init2();
+
+void mozilla_sampler_shutdown1();
+void mozilla_sampler_shutdown2();
+
+void mozilla_sampler_print_location1();
+void mozilla_sampler_print_location2();
+
 // Lock the profiler. When locked the profiler is (1) stopped,
 // (2) profile data is cleared, (3) profiler-locked is fired.
 // This is used to lock down the profiler during private browsing
-void mozilla_sampler_lock();
+void mozilla_sampler_lock1();
+void mozilla_sampler_lock2();
+
 // Unlock the profiler, leaving it stopped and fires profiler-unlocked.
-void mozilla_sampler_unlock();
+void mozilla_sampler_unlock1();
+void mozilla_sampler_unlock2();
 
 namespace mozilla {
 
 class NS_STACK_CLASS SamplerStackFrameRAII {
 public:
   // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
   SamplerStackFrameRAII(const char *aInfo, uint32_t line) {
     mHandle = mozilla_sampler_call_enter(aInfo, this, false, line);
@@ -206,17 +311,17 @@ private:
   void* mHandle;
 };
 
 static const int SAMPLER_MAX_STRING = 128;
 class NS_STACK_CLASS SamplerStackFramePrintfRAII {
 public:
   // we only copy the strings at save time, so to take multiple parameters we'd need to copy them then.
   SamplerStackFramePrintfRAII(const char *aDefault, uint32_t line, const char *aFormat, ...) {
-    if (mozilla_sampler_is_active()) {
+    if (SAMPLER_IS_ACTIVE()) {
       va_list args;
       va_start(args, aFormat);
       char buff[SAMPLER_MAX_STRING];
 
       // We have to use seperate printf's because we're using
       // the vargs.
 #if _MSC_VER
       _vsnprintf(buff, SAMPLER_MAX_STRING, aFormat, args);
@@ -268,23 +373,24 @@ public:
                         reinterpret_cast<uintptr_t>(sparg) & ~0x1));
     } else {
       setStackAddress(reinterpret_cast<void*>(
                         reinterpret_cast<uintptr_t>(sparg) | 0x1));
     }
   }
 };
 
-// the SamplerStack members are read by signal
+// the PseudoStack members are read by signal
 // handlers, so the mutation of them needs to be signal-safe.
-struct ProfileStack
+struct PseudoStack
 {
 public:
-  ProfileStack()
+  PseudoStack()
     : mStackPointer(0)
+    , mSignalLock(false)
     , mMarkerPointer(0)
     , mQueueClearMarker(false)
     , mRuntime(NULL)
     , mStartJSSampling(false)
   { }
 
   void addMarker(const char *aMarker)
   {
@@ -415,32 +521,32 @@ public:
   // it to queue a clear operation.
   volatile mozilla::sig_safe_t mQueueClearMarker;
   // The runtime which is being sampled
   JSRuntime *mRuntime;
   // Start JS Profiling when possible
   bool mStartJSSampling;
 };
 
-inline ProfileStack* mozilla_profile_stack(void)
+inline PseudoStack* mozilla_get_pseudo_stack(void)
 {
   if (!stack_key_initialized)
     return NULL;
-  return tlsStack.get();
+  return tlsPseudoStack.get();
 }
 
 inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress,
                                         bool aCopy, uint32_t line)
 {
   // check if we've been initialized to avoid calling pthread_getspecific
   // with a null tlsStack which will return undefined results.
   if (!stack_key_initialized)
     return NULL;
 
-  ProfileStack *stack = tlsStack.get();
+  PseudoStack *stack = tlsPseudoStack.get();
   // we can't infer whether 'stack' has been initialized
   // based on the value of stack_key_intiailized because
   // 'stack' is only intialized when a thread is being
   // profiled.
   if (!stack) {
     return stack;
   }
   stack->push(aInfo, aFrameAddress, aCopy, line);
@@ -453,30 +559,31 @@ inline void* mozilla_sampler_call_enter(
   return stack;
 }
 
 inline void mozilla_sampler_call_exit(void *aHandle)
 {
   if (!aHandle)
     return;
 
-  ProfileStack *stack = (ProfileStack*)aHandle;
+  PseudoStack *stack = (PseudoStack*)aHandle;
   stack->pop();
 }
 
 inline void mozilla_sampler_add_marker(const char *aMarker)
 {
   if (!stack_key_initialized)
     return;
 
   // Don't insert a marker if we're not profiling to avoid
   // the heap copy (malloc).
-  if (!mozilla_sampler_is_active()) {
+  if (!SAMPLER_IS_ACTIVE()) {
     return;
   }
 
-  ProfileStack *stack = tlsStack.get();
+  PseudoStack *stack = tlsPseudoStack.get();
   if (!stack) {
     return;
   }
   stack->addMarker(aMarker);
 }
 
+#endif /* ndef TOOLS_SPS_SAMPLER_H_ */