platform-win32.cpp (10853B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 // Copyright (c) 2006-2011 The Chromium Authors. All rights reserved. 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions 7 // are met: 8 // * Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // * Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in 12 // the documentation and/or other materials provided with the 13 // distribution. 14 // * Neither the name of Google, Inc. nor the names of its contributors 15 // may be used to endorse or promote products derived from this 16 // software without specific prior written permission. 17 // 18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 // COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 25 // OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 28 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 // SUCH DAMAGE. 30 31 #include <windows.h> 32 #include <mmsystem.h> 33 #include <process.h> 34 35 namespace mozilla { 36 namespace baseprofiler { 37 38 static int64_t MicrosecondsSince1970() { 39 int64_t prt; 40 FILETIME ft; 41 SYSTEMTIME st; 42 43 GetSystemTime(&st); 44 SystemTimeToFileTime(&st, &ft); 45 static_assert(sizeof(ft) == sizeof(prt), "Expect FILETIME to be 64 bits"); 46 memcpy(&prt, &ft, sizeof(prt)); 47 const int64_t epochBias = 116444736000000000LL; 48 prt = (prt - epochBias) / 10; 49 50 return prt; 51 } 52 53 void* GetStackTop(void* aGuess) { 54 PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb()); 55 return reinterpret_cast<void*>(pTib->StackBase); 56 } 57 58 static void PopulateRegsFromContext(Registers& aRegs, CONTEXT* aContext) { 59 #if defined(GP_ARCH_amd64) 60 aRegs.mPC = reinterpret_cast<Address>(aContext->Rip); 61 aRegs.mSP = reinterpret_cast<Address>(aContext->Rsp); 62 aRegs.mFP = reinterpret_cast<Address>(aContext->Rbp); 63 aRegs.mR10 = reinterpret_cast<Address>(aContext->R10); 64 aRegs.mR12 = reinterpret_cast<Address>(aContext->R12); 65 #elif defined(GP_ARCH_x86) 66 aRegs.mPC = reinterpret_cast<Address>(aContext->Eip); 67 aRegs.mSP = reinterpret_cast<Address>(aContext->Esp); 68 aRegs.mFP = reinterpret_cast<Address>(aContext->Ebp); 69 aRegs.mEcx = reinterpret_cast<Address>(aContext->Ecx); 70 aRegs.mEdx = reinterpret_cast<Address>(aContext->Edx); 71 #elif defined(GP_ARCH_arm64) 72 aRegs.mPC = reinterpret_cast<Address>(aContext->Pc); 73 aRegs.mSP = reinterpret_cast<Address>(aContext->Sp); 74 aRegs.mFP = reinterpret_cast<Address>(aContext->Fp); 75 aRegs.mLR = reinterpret_cast<Address>(aContext->Lr); 76 aRegs.mR11 = reinterpret_cast<Address>(aContext->X11); 77 #else 78 # error "bad arch" 79 #endif 80 } 81 82 // Gets a real (i.e. not pseudo) handle for the current thread, with the 83 // permissions needed for profiling. 84 // @return a real HANDLE for the current thread. 85 static HANDLE GetRealCurrentThreadHandleForProfiling() { 86 HANDLE realCurrentThreadHandle; 87 if (!::DuplicateHandle( 88 ::GetCurrentProcess(), ::GetCurrentThread(), ::GetCurrentProcess(), 89 &realCurrentThreadHandle, 90 THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, 91 FALSE, 0)) { 92 return nullptr; 93 } 94 95 return realCurrentThreadHandle; 96 } 97 98 class PlatformData { 99 public: 100 // Get a handle to the calling thread. This is the thread that we are 101 // going to profile. We need a real handle because we are going to use it in 102 // the sampler thread. 103 explicit PlatformData(BaseProfilerThreadId aThreadId) 104 : mProfiledThread(GetRealCurrentThreadHandleForProfiling()) { 105 MOZ_ASSERT(DWORD(aThreadId.ToNumber()) == ::GetCurrentThreadId()); 106 } 107 108 ~PlatformData() { 109 if (mProfiledThread != nullptr) { 110 CloseHandle(mProfiledThread); 111 mProfiledThread = nullptr; 112 } 113 } 114 115 HANDLE ProfiledThread() { return mProfiledThread; } 116 117 private: 118 HANDLE mProfiledThread; 119 }; 120 121 #if defined(USE_MOZ_STACK_WALK) 122 HANDLE 123 GetThreadHandle(PlatformData* aData) { return aData->ProfiledThread(); } 124 #endif 125 126 static const HANDLE kNoThread = INVALID_HANDLE_VALUE; 127 128 //////////////////////////////////////////////////////////////////////// 129 // BEGIN Sampler target specifics 130 131 Sampler::Sampler(PSLockRef aLock) {} 132 133 void Sampler::Disable(PSLockRef aLock) {} 134 135 template <typename Func> 136 void Sampler::SuspendAndSampleAndResumeThread( 137 PSLockRef aLock, const RegisteredThread& aRegisteredThread, 138 const TimeStamp& aNow, const Func& aProcessRegs) { 139 HANDLE profiled_thread = 140 aRegisteredThread.GetPlatformData()->ProfiledThread(); 141 if (profiled_thread == nullptr) { 142 return; 143 } 144 145 // Context used for sampling the register state of the profiled thread. 146 CONTEXT context; 147 memset(&context, 0, sizeof(context)); 148 149 //----------------------------------------------------------------// 150 // Suspend the samplee thread and get its context. 151 152 static const DWORD kSuspendFailed = static_cast<DWORD>(-1); 153 if (SuspendThread(profiled_thread) == kSuspendFailed) { 154 return; 155 } 156 157 // SuspendThread is asynchronous, so the thread may still be running. 158 // Call GetThreadContext first to ensure the thread is really suspended. 159 // See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743. 160 161 // Using only CONTEXT_CONTROL is faster but on 64-bit it causes crashes in 162 // RtlVirtualUnwind (see bug 1120126) so we set all the flags. 163 #if defined(GP_ARCH_amd64) 164 context.ContextFlags = CONTEXT_FULL; 165 #else 166 context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; 167 #endif 168 if (!GetThreadContext(profiled_thread, &context)) { 169 ResumeThread(profiled_thread); 170 return; 171 } 172 173 //----------------------------------------------------------------// 174 // Sample the target thread. 175 176 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 177 // 178 // The profiler's "critical section" begins here. We must be very careful 179 // what we do here, or risk deadlock. See the corresponding comment in 180 // platform-linux-android.cpp for details. 181 182 Registers regs; 183 PopulateRegsFromContext(regs, &context); 184 aProcessRegs(regs, aNow); 185 186 //----------------------------------------------------------------// 187 // Resume the target thread. 188 189 ResumeThread(profiled_thread); 190 191 // The profiler's critical section ends here. 192 // 193 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 194 } 195 196 // END Sampler target specifics 197 //////////////////////////////////////////////////////////////////////// 198 199 //////////////////////////////////////////////////////////////////////// 200 // BEGIN SamplerThread target specifics 201 202 static unsigned int __stdcall ThreadEntry(void* aArg) { 203 auto thread = static_cast<SamplerThread*>(aArg); 204 thread->Run(); 205 return 0; 206 } 207 208 SamplerThread::SamplerThread(PSLockRef aLock, uint32_t aActivityGeneration, 209 double aIntervalMilliseconds, uint32_t aFeatures) 210 : mSampler(aLock), 211 mActivityGeneration(aActivityGeneration), 212 mIntervalMicroseconds( 213 std::max(1, int(floor(aIntervalMilliseconds * 1000 + 0.5)))), 214 mNoTimerResolutionChange( 215 ProfilerFeature::HasNoTimerResolutionChange(aFeatures)) { 216 if ((!mNoTimerResolutionChange) && (mIntervalMicroseconds < 10 * 1000)) { 217 // By default the timer resolution (which tends to be 1/64Hz, around 16ms) 218 // is not changed. However, if the requested interval is sufficiently low, 219 // the resolution will be adjusted to match. Note that this affects all 220 // timers in Firefox, and could therefore hide issues while profiling. This 221 // change may be prevented with the "notimerresolutionchange" feature. 222 ::timeBeginPeriod(mIntervalMicroseconds / 1000); 223 } 224 225 // Create a new thread. It is important to use _beginthreadex() instead of 226 // the Win32 function CreateThread(), because the CreateThread() does not 227 // initialize thread-specific structures in the C runtime library. 228 mThread = reinterpret_cast<HANDLE>(_beginthreadex(nullptr, 229 /* stack_size */ 0, 230 ThreadEntry, this, 231 /* initflag */ 0, nullptr)); 232 if (mThread == 0) { 233 MOZ_CRASH("_beginthreadex failed"); 234 } 235 } 236 237 SamplerThread::~SamplerThread() { 238 WaitForSingleObject(mThread, INFINITE); 239 240 // Close our own handle for the thread. 241 if (mThread != kNoThread) { 242 CloseHandle(mThread); 243 } 244 } 245 246 void SamplerThread::SleepMicro(uint32_t aMicroseconds) { 247 // For now, keep the old behaviour of minimum Sleep(1), even for 248 // smaller-than-usual sleeps after an overshoot, unless the user has 249 // explicitly opted into a sub-millisecond profiler interval. 250 if (mIntervalMicroseconds >= 1000) { 251 ::Sleep(std::max(1u, aMicroseconds / 1000)); 252 } else { 253 TimeStamp start = TimeStamp::Now(); 254 TimeStamp end = start + TimeDuration::FromMicroseconds(aMicroseconds); 255 256 // First, sleep for as many whole milliseconds as possible. 257 if (aMicroseconds >= 1000) { 258 ::Sleep(aMicroseconds / 1000); 259 } 260 261 // Then, spin until enough time has passed. 262 while (TimeStamp::Now() < end) { 263 YieldProcessor(); 264 } 265 } 266 } 267 268 void SamplerThread::Stop(PSLockRef aLock) { 269 if ((!mNoTimerResolutionChange) && (mIntervalMicroseconds < 10 * 1000)) { 270 // Disable any timer resolution changes we've made. Do it now while 271 // gPSMutex is locked, i.e. before any other SamplerThread can be created 272 // and call ::timeBeginPeriod(). 273 // 274 // It's safe to do this now even though this SamplerThread is still alive, 275 // because the next time the main loop of Run() iterates it won't get past 276 // the mActivityGeneration check, and so it won't make any more ::Sleep() 277 // calls. 278 ::timeEndPeriod(mIntervalMicroseconds / 1000); 279 } 280 281 mSampler.Disable(aLock); 282 } 283 284 // END SamplerThread target specifics 285 //////////////////////////////////////////////////////////////////////// 286 287 static void PlatformInit(PSLockRef aLock) {} 288 289 #if defined(HAVE_NATIVE_UNWIND) 290 # define REGISTERS_SYNC_POPULATE(regs) \ 291 CONTEXT context; \ 292 RtlCaptureContext(&context); \ 293 PopulateRegsFromContext(regs, &context); 294 #endif 295 296 } // namespace baseprofiler 297 } // namespace mozilla