Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "ThreadStackHelper.h" |
michael@0 | 7 | #include "MainThreadUtils.h" |
michael@0 | 8 | |
michael@0 | 9 | #include "mozilla/Assertions.h" |
michael@0 | 10 | #include "mozilla/Move.h" |
michael@0 | 11 | |
michael@0 | 12 | #ifdef XP_LINUX |
michael@0 | 13 | #include <unistd.h> |
michael@0 | 14 | #include <sys/syscall.h> |
michael@0 | 15 | #endif |
michael@0 | 16 | |
michael@0 | 17 | #ifdef ANDROID |
michael@0 | 18 | #ifndef SYS_gettid |
michael@0 | 19 | #define SYS_gettid __NR_gettid |
michael@0 | 20 | #endif |
michael@0 | 21 | #ifndef SYS_tgkill |
michael@0 | 22 | #define SYS_tgkill __NR_tgkill |
michael@0 | 23 | #endif |
michael@0 | 24 | #endif |
michael@0 | 25 | |
michael@0 | 26 | namespace mozilla { |
michael@0 | 27 | |
michael@0 | 28 | void |
michael@0 | 29 | ThreadStackHelper::Startup() |
michael@0 | 30 | { |
michael@0 | 31 | #if defined(XP_LINUX) |
michael@0 | 32 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 33 | if (!sInitialized) { |
michael@0 | 34 | MOZ_ALWAYS_TRUE(!::sem_init(&sSem, 0, 0)); |
michael@0 | 35 | } |
michael@0 | 36 | sInitialized++; |
michael@0 | 37 | #endif |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | void |
michael@0 | 41 | ThreadStackHelper::Shutdown() |
michael@0 | 42 | { |
michael@0 | 43 | #if defined(XP_LINUX) |
michael@0 | 44 | MOZ_ASSERT(NS_IsMainThread()); |
michael@0 | 45 | if (sInitialized == 1) { |
michael@0 | 46 | MOZ_ALWAYS_TRUE(!::sem_destroy(&sSem)); |
michael@0 | 47 | } |
michael@0 | 48 | sInitialized--; |
michael@0 | 49 | #endif |
michael@0 | 50 | } |
michael@0 | 51 | |
michael@0 | 52 | ThreadStackHelper::ThreadStackHelper() |
michael@0 | 53 | : |
michael@0 | 54 | #ifdef MOZ_ENABLE_PROFILER_SPS |
michael@0 | 55 | mPseudoStack(mozilla_get_pseudo_stack()), |
michael@0 | 56 | #endif |
michael@0 | 57 | mStackBuffer() |
michael@0 | 58 | , mMaxStackSize(mStackBuffer.capacity()) |
michael@0 | 59 | { |
michael@0 | 60 | #if defined(XP_LINUX) |
michael@0 | 61 | mThreadID = ::syscall(SYS_gettid); |
michael@0 | 62 | #elif defined(XP_WIN) |
michael@0 | 63 | mInitialized = !!::DuplicateHandle( |
michael@0 | 64 | ::GetCurrentProcess(), ::GetCurrentThread(), |
michael@0 | 65 | ::GetCurrentProcess(), &mThreadID, |
michael@0 | 66 | THREAD_SUSPEND_RESUME, FALSE, 0); |
michael@0 | 67 | MOZ_ASSERT(mInitialized); |
michael@0 | 68 | #elif defined(XP_MACOSX) |
michael@0 | 69 | mThreadID = mach_thread_self(); |
michael@0 | 70 | #endif |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | ThreadStackHelper::~ThreadStackHelper() |
michael@0 | 74 | { |
michael@0 | 75 | #if defined(XP_WIN) |
michael@0 | 76 | if (mInitialized) { |
michael@0 | 77 | MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID)); |
michael@0 | 78 | } |
michael@0 | 79 | #endif |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | #if defined(XP_LINUX) && defined(__arm__) |
michael@0 | 83 | // Some (old) Linux kernels on ARM have a bug where a signal handler |
michael@0 | 84 | // can be called without clearing the IT bits in CPSR first. The result |
michael@0 | 85 | // is that the first few instructions of the handler could be skipped, |
michael@0 | 86 | // ultimately resulting in crashes. To workaround this bug, the handler |
michael@0 | 87 | // on ARM is a trampoline that starts with enough NOP instructions, so |
michael@0 | 88 | // that even if the IT bits are not cleared, only the NOP instructions |
michael@0 | 89 | // will be skipped over. |
michael@0 | 90 | |
michael@0 | 91 | template <void (*H)(int, siginfo_t*, void*)> |
michael@0 | 92 | __attribute__((naked)) void |
michael@0 | 93 | SignalTrampoline(int aSignal, siginfo_t* aInfo, void* aContext) |
michael@0 | 94 | { |
michael@0 | 95 | asm volatile ( |
michael@0 | 96 | "nop; nop; nop; nop" |
michael@0 | 97 | : : : "memory"); |
michael@0 | 98 | |
michael@0 | 99 | // Because the assembler may generate additional insturctions below, we |
michael@0 | 100 | // need to ensure NOPs are inserted first by separating them out above. |
michael@0 | 101 | |
michael@0 | 102 | asm volatile ( |
michael@0 | 103 | "bx %0" |
michael@0 | 104 | : |
michael@0 | 105 | : "r"(H), "l"(aSignal), "l"(aInfo), "l"(aContext) |
michael@0 | 106 | : "memory"); |
michael@0 | 107 | } |
michael@0 | 108 | #endif // XP_LINUX && __arm__ |
michael@0 | 109 | |
michael@0 | 110 | void |
michael@0 | 111 | ThreadStackHelper::GetStack(Stack& aStack) |
michael@0 | 112 | { |
michael@0 | 113 | // Always run PrepareStackBuffer first to clear aStack |
michael@0 | 114 | if (!PrepareStackBuffer(aStack)) { |
michael@0 | 115 | // Skip and return empty aStack |
michael@0 | 116 | return; |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | #if defined(XP_LINUX) |
michael@0 | 120 | if (profiler_is_active()) { |
michael@0 | 121 | // Profiler can interfere with our Linux signal handling |
michael@0 | 122 | return; |
michael@0 | 123 | } |
michael@0 | 124 | if (!sInitialized) { |
michael@0 | 125 | MOZ_ASSERT(false); |
michael@0 | 126 | return; |
michael@0 | 127 | } |
michael@0 | 128 | sCurrent = this; |
michael@0 | 129 | struct sigaction sigact = {}; |
michael@0 | 130 | #ifdef __arm__ |
michael@0 | 131 | sigact.sa_sigaction = SignalTrampoline<SigAction>; |
michael@0 | 132 | #else |
michael@0 | 133 | sigact.sa_sigaction = SigAction; |
michael@0 | 134 | #endif |
michael@0 | 135 | sigemptyset(&sigact.sa_mask); |
michael@0 | 136 | sigact.sa_flags = SA_SIGINFO | SA_RESTART; |
michael@0 | 137 | if (::sigaction(SIGPROF, &sigact, &sOldSigAction)) { |
michael@0 | 138 | MOZ_ASSERT(false); |
michael@0 | 139 | return; |
michael@0 | 140 | } |
michael@0 | 141 | MOZ_ALWAYS_TRUE(!::syscall(SYS_tgkill, getpid(), mThreadID, SIGPROF)); |
michael@0 | 142 | MOZ_ALWAYS_TRUE(!::sem_wait(&sSem)); |
michael@0 | 143 | |
michael@0 | 144 | #elif defined(XP_WIN) |
michael@0 | 145 | if (!mInitialized) { |
michael@0 | 146 | MOZ_ASSERT(false); |
michael@0 | 147 | return; |
michael@0 | 148 | } |
michael@0 | 149 | if (::SuspendThread(mThreadID) == DWORD(-1)) { |
michael@0 | 150 | MOZ_ASSERT(false); |
michael@0 | 151 | return; |
michael@0 | 152 | } |
michael@0 | 153 | FillStackBuffer(); |
michael@0 | 154 | MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1)); |
michael@0 | 155 | |
michael@0 | 156 | #elif defined(XP_MACOSX) |
michael@0 | 157 | if (::thread_suspend(mThreadID) != KERN_SUCCESS) { |
michael@0 | 158 | MOZ_ASSERT(false); |
michael@0 | 159 | return; |
michael@0 | 160 | } |
michael@0 | 161 | FillStackBuffer(); |
michael@0 | 162 | MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS); |
michael@0 | 163 | |
michael@0 | 164 | #endif |
michael@0 | 165 | aStack = Move(mStackBuffer); |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | #ifdef XP_LINUX |
michael@0 | 169 | |
michael@0 | 170 | int ThreadStackHelper::sInitialized; |
michael@0 | 171 | sem_t ThreadStackHelper::sSem; |
michael@0 | 172 | struct sigaction ThreadStackHelper::sOldSigAction; |
michael@0 | 173 | ThreadStackHelper* ThreadStackHelper::sCurrent; |
michael@0 | 174 | |
michael@0 | 175 | void |
michael@0 | 176 | ThreadStackHelper::SigAction(int aSignal, siginfo_t* aInfo, void* aContext) |
michael@0 | 177 | { |
michael@0 | 178 | ::sigaction(SIGPROF, &sOldSigAction, nullptr); |
michael@0 | 179 | sCurrent->FillStackBuffer(); |
michael@0 | 180 | sCurrent = nullptr; |
michael@0 | 181 | ::sem_post(&sSem); |
michael@0 | 182 | } |
michael@0 | 183 | |
michael@0 | 184 | #endif // XP_LINUX |
michael@0 | 185 | |
michael@0 | 186 | bool |
michael@0 | 187 | ThreadStackHelper::PrepareStackBuffer(Stack& aStack) { |
michael@0 | 188 | // Return false to skip getting the stack and return an empty stack |
michael@0 | 189 | aStack.clear(); |
michael@0 | 190 | #ifdef MOZ_ENABLE_PROFILER_SPS |
michael@0 | 191 | /* Normally, provided the profiler is enabled, it would be an error if we |
michael@0 | 192 | don't have a pseudostack here (the thread probably forgot to call |
michael@0 | 193 | profiler_register_thread). However, on B2G, profiling secondary threads |
michael@0 | 194 | may be disabled despite profiler being enabled. This is by-design and |
michael@0 | 195 | is not an error. */ |
michael@0 | 196 | #ifdef MOZ_WIDGET_GONK |
michael@0 | 197 | if (!mPseudoStack) { |
michael@0 | 198 | return false; |
michael@0 | 199 | } |
michael@0 | 200 | #endif |
michael@0 | 201 | MOZ_ASSERT(mPseudoStack); |
michael@0 | 202 | mStackBuffer.clear(); |
michael@0 | 203 | MOZ_ALWAYS_TRUE(mStackBuffer.reserve(mMaxStackSize)); |
michael@0 | 204 | return true; |
michael@0 | 205 | #else |
michael@0 | 206 | return false; |
michael@0 | 207 | #endif |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | void |
michael@0 | 211 | ThreadStackHelper::FillStackBuffer() { |
michael@0 | 212 | #ifdef MOZ_ENABLE_PROFILER_SPS |
michael@0 | 213 | size_t reservedSize = mMaxStackSize; |
michael@0 | 214 | |
michael@0 | 215 | // Go from front to back |
michael@0 | 216 | const volatile StackEntry* entry = mPseudoStack->mStack; |
michael@0 | 217 | const volatile StackEntry* end = entry + mPseudoStack->stackSize(); |
michael@0 | 218 | // Deduplicate identical, consecutive frames |
michael@0 | 219 | const char* prevLabel = nullptr; |
michael@0 | 220 | for (; reservedSize-- && entry != end; entry++) { |
michael@0 | 221 | /* We only accept non-copy labels, because |
michael@0 | 222 | we are unable to actually copy labels here */ |
michael@0 | 223 | if (entry->isCopyLabel()) { |
michael@0 | 224 | continue; |
michael@0 | 225 | } |
michael@0 | 226 | const char* const label = entry->label(); |
michael@0 | 227 | if (label == prevLabel) { |
michael@0 | 228 | continue; |
michael@0 | 229 | } |
michael@0 | 230 | mStackBuffer.infallibleAppend(label); |
michael@0 | 231 | prevLabel = label; |
michael@0 | 232 | } |
michael@0 | 233 | // If we exited early due to buffer size, expand the buffer for next time |
michael@0 | 234 | mMaxStackSize += (end - entry); |
michael@0 | 235 | #endif |
michael@0 | 236 | } |
michael@0 | 237 | |
michael@0 | 238 | } // namespace mozilla |