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