diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index d733ddaeb28..de021f9a5c0 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1354,7 +1354,7 @@ XPCJSRuntime::~XPCJSRuntime() } #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 @@ -2561,7 +2561,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) 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); diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc index e6a9f3d38c0..dc27c050027 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/dump_symbols.cc @@ -626,7 +626,7 @@ bool LoadSymbols(const string& obj_file, } } - 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()); diff --git a/tools/profiler/Makefile.in b/tools/profiler/Makefile.in index e5e615e2bff..7a69ded148c 100644 --- a/tools/profiler/Makefile.in +++ b/tools/profiler/Makefile.in @@ -25,6 +25,7 @@ EXPORTS += \ 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))) @@ -45,6 +46,10 @@ CPPSRCS = \ nsProfilerFactory.cpp \ nsProfiler.cpp \ TableTicker.cpp \ + TableTicker2.cpp \ + UnwinderThread2.cpp \ + ProfileEntry2.cpp \ + local_debug_info_symbolizer.cc \ JSObjectBuilder.cpp \ JSCustomObjectBuilder.cpp \ $(NULL) @@ -69,6 +74,8 @@ CPPSRCS += \ shared-libraries-macos.cc \ platform-macos.cc \ $(NULL) +CMMSRCS += \ + shim_mac_dump_syms.mm endif ifeq ($(OS_TARGET),WINNT) diff --git a/tools/profiler/PlatformMacros.h b/tools/profiler/PlatformMacros.h new file mode 100644 index 00000000000..5f8f6d3dd39 --- /dev/null +++ b/tools/profiler/PlatformMacros.h @@ -0,0 +1,70 @@ +/* -*- 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_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(__linux__) && defined(__arm__) && defined(ANDROID) +# define SPS_PLAT_arm_android 1 +# define SPS_ARCH_arm 1 +# define SPS_OS_android 1 + +#elif defined(__linux__) && defined(__i386__) && !defined(ANDROID) +# 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(_WIN32) && !defined(_WIN64) +# define SPS_PLAT_x86_windows 1 +# define SPS_ARCH_x86 1 +# define SPS_OS_windows 1 + +#elif defined(__linux__) && defined(__i386__) && defined(ANDROID) +# define SPS_PLAT_x86_android 1 +# define SPS_ARCH_x86 1 +# define SPS_OS_android 1 + +#else +# error "Unsupported platform" +#endif + +#endif /* ndef SPS_PLATFORM_MACROS_H */ diff --git a/tools/profiler/ProfileEntry2.cpp b/tools/profiler/ProfileEntry2.cpp new file mode 100644 index 00000000000..5b591f0c2a8 --- /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 +#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 +//////////////////////////////////////////////////////////////////////// diff --git a/tools/profiler/ProfileEntry2.h b/tools/profiler/ProfileEntry2.h new file mode 100644 index 00000000000..bc5db6d5621 --- /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 */ diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index c39e04f6c48..a8f1af4863c 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -31,6 +31,7 @@ #include "nsDirectoryServiceDefs.h" #include "nsIObserverService.h" #include "mozilla/Services.h" +#include "PlatformMacros.h" // JS #include "jsdbgapi.h" @@ -82,13 +83,7 @@ using namespace mozilla; static const int DYNAMIC_MAX_STRING = 512; -mozilla::ThreadLocal tlsStack; -mozilla::ThreadLocal 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 tlsTicker; TimeStamp sLastTracerEvent; int sFrameNumber = 0; @@ -155,7 +150,7 @@ typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagSt class ThreadProfile { public: - ThreadProfile(int aEntrySize, ProfileStack *aStack) + ThreadProfile(int aEntrySize, PseudoStack *aStack) : mWritePos(0) , mLastFlushPos(0) , mReadPos(0) @@ -416,7 +411,7 @@ public: } } - ProfileStack* GetStack() + PseudoStack* GetStack() { return mStack; } @@ -428,7 +423,7 @@ private: 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; @@ -444,7 +439,7 @@ hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) 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) @@ -586,7 +581,7 @@ public: // 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(); @@ -729,7 +724,7 @@ void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr) static void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile, - ProfileStack *stack, void *lastpc) + PseudoStack *stack, void *lastpc) { int lineno = -1; @@ -843,7 +838,7 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) 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, @@ -882,7 +877,7 @@ void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) #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 @@ -916,7 +911,7 @@ unsigned int sCurrentEventGeneration = 0; 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)); } @@ -1002,19 +997,61 @@ std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry) 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) + 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(); @@ -1032,11 +1069,11 @@ void mozilla_sampler_init() , "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) { @@ -1051,13 +1088,13 @@ void mozilla_sampler_shutdown() } } - 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) { @@ -1070,7 +1107,7 @@ void mozilla_sampler_save() t->HandleSaveRequest(); } -char* mozilla_sampler_get_profile() +char* mozilla_sampler_get_profile1() { TableTicker *t = tlsTicker.get(); if (!t) { @@ -1088,7 +1125,7 @@ char* mozilla_sampler_get_profile() return rtn; } -JSObject *mozilla_sampler_get_profile_data(JSContext *aCx) +JSObject *mozilla_sampler_get_profile_data1(JSContext *aCx) { TableTicker *t = tlsTicker.get(); if (!t) { @@ -1099,7 +1136,7 @@ JSObject *mozilla_sampler_get_profile_data(JSContext *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)) @@ -1117,19 +1154,19 @@ const char** mozilla_sampler_get_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, @@ -1144,10 +1181,10 @@ void mozilla_sampler_start(int aProfileEntries, int aInterval, 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) { @@ -1159,7 +1196,7 @@ void mozilla_sampler_stop() t->Stop(); delete t; tlsTicker.set(NULL); - ProfileStack *stack = tlsStack.get(); + PseudoStack *stack = tlsPseudoStack.get(); ASSERT(stack != NULL); if (disableJS) @@ -1170,10 +1207,10 @@ void mozilla_sampler_stop() 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) { @@ -1183,10 +1220,9 @@ bool mozilla_sampler_is_active() 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) { @@ -1203,17 +1239,17 @@ void mozilla_sampler_responsiveness(TimeStamp aTime) 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': @@ -1221,12 +1257,12 @@ void print_callback(const ProfileEntry& entry, const char* 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; @@ -1241,15 +1277,15 @@ void mozilla_sampler_print_location() threadProfile.IterateTags(print_callback); } -void mozilla_sampler_lock() +void mozilla_sampler_lock1() { - mozilla_sampler_stop(); + mozilla_sampler_stop1(); nsCOMPtr os = mozilla::services::GetObserverService(); if (os) os->NotifyObservers(nullptr, "profiler-locked", nullptr); } -void mozilla_sampler_unlock() +void mozilla_sampler_unlock1() { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) diff --git a/tools/profiler/TableTicker2.cpp b/tools/profiler/TableTicker2.cpp new file mode 100644 index 00000000000..9ca4dd98b61 --- /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 +#include +#include +#include +#include +#include +#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 + #define getpid GetCurrentProcessId +#else + #include +#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 tlsPseudoStack; +static mozilla::ThreadLocal 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(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 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 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 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 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 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 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_PLAT_x86_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= (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) /* no idea if x86-win 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 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 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 +//////////////////////////////////////////////////////////////////////// diff --git a/tools/profiler/UnwinderThread2.cpp b/tools/profiler/UnwinderThread2.cpp new file mode 100644 index 00000000000..302f574b7fa --- /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 +#include +#include +#include +#include + +#ifdef MOZ_VALGRIND +# include +# include +#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 + +#include "ProfileEntry2.h" +#include "UnwinderThread2.h" + +#if !defined(SPS_PLAT_x86_windows) +# include +# include +# include + // mmap +# include +#endif + +#if defined(SPS_OS_android) +# include "android-signal-defs.h" +#endif + +#if defined(SPS_OS_darwin) +# include +#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_PLAT_x86_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(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(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(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(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 +#include +#include + +#include +#include +#include +#include + +#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 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(&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& 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(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(cmd); + + if (!strcmp(seg->segname, "__TEXT")) { + cm = new MyCodeModule( seg->vmaddr + slide, + seg->vmsize, string(name) ); + break; + } + } + cmd = reinterpret_cast((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::const_iterator it; + for (it = mods_.begin(); it < mods_.end(); it++) { + MyCodeModule* cm = *it; + delete cm; + } + } + + private: + std::vector 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::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 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* modules_without_symbols + = new std::vector(); + 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(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(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(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_PLAT_x86_windows) */ diff --git a/tools/profiler/UnwinderThread2.h b/tools/profiler/UnwinderThread2.h new file mode 100644 index 00000000000..29111fbaa28 --- /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 */ diff --git a/tools/profiler/local_debug_info_symbolizer.cc b/tools/profiler/local_debug_info_symbolizer.cc new file mode 100644 index 00000000000..082861d3131 --- /dev/null +++ b/tools/profiler/local_debug_info_symbolizer.cc @@ -0,0 +1,160 @@ + +#include "PlatformMacros.h" + +#if !defined(SPS_PLAT_x86_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_PLAT_x86_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_PLAT_x86_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_PLAT_x86_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_PLAT_x86_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_PLAT_x86_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_PLAT_x86_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 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_PLAT_x86_windows) */ +} + +} // namespace google_breakpad diff --git a/tools/profiler/local_debug_info_symbolizer.h b/tools/profiler/local_debug_info_symbolizer.h new file mode 100644 index 00000000000..311cc49768e --- /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 +#include + +namespace google_breakpad { + +class Module; + +class LocalDebugInfoSymbolizer : public StackFrameSymbolizer { + public: + using StackFrameSymbolizer::SymbolizerResult; + LocalDebugInfoSymbolizer(const std::vector& 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 SymbolMap; + SymbolMap symbols_; + std::vector debug_dirs_; +}; + +} // namespace google_breakpad + +#endif // PROCESSOR_LOCAL_DEBUG_INFO_SYMBOLIZER_H_ diff --git a/tools/profiler/nsProfiler.cpp b/tools/profiler/nsProfiler.cpp index c44f1b9c7f8..feb03c2c239 100644 --- a/tools/profiler/nsProfiler.cpp +++ b/tools/profiler/nsProfiler.cpp @@ -60,11 +60,11 @@ nsProfiler::Observe(nsISupports *aSubject, nsCOMPtr 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; } diff --git a/tools/profiler/platform-linux.cc b/tools/profiler/platform-linux.cc index 9f54cf26488..7685a9f19c3 100644 --- a/tools/profiler/platform-linux.cc +++ b/tools/profiler/platform-linux.cc @@ -224,7 +224,7 @@ Sampler::~Sampler() { void Sampler::Start() { - LOG("Sampler Started"); + LOG("Sampler started"); if (sActiveSampler != NULL) return; // Request profiling signals. @@ -291,8 +291,8 @@ 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() diff --git a/tools/profiler/platform-macos.cc b/tools/profiler/platform-macos.cc index 0d3b3423952..99392cc7ed6 100644 --- a/tools/profiler/platform-macos.cc +++ b/tools/profiler/platform-macos.cc @@ -28,8 +28,8 @@ #include #include - #include "platform.h" +#include "UnwinderThread2.h" /* uwt__register_thread_for_profiling */ // this port is based off of v8 svn revision 9837 @@ -129,6 +129,16 @@ static void* ThreadEntry(void* 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); diff --git a/tools/profiler/platform.h b/tools/profiler/platform.h index c67d3540d72..021ddc32eba 100644 --- a/tools/profiler/platform.h +++ b/tools/profiler/platform.h @@ -26,6 +26,9 @@ // 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 #else @@ -44,11 +47,11 @@ #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) @@ -276,3 +279,4 @@ class Sampler { PlatformData* data_; // Platform specific data. }; +#endif /* ndef TOOLS_PLATFORM_H_ */ diff --git a/tools/profiler/shim_mac_dump_syms.h b/tools/profiler/shim_mac_dump_syms.h new file mode 100644 index 00000000000..5820daf1c97 --- /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 &debug_dirs, + SymbolData symbol_data, + google_breakpad::Module** module); diff --git a/tools/profiler/shim_mac_dump_syms.mm b/tools/profiler/shim_mac_dump_syms.mm new file mode 100644 index 00000000000..4eab6b9debf --- /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 &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); +} diff --git a/tools/profiler/sps_sampler.h b/tools/profiler/sps_sampler.h index 007b7391fa6..9acaa1d2515 100644 --- a/tools/profiler/sps_sampler.h +++ b/tools/profiler/sps_sampler.h @@ -3,6 +3,9 @@ * 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 #include #include @@ -26,12 +29,11 @@ using mozilla::TimeStamp; using mozilla::TimeDuration; -struct ProfileStack; +struct PseudoStack; class TableTicker; class JSCustomObject; -extern mozilla::ThreadLocal tlsStack; -extern mozilla::ThreadLocal tlsTicker; +extern mozilla::ThreadLocal tlsPseudoStack; extern bool stack_key_initialized; #ifndef SAMPLE_FUNCTION_NAME @@ -44,18 +46,73 @@ extern bool stack_key_initialized; # 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 @@ -70,7 +127,23 @@ extern bool stack_key_initialized; #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 */ @@ -165,31 +238,63 @@ LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = # 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 { @@ -211,7 +316,7 @@ 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]; @@ -273,13 +378,14 @@ public: } }; -// 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) @@ -420,11 +526,11 @@ public: 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, @@ -435,7 +541,7 @@ inline void* mozilla_sampler_call_enter(const char *aInfo, void *aFrameAddress, 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 @@ -458,7 +564,7 @@ inline void mozilla_sampler_call_exit(void *aHandle) if (!aHandle) return; - ProfileStack *stack = (ProfileStack*)aHandle; + PseudoStack *stack = (PseudoStack*)aHandle; stack->pop(); } @@ -469,14 +575,15 @@ inline void mozilla_sampler_add_marker(const char *aMarker) // 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_ */