//----------------------------------------------------------------------------- // Real OS thread proc for Canary thread. // param - 'this' pointer for HelperCanary // return value - meaningless, but threads need to return something. //----------------------------------------------------------------------------- DWORD HelperCanary::ThreadProc(LPVOID param) { _ASSERTE(!ThisIsHelperThreadWorker()); STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary thread spun up\n"); HelperCanary * pThis = reinterpret_cast<HelperCanary*> (param); pThis->ThreadProc(); _ASSERTE(pThis->m_fStop); STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary thread exiting\n"); return 0; }
void StackFrameIterator::InternalInitForStackTrace() { STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ StackTrace ]\n"); Thread * pThreadToWalk = ThreadStore::GetCurrentThread(); PTR_VOID pFrame = pThreadToWalk->GetTransitionFrameForStackTrace(); InternalInit(pThreadToWalk, GetPInvokeTransitionFrame(pFrame)); }
void StackFrameIterator::InternalInitForEH(Thread * pThreadToWalk, PAL_LIMITED_CONTEXT * pCtx) { STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ EH ]\n"); StackFrameIterator::InternalInit(pThreadToWalk, pCtx, ApplyReturnAddressAdjustment); STRESS_LOG1(LF_STACKWALK, LL_INFO10000, " %p\n", m_ControlPC); }
StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PTR_PAL_LIMITED_CONTEXT pCtx) { STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ hijack ]\n"); InternalInit(pThreadToWalk, pCtx, 0); }
StackFrameIterator::StackFrameIterator(Thread * pThreadToWalk, PTR_VOID pInitialTransitionFrame) { STRESS_LOG0(LF_STACKWALK, LL_INFO10000, "----Init---- [ GC ]\n"); ASSERT(!pThreadToWalk->DangerousCrossThreadIsHijacked()); InternalInit(pThreadToWalk, GetPInvokeTransitionFrame(pInitialTransitionFrame)); }
//----------------------------------------------------------------------------- // Does real work for AreLocksAvailable(), minus caching. //----------------------------------------------------------------------------- bool HelperCanary::AreLocksAvailableWorker() { #if _DEBUG // For debugging, allow a way to force the canary to fail, and thus test our // failure paths. static BOOL fShortcut= -1; if (fShortcut == -1) { fShortcut = UnsafeGetConfigDWORD(CLRConfig::INTERNAL_DbgShortcutCanary); } if (fShortcut == 1) { return false; } if (fShortcut == 2) { return true; } #endif // We used to do lazy init but that is dangerous... CreateThread // allocates some memory which can block on a lock, exactly the // situation we are attempting to detect and not block on. // Instead we spin up the canary in advance and if that failed then // assume unsafe if(m_CanaryThreadId == 0) { _ASSERTE(!"We shouldn't be lazy initing the canary anymore"); return false; } // Canary will take the locks of interest and then set the Answer counter equal to our request counter. m_RequestCounter = m_RequestCounter + 1; ResetEvent(m_hWaitEvent); SetEvent(m_hPingEvent); // Spin waiting for answer. If canary gets back to us, then the locks must be free and so it's safe for helper-thread. // If we timeout, then we err on the side of safety and assume canary blocked on a lock and so it's not safe // for the helper thread to take those locks. // We explicitly have a simple spin-wait instead of using win32 events because we want something simple and // provably correct. Since we already need the spin-wait for the counters, adding an extra win32 event // to get rid of the sleep would be additional complexity and race windows without a clear benefit. // We need to track what iteration of "AreLocksAvailable" the helper is on. Say canary sniffs two locks, now Imagine if: // 1) Helper calls AreLocksAvailable, // 2) the canary does get blocked on lock #1, // 3) process resumes, canary now gets + releases lock #1, // 4) another random thread takes lock #1 // 5) then helper calls AreLocksAvailable again later // 6) then the canary finally finishes. Note it's never tested lock #1 on the 2nd iteration. // We don't want the canary's response initiated from the 1st request to impact the Helper's 2nd request. // Thus we keep a request / answer counter to make sure that the canary tests all locks on the same iteration. DWORD retry = 0; const DWORD msSleepSteadyState = 150; // sleep time in ms const DWORD maxRetry = 15; // number of times to try. DWORD msSleep = 80; // how much to sleep on first iteration. while(m_RequestCounter != m_AnswerCounter) { retry ++; if (retry > maxRetry) { STRESS_LOG0(LF_CORDB, LL_ALWAYS, "Canary timed out!\n"); return false; } // We'll either timeout (in which case it's like a Sleep(), or // get the event, which shortcuts the sleep. WaitForSingleObject(m_hWaitEvent, msSleep); // In case a stale answer sets the wait event high, reset it now to avoid us doing // a live spin-lock. ResetEvent(m_hWaitEvent); msSleep = msSleepSteadyState; } // Canary made it on same Request iteration, so it must be safe! return true; }