diff -r 000000000000 -r 6474c204b198 xpcom/threads/ThreadStackHelper.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/xpcom/threads/ThreadStackHelper.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,238 @@ +/* -*- Mode: C++; tab-width: 8; 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 "ThreadStackHelper.h" +#include "MainThreadUtils.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Move.h" + +#ifdef XP_LINUX +#include +#include +#endif + +#ifdef ANDROID +#ifndef SYS_gettid +#define SYS_gettid __NR_gettid +#endif +#ifndef SYS_tgkill +#define SYS_tgkill __NR_tgkill +#endif +#endif + +namespace mozilla { + +void +ThreadStackHelper::Startup() +{ +#if defined(XP_LINUX) + MOZ_ASSERT(NS_IsMainThread()); + if (!sInitialized) { + MOZ_ALWAYS_TRUE(!::sem_init(&sSem, 0, 0)); + } + sInitialized++; +#endif +} + +void +ThreadStackHelper::Shutdown() +{ +#if defined(XP_LINUX) + MOZ_ASSERT(NS_IsMainThread()); + if (sInitialized == 1) { + MOZ_ALWAYS_TRUE(!::sem_destroy(&sSem)); + } + sInitialized--; +#endif +} + +ThreadStackHelper::ThreadStackHelper() + : +#ifdef MOZ_ENABLE_PROFILER_SPS + mPseudoStack(mozilla_get_pseudo_stack()), +#endif + mStackBuffer() + , mMaxStackSize(mStackBuffer.capacity()) +{ +#if defined(XP_LINUX) + mThreadID = ::syscall(SYS_gettid); +#elif defined(XP_WIN) + mInitialized = !!::DuplicateHandle( + ::GetCurrentProcess(), ::GetCurrentThread(), + ::GetCurrentProcess(), &mThreadID, + THREAD_SUSPEND_RESUME, FALSE, 0); + MOZ_ASSERT(mInitialized); +#elif defined(XP_MACOSX) + mThreadID = mach_thread_self(); +#endif +} + +ThreadStackHelper::~ThreadStackHelper() +{ +#if defined(XP_WIN) + if (mInitialized) { + MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID)); + } +#endif +} + +#if defined(XP_LINUX) && defined(__arm__) +// Some (old) Linux kernels on ARM have a bug where a signal handler +// can be called without clearing the IT bits in CPSR first. The result +// is that the first few instructions of the handler could be skipped, +// ultimately resulting in crashes. To workaround this bug, the handler +// on ARM is a trampoline that starts with enough NOP instructions, so +// that even if the IT bits are not cleared, only the NOP instructions +// will be skipped over. + +template +__attribute__((naked)) void +SignalTrampoline(int aSignal, siginfo_t* aInfo, void* aContext) +{ + asm volatile ( + "nop; nop; nop; nop" + : : : "memory"); + + // Because the assembler may generate additional insturctions below, we + // need to ensure NOPs are inserted first by separating them out above. + + asm volatile ( + "bx %0" + : + : "r"(H), "l"(aSignal), "l"(aInfo), "l"(aContext) + : "memory"); +} +#endif // XP_LINUX && __arm__ + +void +ThreadStackHelper::GetStack(Stack& aStack) +{ + // Always run PrepareStackBuffer first to clear aStack + if (!PrepareStackBuffer(aStack)) { + // Skip and return empty aStack + return; + } + +#if defined(XP_LINUX) + if (profiler_is_active()) { + // Profiler can interfere with our Linux signal handling + return; + } + if (!sInitialized) { + MOZ_ASSERT(false); + return; + } + sCurrent = this; + struct sigaction sigact = {}; +#ifdef __arm__ + sigact.sa_sigaction = SignalTrampoline; +#else + sigact.sa_sigaction = SigAction; +#endif + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = SA_SIGINFO | SA_RESTART; + if (::sigaction(SIGPROF, &sigact, &sOldSigAction)) { + MOZ_ASSERT(false); + return; + } + MOZ_ALWAYS_TRUE(!::syscall(SYS_tgkill, getpid(), mThreadID, SIGPROF)); + MOZ_ALWAYS_TRUE(!::sem_wait(&sSem)); + +#elif defined(XP_WIN) + if (!mInitialized) { + MOZ_ASSERT(false); + return; + } + if (::SuspendThread(mThreadID) == DWORD(-1)) { + MOZ_ASSERT(false); + return; + } + FillStackBuffer(); + MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1)); + +#elif defined(XP_MACOSX) + if (::thread_suspend(mThreadID) != KERN_SUCCESS) { + MOZ_ASSERT(false); + return; + } + FillStackBuffer(); + MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS); + +#endif + aStack = Move(mStackBuffer); +} + +#ifdef XP_LINUX + +int ThreadStackHelper::sInitialized; +sem_t ThreadStackHelper::sSem; +struct sigaction ThreadStackHelper::sOldSigAction; +ThreadStackHelper* ThreadStackHelper::sCurrent; + +void +ThreadStackHelper::SigAction(int aSignal, siginfo_t* aInfo, void* aContext) +{ + ::sigaction(SIGPROF, &sOldSigAction, nullptr); + sCurrent->FillStackBuffer(); + sCurrent = nullptr; + ::sem_post(&sSem); +} + +#endif // XP_LINUX + +bool +ThreadStackHelper::PrepareStackBuffer(Stack& aStack) { + // Return false to skip getting the stack and return an empty stack + aStack.clear(); +#ifdef MOZ_ENABLE_PROFILER_SPS + /* Normally, provided the profiler is enabled, it would be an error if we + don't have a pseudostack here (the thread probably forgot to call + profiler_register_thread). However, on B2G, profiling secondary threads + may be disabled despite profiler being enabled. This is by-design and + is not an error. */ +#ifdef MOZ_WIDGET_GONK + if (!mPseudoStack) { + return false; + } +#endif + MOZ_ASSERT(mPseudoStack); + mStackBuffer.clear(); + MOZ_ALWAYS_TRUE(mStackBuffer.reserve(mMaxStackSize)); + return true; +#else + return false; +#endif +} + +void +ThreadStackHelper::FillStackBuffer() { +#ifdef MOZ_ENABLE_PROFILER_SPS + size_t reservedSize = mMaxStackSize; + + // Go from front to back + const volatile StackEntry* entry = mPseudoStack->mStack; + const volatile StackEntry* end = entry + mPseudoStack->stackSize(); + // Deduplicate identical, consecutive frames + const char* prevLabel = nullptr; + for (; reservedSize-- && entry != end; entry++) { + /* We only accept non-copy labels, because + we are unable to actually copy labels here */ + if (entry->isCopyLabel()) { + continue; + } + const char* const label = entry->label(); + if (label == prevLabel) { + continue; + } + mStackBuffer.infallibleAppend(label); + prevLabel = label; + } + // If we exited early due to buffer size, expand the buffer for next time + mMaxStackSize += (end - entry); +#endif +} + +} // namespace mozilla