static void addProfileEntry(ProfileStack *aStack, ThreadProfile &aProfile, int i) { // First entry has tagName 's' (start) // Check for magic pointer bit 1 to indicate copy const char* sampleLabel = aStack->mStack[i].mLabel; if (aStack->mStack[i].isCopyLabel()) { // Store the string using 1 or more 'd' (dynamic) tags // that will happen to the preceding tag aProfile.addTag(ProfileEntry('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* aProfile.addTag(ProfileEntry('d', *((void**)(&text[0])))); } } else { aProfile.addTag(ProfileEntry('c', sampleLabel)); } }
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) { #ifndef XP_MACOSX uintptr_t thread = GetThreadHandle(platform_data()); MOZ_ASSERT(thread); #endif void* pc_array[1000]; PCArray array = { pc_array, mozilla::ArrayLength(pc_array), 0 }; // Start with the current function. StackWalkCallback(aSample->pc, &array); #ifdef XP_MACOSX pthread_t pt = GetProfiledThread(platform_data()); void *stackEnd = reinterpret_cast<void*>(-1); if (pt) stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt)); nsresult rv = FramePointerStackWalk(StackWalkCallback, 0, &array, reinterpret_cast<void**>(aSample->fp), stackEnd); #else nsresult rv = NS_StackWalk(StackWalkCallback, 0, &array, thread); #endif if (NS_SUCCEEDED(rv)) { aProfile.addTag(ProfileEntry('s', "(root)")); for (size_t i = array.count; i > 0; --i) { aProfile.addTag(ProfileEntry('l', (void*)array.array[i - 1])); } } }
void TableTicker::Tick(TickSample* sample) { // Marker(s) come before the sample ProfileStack* stack = mPrimaryThreadProfile.GetStack(); for (int i = 0; stack->getMarker(i) != NULL; i++) { mPrimaryThreadProfile.addTag(ProfileEntry('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; } } } #if defined(USE_BACKTRACE) || defined(USE_NS_STACKWALK) || defined(USE_LIBUNWIND) if (mUseStackWalk) { doBacktrace(mPrimaryThreadProfile, sample); } else { doSampleStackTrace(stack, mPrimaryThreadProfile, sample); } #else doSampleStackTrace(stack, mPrimaryThreadProfile, sample); #endif if (recordSample) mPrimaryThreadProfile.flush(); if (!sLastTracerEvent.IsNull() && sample) { TimeDuration delta = sample->timestamp - sLastTracerEvent; mPrimaryThreadProfile.addTag(ProfileEntry('r', delta.ToMilliseconds())); } if (sample) { TimeDuration delta = sample->timestamp - mStartTime; mPrimaryThreadProfile.addTag(ProfileEntry('t', delta.ToMilliseconds())); } if (sLastFrameNumber != sFrameNumber) { mPrimaryThreadProfile.addTag(ProfileEntry('f', sFrameNumber)); sLastFrameNumber = sFrameNumber; } }
static void addPseudoEntry(volatile StackEntry &entry, ThreadProfile &aProfile, PseudoStack *stack, void *lastpc) { // Pseudo-frames with the ASMJS flag are just annotations and should not be // recorded in the profile. if (entry.hasFlag(StackEntry::ASMJS)) return; int lineno = -1; // First entry has tagName 's' (start) // Check for magic pointer bit 1 to indicate copy const char* sampleLabel = entry.label(); if (entry.isCopyLabel()) { // Store the string using 1 or more 'd' (dynamic) tags // that will happen to the preceding tag addDynamicTag(aProfile, 'c', sampleLabel); if (entry.isJs()) { if (!entry.pc()) { // The JIT only allows the top-most entry to have a nullptr 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(entry.script(), jspc); } } } else { lineno = JS_PCToLineNumber(entry.script(), entry.pc()); } } else { lineno = entry.line(); } } else { aProfile.addTag(ProfileEntry('c', sampleLabel)); // XXX: Bug 1010578. Don't assume a CPP entry and try to get the // line for js entries as well. if (entry.isCpp()) { lineno = entry.line(); } } if (lineno != -1) { aProfile.addTag(ProfileEntry('n', lineno)); } uint32_t category = entry.category(); MOZ_ASSERT(!(category & StackEntry::IS_CPP_ENTRY)); MOZ_ASSERT(!(category & StackEntry::FRAME_LABEL_COPY)); if (category) { aProfile.addTag(ProfileEntry('y', (int)category)); } }
void TableTicker::doBacktrace(ThreadProfile &aProfile, Address pc) { void *array[100]; int count = backtrace (array, 100); aProfile.addTag(ProfileEntry('s', "(root)", 0)); for (int i = 0; i < count; i++) { if( (intptr_t)array[i] == -1 ) break; aProfile.addTag(ProfileEntry('l', (const char*)array[i])); } }
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) { void *array[100]; int count = backtrace (array, 100); aProfile.addTag(ProfileEntry('s', "(root)")); for (int i = 0; i < count; i++) { if( (intptr_t)array[i] == -1 ) break; aProfile.addTag(ProfileEntry('l', (void*)array[i])); } }
static void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile, ProfileStack *stack, void *lastpc) { int lineno = -1; // First entry has tagName 's' (start) // Check for magic pointer bit 1 to indicate copy const char* sampleLabel = entry.label(); if (entry.isCopyLabel()) { // Store the string using 1 or more 'd' (dynamic) tags // that will happen to the preceding tag aProfile.addTag(ProfileEntry('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* aProfile.addTag(ProfileEntry('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 { aProfile.addTag(ProfileEntry('c', sampleLabel)); lineno = entry.line(); } if (lineno != -1) { aProfile.addTag(ProfileEntry('n', lineno)); } }
static void doSampleStackTrace(ThreadProfile &aProfile, TickSample *aSample, bool aAddLeafAddresses) { NativeStack nativeStack = { nullptr, nullptr, 0, 0 }; mergeStacksIntoProfile(aProfile, aSample, nativeStack); #ifdef ENABLE_SPS_LEAF_DATA if (aSample && aAddLeafAddresses) { aProfile.addTag(ProfileEntry('l', (void*)aSample->pc)); #ifdef ENABLE_ARM_LR_SAVING aProfile.addTag(ProfileEntry('L', (void*)aSample->lr)); #endif } #endif }
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) { void* pc_array[1000]; size_t count = 0; unw_cursor_t cursor; unw_context_t uc; unw_word_t ip; unw_getcontext(&uc); // Dirty hack: replace the registers with values from the signal handler // We do this in order to avoid the overhead of walking up to reach the // signal handler frame, and the possibility that libunwind fails to // handle it correctly. unw_tdep_context_t *unw_ctx = reinterpret_cast<unw_tdep_context_t*> (&uc); mcontext_t& mcontext = reinterpret_cast<ucontext_t*> (aSample->context)->uc_mcontext; #define REPLACE_REG(num) unw_ctx->regs[num] = mcontext.gregs[R##num] REPLACE_REG(0); REPLACE_REG(1); REPLACE_REG(2); REPLACE_REG(3); REPLACE_REG(4); REPLACE_REG(5); REPLACE_REG(6); REPLACE_REG(7); REPLACE_REG(8); REPLACE_REG(9); REPLACE_REG(10); REPLACE_REG(11); REPLACE_REG(12); REPLACE_REG(13); REPLACE_REG(14); REPLACE_REG(15); #undef REPLACE_REG unw_init_local(&cursor, &uc); while (count < ArrayLength(pc_array) && unw_step(&cursor) > 0) { unw_get_reg(&cursor, UNW_REG_IP, &ip); pc_array[count++] = reinterpret_cast<void*> (ip); } aProfile.addTag(ProfileEntry('s', "(root)")); for (size_t i = count; i > 0; --i) { aProfile.addTag(ProfileEntry('l', reinterpret_cast<void*>(pc_array[i - 1]))); } }
static void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSample *sample) { // Sample // 's' tag denotes the start of a sample block // followed by 0 or more 'c' tags. for (int i = 0; i < aStack->mStackPointer; i++) { if (i == 0) { Address pc = 0; if (sample) { pc = sample->pc; } aProfile.addTag(ProfileEntry('s', aStack->mStack[i], pc)); } else { aProfile.addTag(ProfileEntry('c', aStack->mStack[i])); } } }
static void doSampleStackTrace(PseudoStack *aStack, ThreadProfile &aProfile, TickSample *sample) { // Sample // 's' tag denotes the start of a sample block // followed by 0 or more 'c' tags. aProfile.addTag(ProfileEntry('s', "(root)")); for (uint32_t i = 0; i < aStack->stackSize(); i++) { addProfileEntry(aStack->mStack[i], aProfile, aStack, nullptr); } #ifdef ENABLE_SPS_LEAF_DATA if (sample) { aProfile.addTag(ProfileEntry('l', (void*)sample->pc)); #ifdef ENABLE_ARM_LR_SAVING aProfile.addTag(ProfileEntry('L', (void*)sample->lr)); #endif } #endif }
static void addDynamicTag(ThreadProfile &aProfile, char aTagName, const char *aStr) { aProfile.addTag(ProfileEntry(aTagName, "")); // Add one to store the null termination size_t strLen = strlen(aStr) + 1; for (size_t j = 0; j < strLen;) { // Store as many characters in the void* as the platform allows char text[sizeof(void*)]; size_t len = sizeof(void*)/sizeof(char); if (j+len >= strLen) { len = strLen - j; } memcpy(text, &aStr[j], len); j += sizeof(void*)/sizeof(char); // Cast to *((void**) to pass the text data to a void* aProfile.addTag(ProfileEntry('d', *((void**)(&text[0])))); } }
static void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSample *sample) { // Sample // 's' tag denotes the start of a sample block // followed by 0 or more 'c' tags. aProfile.addTag(ProfileEntry('s', "(root)")); for (mozilla::sig_safe_t i = 0; i < aStack->mStackPointer && i < mozilla::ArrayLength(aStack->mStack); i++) { addProfileEntry(aStack, aProfile, i); } #ifdef ENABLE_SPS_LEAF_DATA if (sample) { aProfile.addTag(ProfileEntry('l', (void*)sample->pc)); #ifdef ENABLE_ARM_LR_SAVING aProfile.addTag(ProfileEntry('L', (void*)sample->lr)); #endif } #endif }
static void addProfileEntry(volatile StackEntry &entry, ThreadProfile &aProfile, PseudoStack *stack, void *lastpc) { int lineno = -1; // First entry has tagName 's' (start) // Check for magic pointer bit 1 to indicate copy const char* sampleLabel = entry.label(); if (entry.isCopyLabel()) { // Store the string using 1 or more 'd' (dynamic) tags // that will happen to the preceding tag addDynamicTag(aProfile, 'c', sampleLabel); 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 { aProfile.addTag(ProfileEntry('c', sampleLabel)); lineno = entry.line(); } if (lineno != -1) { aProfile.addTag(ProfileEntry('n', lineno)); } }
static void mergeNativeBacktrace(ThreadProfile &aProfile, const PCArray &array) { aProfile.addTag(ProfileEntry('s', "(root)")); PseudoStack* stack = aProfile.GetPseudoStack(); uint32_t pseudoStackPos = 0; /* We have two stacks, the native C stack we extracted from unwinding, * and the pseudostack we managed during execution. We want to consolidate * the two in order. We do so by merging using the approximate stack address * when each entry was push. When pushing JS entry we may not now the stack * address in which case we have a NULL stack address in which case we assume * that it follows immediatly the previous element. * * C Stack | Address -- Pseudo Stack | Address * main() | 0x100 run_js() | 0x40 * start() | 0x80 jsCanvas() | NULL * timer() | 0x50 drawLine() | NULL * azure() | 0x10 * * Merged: main(), start(), timer(), run_js(), jsCanvas(), drawLine(), azure() */ // i is the index in C stack starting at main and decreasing // pseudoStackPos is the position in the Pseudo stack starting // at the first frame (run_js in the example) and increasing. for (size_t i = array.count; i > 0; --i) { while (pseudoStackPos < stack->stackSize()) { volatile StackEntry& entry = stack->mStack[pseudoStackPos]; if (entry.stackAddress() < array.sp_array[i-1] && entry.stackAddress()) break; addProfileEntry(entry, aProfile, stack, array.array[0]); pseudoStackPos++; } aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1])); } }
static void doSampleStackTrace(ProfileStack *aStack, ThreadProfile &aProfile, TickSample *sample) { // Sample // 's' tag denotes the start of a sample block // followed by 0 or more 'c' tags. aProfile.addTag(ProfileEntry('s', "(root)")); for (mozilla::sig_safe_t i = 0; i < aStack->mStackPointer; i++) { // First entry has tagName 's' (start) // Check for magic pointer bit 1 to indicate copy const char* sampleLabel = aStack->mStack[i].mLabel; if (aStack->mStack[i].isCopyLabel()) { // Store the string using 1 or more 'd' (dynamic) tags // that will happen to the preceding tag aProfile.addTag(ProfileEntry('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*); // Take '*((void**)(&text[0]))' to pass the char[] as a single void* aProfile.addTag(ProfileEntry('d', *((void**)(&text[0])))); } } else { aProfile.addTag(ProfileEntry('c', sampleLabel)); } } #ifdef ENABLE_SPS_LEAF_DATA if (sample) { aProfile.addTag(ProfileEntry('l', (void*)sample->pc)); } #endif }
void TableTicker::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) { #ifdef XP_WIN /* For the purpose of SPS it is currently too expensive to call NS_StackWalk() on itself on Windows. For each call to NS_StackWalk(), we currently have to use another thread to suspend the calling thread, obtain its context, and walk its stack. We shouldn't do that every time we need a thread to obtain its own backtrace in SPS.*/ if (aSample->isSamplingCurrentThread) { void* ptrs[100]; USHORT count = RtlCaptureStackBackTrace(0, 100, ptrs, nullptr); aProfile.addTag(ProfileEntry('s', "(root)")); if (count) { USHORT i = count; do { --i; aProfile.addTag(ProfileEntry('l', ptrs[i])); } while (i != 0); } return; } #endif #ifndef XP_MACOSX uintptr_t thread = GetThreadHandle(aSample->threadProfile->GetPlatformData()); MOZ_ASSERT(thread); #endif void* pc_array[1000]; void* sp_array[1000]; PCArray array = { pc_array, sp_array, mozilla::ArrayLength(pc_array), 0 }; // Start with the current function. StackWalkCallback(aSample->pc, aSample->sp, &array); uint32_t maxFrames = uint32_t(array.size - array.count); #ifdef XP_MACOSX pthread_t pt = GetProfiledThread(aSample->threadProfile->GetPlatformData()); void *stackEnd = reinterpret_cast<void*>(-1); if (pt) stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt)); nsresult rv = NS_OK; if (aSample->fp >= aSample->sp && aSample->fp <= stackEnd) rv = FramePointerStackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, &array, reinterpret_cast<void**>(aSample->fp), stackEnd); #else void *platformData = nullptr; #ifdef XP_WIN platformData = aSample->context; #endif // XP_WIN nsresult rv = NS_StackWalk(StackWalkCallback, /* skipFrames */ 0, maxFrames, &array, thread, platformData); #endif if (NS_SUCCEEDED(rv)) mergeNativeBacktrace(aProfile, array); }
static void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) { PseudoStack* pseudoStack = aProfile.GetPseudoStack(); volatile StackEntry *pseudoFrames = pseudoStack->mStack; uint32_t pseudoCount = pseudoStack->stackSize(); // Make a copy of the JS stack into a JSFrame array. This is necessary since, // like the native stack, the JS stack is iterated youngest-to-oldest and we // need to iterate oldest-to-youngest when adding entries to aProfile. // Synchronous sampling reports an invalid buffer generation to // ProfilingFrameIterator to avoid incorrectly resetting the generation of // sampled JIT entries inside the JS engine. See note below concerning 'J' // entries. uint32_t startBufferGen; if (aSample->isSamplingCurrentThread) { startBufferGen = UINT32_MAX; } else { startBufferGen = aProfile.bufferGeneration(); } uint32_t jsCount = 0; #ifndef SPS_STANDALONE JS::ProfilingFrameIterator::Frame jsFrames[1000]; // Only walk jit stack if profiling frame iterator is turned on. if (pseudoStack->mRuntime && JS::IsProfilingEnabledForRuntime(pseudoStack->mRuntime)) { AutoWalkJSStack autoWalkJSStack; const uint32_t maxFrames = mozilla::ArrayLength(jsFrames); if (aSample && autoWalkJSStack.walkAllowed) { JS::ProfilingFrameIterator::RegisterState registerState; registerState.pc = aSample->pc; registerState.sp = aSample->sp; #ifdef ENABLE_ARM_LR_SAVING registerState.lr = aSample->lr; #endif JS::ProfilingFrameIterator jsIter(pseudoStack->mRuntime, registerState, startBufferGen); for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) { // See note below regarding 'J' entries. if (aSample->isSamplingCurrentThread || jsIter.isAsmJS()) { uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames); jsCount += extracted; if (jsCount == maxFrames) break; } else { mozilla::Maybe<JS::ProfilingFrameIterator::Frame> frame = jsIter.getPhysicalFrameWithoutLabel(); if (frame.isSome()) jsFrames[jsCount++] = frame.value(); } } } } #endif // Start the sample with a root entry. aProfile.addTag(ProfileEntry('s', "(root)")); // While the pseudo-stack array is ordered oldest-to-youngest, the JS and // native arrays are ordered youngest-to-oldest. We must add frames to // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards // and JS and native arrays backwards. Note: this means the terminating // condition jsIndex and nativeIndex is being < 0. uint32_t pseudoIndex = 0; int32_t jsIndex = jsCount - 1; int32_t nativeIndex = aNativeStack.count - 1; uint8_t *lastPseudoCppStackAddr = nullptr; // Iterate as long as there is at least one frame remaining. while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { // There are 1 to 3 frames available. Find and add the oldest. uint8_t *pseudoStackAddr = nullptr; uint8_t *jsStackAddr = nullptr; uint8_t *nativeStackAddr = nullptr; if (pseudoIndex != pseudoCount) { volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; if (pseudoFrame.isCpp()) lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress(); #ifndef SPS_STANDALONE // Skip any pseudo-stack JS frames which are marked isOSR // Pseudostack frames are marked isOSR when the JS interpreter // enters a jit frame on a loop edge (via on-stack-replacement, // or OSR). To avoid both the pseudoframe and jit frame being // recorded (and showing up twice), the interpreter marks the // interpreter pseudostack entry with the OSR flag to ensure that // it doesn't get counted. if (pseudoFrame.isJs() && pseudoFrame.isOSR()) { pseudoIndex++; continue; } #endif MOZ_ASSERT(lastPseudoCppStackAddr); pseudoStackAddr = lastPseudoCppStackAddr; } #ifndef SPS_STANDALONE if (jsIndex >= 0) jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress; #endif if (nativeIndex >= 0) nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex]; // If there's a native stack entry which has the same SP as a // pseudo stack entry, pretend we didn't see the native stack // entry. Ditto for a native stack entry which has the same SP as // a JS stack entry. In effect this means pseudo or JS entries // trump conflicting native entries. if (nativeStackAddr && (pseudoStackAddr == nativeStackAddr || jsStackAddr == nativeStackAddr)) { nativeStackAddr = nullptr; nativeIndex--; MOZ_ASSERT(pseudoStackAddr || jsStackAddr); } // Sanity checks. MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr && pseudoStackAddr != nativeStackAddr); MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr && jsStackAddr != nativeStackAddr); MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr && nativeStackAddr != jsStackAddr); // Check to see if pseudoStack frame is top-most. if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) { MOZ_ASSERT(pseudoIndex < pseudoCount); volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } #ifndef SPS_STANDALONE // Check to see if JS jit stack frame is top-most if (jsStackAddr > nativeStackAddr) { MOZ_ASSERT(jsIndex >= 0); const JS::ProfilingFrameIterator::Frame& jsFrame = jsFrames[jsIndex]; // Stringifying non-asm.js JIT frames is delayed until streaming // time. To re-lookup the entry in the JitcodeGlobalTable, we need to // store the JIT code address ('J') in the circular buffer. // // Note that we cannot do this when we are sychronously sampling the // current thread; that is, when called from profiler_get_backtrace. The // captured backtrace is usually externally stored for an indeterminate // amount of time, such as in nsRefreshDriver. Problematically, the // stored backtrace may be alive across a GC during which the profiler // itself is disabled. In that case, the JS engine is free to discard // its JIT code. This means that if we inserted such 'J' entries into // the buffer, nsRefreshDriver would now be holding on to a backtrace // with stale JIT code return addresses. if (aSample->isSamplingCurrentThread || jsFrame.kind == JS::ProfilingFrameIterator::Frame_AsmJS) { addDynamicTag(aProfile, 'c', jsFrame.label); } else { MOZ_ASSERT(jsFrame.kind == JS::ProfilingFrameIterator::Frame_Ion || jsFrame.kind == JS::ProfilingFrameIterator::Frame_Baseline); aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress)); } jsIndex--; continue; } #endif // If we reach here, there must be a native stack entry and it must be the // greatest entry. if (nativeStackAddr) { MOZ_ASSERT(nativeIndex >= 0); aProfile .addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); } if (nativeIndex >= 0) { nativeIndex--; } } #ifndef SPS_STANDALONE // Update the JS runtime with the current profile sample buffer generation. // // Do not do this for synchronous sampling, which create their own // ProfileBuffers. if (!aSample->isSamplingCurrentThread && pseudoStack->mRuntime) { MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen); uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen; JS::UpdateJSRuntimeProfilerSampleBufferGen(pseudoStack->mRuntime, aProfile.bufferGeneration(), lapCount); } #endif }
static void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) { PseudoStack* pseudoStack = aProfile.GetPseudoStack(); volatile StackEntry *pseudoFrames = pseudoStack->mStack; uint32_t pseudoCount = pseudoStack->stackSize(); // Make a copy of the JS stack into a JSFrame array. This is necessary since, // like the native stack, the JS stack is iterated youngest-to-oldest and we // need to iterate oldest-to-youngest when adding entries to aProfile. JSFrame jsFrames[1000]; uint32_t jsCount = 0; if (aSample && pseudoStack->mRuntime) { JS::ProfilingFrameIterator::RegisterState registerState; registerState.pc = aSample->pc; registerState.sp = aSample->sp; #ifdef ENABLE_ARM_LR_SAVING registerState.lr = aSample->lr; #endif JS::ProfilingFrameIterator jsIter(pseudoStack->mRuntime, registerState); for (; jsCount < mozilla::ArrayLength(jsFrames) && !jsIter.done(); ++jsCount, ++jsIter) { jsFrames[jsCount].stackAddress = jsIter.stackAddress(); jsFrames[jsCount].label = jsIter.label(); } } // Start the sample with a root entry. aProfile.addTag(ProfileEntry('s', "(root)")); // While the pseudo-stack array is ordered oldest-to-youngest, the JS and // native arrays are ordered youngest-to-oldest. We must add frames to // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards // and JS and native arrays backwards. Note: this means the terminating // condition jsIndex and nativeIndex is being < 0. uint32_t pseudoIndex = 0; int32_t jsIndex = jsCount - 1; int32_t nativeIndex = aNativeStack.count - 1; // Iterate as long as there is at least one frame remaining. while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { // There are 1 to 3 frames available. Find and add the oldest. Handle pseudo // frames first, since there are two special cases that must be considered // before everything else. if (pseudoIndex != pseudoCount) { volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; // isJs pseudo-stack frames assume the stackAddress of the preceding isCpp // pseudo-stack frame. If we arrive at an isJs pseudo frame, we've already // encountered the preceding isCpp stack frame and it was oldest, we can // assume the isJs frame is oldest without checking other frames. if (pseudoFrame.isJs()) { addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } // Currently, only asm.js frames use the JS stack and Ion/Baseline/Interp // frames use the pseudo stack. In the optimized asm.js->Ion call path, no // isCpp frame is pushed, leading to the callstack: // old | pseudo isCpp | asm.js | pseudo isJs | new // Since there is no interleaving isCpp pseudo frame between the asm.js // and isJs pseudo frame, the above isJs logic will render the callstack: // old | pseudo isCpp | pseudo isJs | asm.js | new // which is wrong. To deal with this, a pseudo isCpp frame pushed right // before entering asm.js flagged with StackEntry::ASMJS. When we see this // flag, we first push all the asm.js frames (up to the next frame with a // stackAddress) before pushing the isJs frames. There is no Ion->asm.js // fast path, so we don't have to worry about asm.js->Ion->asm.js. // // (This and the above isJs special cases can be removed once all JS // execution modes switch from the pseudo stack to the JS stack.) if (pseudoFrame.hasFlag(StackEntry::ASMJS)) { void *stopStackAddress = nullptr; for (uint32_t i = pseudoIndex + 1; i != pseudoCount; i++) { if (pseudoFrames[i].isCpp()) { stopStackAddress = pseudoFrames[i].stackAddress(); break; } } if (nativeIndex >= 0) { stopStackAddress = std::max(stopStackAddress, aNativeStack.sp_array[nativeIndex]); } while (jsIndex >= 0 && jsFrames[jsIndex].stackAddress > stopStackAddress) { addDynamicTag(aProfile, 'c', jsFrames[jsIndex].label); jsIndex--; } pseudoIndex++; continue; } // Finally, consider the normal case of a plain C++ pseudo-frame. if ((jsIndex < 0 || pseudoFrame.stackAddress() > jsFrames[jsIndex].stackAddress) && (nativeIndex < 0 || pseudoFrame.stackAddress() > aNativeStack.sp_array[nativeIndex])) { // The (C++) pseudo-frame is the oldest. addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } } if (jsIndex >= 0) { // Test whether the JS frame is the oldest. JSFrame &jsFrame = jsFrames[jsIndex]; if ((pseudoIndex == pseudoCount || jsFrame.stackAddress > pseudoFrames[pseudoIndex].stackAddress()) && (nativeIndex < 0 || jsFrame.stackAddress > aNativeStack.sp_array[nativeIndex])) { // The JS frame is the oldest. addDynamicTag(aProfile, 'c', jsFrame.label); jsIndex--; continue; } } // If execution reaches this point, there must be a native frame and it must // be the oldest. MOZ_ASSERT(nativeIndex >= 0); aProfile.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); nativeIndex--; } }
void TableTicker::doBacktrace(ThreadProfile &aProfile, TickSample* aSample) { #ifndef XP_MACOSX uintptr_t thread = GetThreadHandle(platform_data()); MOZ_ASSERT(thread); #endif void* pc_array[1000]; void* sp_array[1000]; PCArray array = { pc_array, sp_array, mozilla::ArrayLength(pc_array), 0 }; // Start with the current function. StackWalkCallback(aSample->pc, aSample->sp, &array); #ifdef XP_MACOSX pthread_t pt = GetProfiledThread(platform_data()); void *stackEnd = reinterpret_cast<void*>(-1); if (pt) stackEnd = static_cast<char*>(pthread_get_stackaddr_np(pt)); nsresult rv = FramePointerStackWalk(StackWalkCallback, 0, &array, reinterpret_cast<void**>(aSample->fp), stackEnd); #else nsresult rv = NS_StackWalk(StackWalkCallback, 0, &array, thread); #endif if (NS_SUCCEEDED(rv)) { aProfile.addTag(ProfileEntry('s', "(root)")); ProfileStack* stack = aProfile.GetStack(); int pseudoStackPos = 0; /* We have two stacks, the native C stack we extracted from unwinding, * and the pseudostack we managed during execution. We want to consolidate * the two in order. We do so by merging using the approximate stack address * when each entry was push. When pushing JS entry we may not now the stack * address in which case we have a NULL stack address in which case we assume * that it follows immediatly the previous element. * * C Stack | Address -- Pseudo Stack | Address * main() | 0x100 run_js() | 0x40 * start() | 0x80 jsCanvas() | NULL * timer() | 0x50 drawLine() | NULL * azure() | 0x10 * * Merged: main(), start(), timer(), run_js(), jsCanvas(), drawLine(), azure() */ // i is the index in C stack starting at main and decreasing // pseudoStackPos is the position in the Pseudo stack starting // at the first frame (run_js in the example) and increasing. for (size_t i = array.count; i > 0; --i) { while (pseudoStackPos < stack->stackSize()) { volatile StackEntry& entry = stack->mStack[pseudoStackPos]; if (entry.stackAddress() < array.sp_array[i-1] && entry.stackAddress()) break; addProfileEntry(entry, aProfile, stack, array.array[0]); pseudoStackPos++; } aProfile.addTag(ProfileEntry('l', (void*)array.array[i-1])); } } }
static void mergeStacksIntoProfile(ThreadProfile& aProfile, TickSample* aSample, NativeStack& aNativeStack) { PseudoStack* pseudoStack = aProfile.GetPseudoStack(); volatile StackEntry *pseudoFrames = pseudoStack->mStack; uint32_t pseudoCount = pseudoStack->stackSize(); // Make a copy of the JS stack into a JSFrame array. This is necessary since, // like the native stack, the JS stack is iterated youngest-to-oldest and we // need to iterate oldest-to-youngest when adding entries to aProfile. uint32_t startBufferGen = aProfile.bufferGeneration(); uint32_t jsCount = 0; JS::ProfilingFrameIterator::Frame jsFrames[1000]; // Only walk jit stack if profiling frame iterator is turned on. if (pseudoStack->mRuntime && JS::IsProfilingEnabledForRuntime(pseudoStack->mRuntime)) { AutoWalkJSStack autoWalkJSStack; const uint32_t maxFrames = mozilla::ArrayLength(jsFrames); if (aSample && autoWalkJSStack.walkAllowed) { JS::ProfilingFrameIterator::RegisterState registerState; registerState.pc = aSample->pc; registerState.sp = aSample->sp; #ifdef ENABLE_ARM_LR_SAVING registerState.lr = aSample->lr; #endif JS::ProfilingFrameIterator jsIter(pseudoStack->mRuntime, registerState, startBufferGen); for (; jsCount < maxFrames && !jsIter.done(); ++jsIter) { uint32_t extracted = jsIter.extractStack(jsFrames, jsCount, maxFrames); MOZ_ASSERT(extracted <= (maxFrames - jsCount)); jsCount += extracted; if (jsCount == maxFrames) break; } } } // Start the sample with a root entry. aProfile.addTag(ProfileEntry('s', "(root)")); // While the pseudo-stack array is ordered oldest-to-youngest, the JS and // native arrays are ordered youngest-to-oldest. We must add frames to // aProfile oldest-to-youngest. Thus, iterate over the pseudo-stack forwards // and JS and native arrays backwards. Note: this means the terminating // condition jsIndex and nativeIndex is being < 0. uint32_t pseudoIndex = 0; int32_t jsIndex = jsCount - 1; int32_t nativeIndex = aNativeStack.count - 1; uint8_t *lastPseudoCppStackAddr = nullptr; // Iterate as long as there is at least one frame remaining. while (pseudoIndex != pseudoCount || jsIndex >= 0 || nativeIndex >= 0) { // There are 1 to 3 frames available. Find and add the oldest. uint8_t *pseudoStackAddr = nullptr; uint8_t *jsStackAddr = nullptr; uint8_t *nativeStackAddr = nullptr; if (pseudoIndex != pseudoCount) { volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; if (pseudoFrame.isCpp()) lastPseudoCppStackAddr = (uint8_t *) pseudoFrame.stackAddress(); // Skip any pseudo-stack JS frames which are marked isOSR // Pseudostack frames are marked isOSR when the JS interpreter // enters a jit frame on a loop edge (via on-stack-replacement, // or OSR). To avoid both the pseudoframe and jit frame being // recorded (and showing up twice), the interpreter marks the // interpreter pseudostack entry with the OSR flag to ensure that // it doesn't get counted. if (pseudoFrame.isJs() && pseudoFrame.isOSR()) { pseudoIndex++; continue; } MOZ_ASSERT(lastPseudoCppStackAddr); pseudoStackAddr = lastPseudoCppStackAddr; } if (jsIndex >= 0) jsStackAddr = (uint8_t *) jsFrames[jsIndex].stackAddress; if (nativeIndex >= 0) nativeStackAddr = (uint8_t *) aNativeStack.sp_array[nativeIndex]; // Sanity checks. MOZ_ASSERT_IF(pseudoStackAddr, pseudoStackAddr != jsStackAddr && pseudoStackAddr != nativeStackAddr); MOZ_ASSERT_IF(jsStackAddr, jsStackAddr != pseudoStackAddr && jsStackAddr != nativeStackAddr); MOZ_ASSERT_IF(nativeStackAddr, nativeStackAddr != pseudoStackAddr && nativeStackAddr != jsStackAddr); // Check to see if pseudoStack frame is top-most. if (pseudoStackAddr > jsStackAddr && pseudoStackAddr > nativeStackAddr) { MOZ_ASSERT(pseudoIndex < pseudoCount); volatile StackEntry &pseudoFrame = pseudoFrames[pseudoIndex]; addPseudoEntry(pseudoFrame, aProfile, pseudoStack, nullptr); pseudoIndex++; continue; } // Check to see if JS jit stack frame is top-most if (jsStackAddr > nativeStackAddr) { MOZ_ASSERT(jsIndex >= 0); addDynamicTag(aProfile, 'c', jsFrames[jsIndex].label); // Stringifying optimization information is delayed until streaming // time. To re-lookup the entry in the JitcodeGlobalTable, we need to // store the JIT code address ('J') in the circular buffer. if (jsFrames[jsIndex].hasTrackedOptimizations) { aProfile.addTag(ProfileEntry('J', jsFrames[jsIndex].returnAddress)); } jsIndex--; continue; } // If we reach here, there must be a native stack entry and it must be the // greatest entry. MOZ_ASSERT(nativeStackAddr); MOZ_ASSERT(nativeIndex >= 0); aProfile.addTag(ProfileEntry('l', (void*)aNativeStack.pc_array[nativeIndex])); nativeIndex--; } MOZ_ASSERT(aProfile.bufferGeneration() >= startBufferGen); uint32_t lapCount = aProfile.bufferGeneration() - startBufferGen; // Update the JS runtime with the current profile sample buffer generation. if (pseudoStack->mRuntime) { JS::UpdateJSRuntimeProfilerSampleBufferGen(pseudoStack->mRuntime, aProfile.bufferGeneration(), lapCount); } }