diff options
Diffstat (limited to 'src/vm/stackprobe.cpp')
-rw-r--r-- | src/vm/stackprobe.cpp | 1765 |
1 files changed, 0 insertions, 1765 deletions
diff --git a/src/vm/stackprobe.cpp b/src/vm/stackprobe.cpp deleted file mode 100644 index 7e40ca8738..0000000000 --- a/src/vm/stackprobe.cpp +++ /dev/null @@ -1,1765 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -// - -// -//----------------------------------------------------------------------------- -// StackProbe.cpp -//----------------------------------------------------------------------------- - - -#include "common.h" -#include "stackprobe.h" - - -#ifdef FEATURE_STACK_PROBE - - -// SOTolerantBoundaryFilter is called when an exception in SO-tolerant code arrives -// at the boundary back into SO-intolerant code. -// -// If we are running in an environment where we must be hardened to SO, then we must -// catch the exception if there is not enough space to run our backout code (the stuff in the -// EX_CATCH clauses). We also cannot let a hard SO propogate into SO-intolerant code, because -// we rip the process if that happens (we have no way to tell that the SO is ok.) -int SOTolerantBoundaryFilter(EXCEPTION_POINTERS *pExceptionInfo, DWORD * pdwSOTolerantFlags) -{ - Thread *pThread = GetThread(); - _ASSERTE(pThread); - _ASSERTE(pdwSOTolerantFlags != NULL); - _ASSERTE(!((*pdwSOTolerantFlags) & BSTC_TRIGGERING_UNWIND_FOR_SO)); - - SaveCurrentExceptionInfo(pExceptionInfo->ExceptionRecord, pExceptionInfo->ContextRecord); - - NTSTATUS exceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; - - // We must always handle a hard SO - if (IsSOExceptionCode(exceptionCode)) - { - if (exceptionCode == EXCEPTION_SOFTSO) - { - *pdwSOTolerantFlags |= BSTC_IS_SOFT_SO; - } - *pdwSOTolerantFlags |= BSTC_IS_SO; - - if (!CLRHosted() || pThread == NULL || GetEEPolicy()->GetActionOnFailure(FAIL_StackOverflow) != eRudeUnloadAppDomain) - { - // For security reason, it is not safe to continue execution if stack overflow happens - // unless a host tells us to do something different. - EEPolicy::HandleFatalStackOverflow(pExceptionInfo); - } - - /* If there is a SO_INTOLERANT region above this */ - /* we should have processed it already in SOIntolerantTransitionHandler */ - EEPolicy::HandleStackOverflow(SOD_SOTolerantTransitor, FRAME_TOP); - - *pdwSOTolerantFlags |= BSTC_TRIGGERING_UNWIND_FOR_SO; - - return EXCEPTION_EXECUTE_HANDLER; - } - - // Make sure we have enough stack to run our backout code. If not, - // catch the exception. - if (! pThread->IsStackSpaceAvailable(ADJUST_PROBE(BACKOUT_CODE_STACK_LIMIT))) - { - *pdwSOTolerantFlags |= BSTC_TRIGGERING_UNWIND_FOR_SO; - return EXCEPTION_EXECUTE_HANDLER; - } - - - return EXCEPTION_CONTINUE_SEARCH; -} - -void SOTolerantCode_RecoverStack(DWORD dwFlags) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - MODE_ANY; - SO_TOLERANT; - } - CONTRACTL_END; - - Thread * pThread = GetThread(); - if (!(dwFlags & BSTC_IS_SOFT_SO)) - { - pThread->RestoreGuardPage(); - } - if (dwFlags & BSTC_IS_SO) - { - if (!pThread->PreemptiveGCDisabled()) - { - pThread->DisablePreemptiveGC(); - } - } - COMPlusThrowSO(); -} - -void SOTolerantCode_ExceptBody(DWORD * pdwFlags, Frame * pSafeForSOFrame) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - SO_TOLERANT; - } - CONTRACTL_END; - - // do nothing here. Get our stack back post-catch and then throw a new exception - *pdwFlags |= BSTC_RECOVER_STACK; - if (*pdwFlags & BSTC_IS_SO) - { - // If this assertion fires, then it means that we have not unwound the frame chain - Thread * pThread = GetThread(); - _ASSERTE(pSafeForSOFrame == pThread->GetFrame()); - pThread->ClearExceptionStateAfterSO(pSafeForSOFrame); - } -} - -// -// ReportStackOverflow is called when our probe infrastructure detects that there -// is insufficient stack to perform the operation. -// - -void ReportStackOverflow() -{ - WRAPPER_NO_CONTRACT; - - _ASSERTE(IsStackProbingEnabled()); - - Thread *pThread = GetThread(); - - if (pThread != NULL) - { - // We don't want an SO to happen while we are trying to throw this one. So check if there - // is enough space left to handle an exception (this translates to check that we have stack - // space left equivalent to the soft guard region). If not, then remove the guard page by - // forcing a hard SO. This effectively turns the SO into a boundary SO. - - // We should only ever get in this situation on a probe from managed code. From within the EE, - // we will never let our probe point get this close. Either way, we'd rip the process if a hard - // SO occurred. - - UINT_PTR stackGuarantee = pThread->GetStackGuarantee(); - - // We expect the stackGuarantee to be a multiple of the page size for - // the call to IsStackSpaceAvailable. - _ASSERTE(stackGuarantee%GetOsPageSize() == 0); - if (pThread->IsStackSpaceAvailable(static_cast<float>(stackGuarantee)/GetOsPageSize())) - { - COMPlusThrowSO(); - } - - // If there isn't much stack left to attempt to report a soft stack overflow, let's trigger a hard - // SO, so we clear the guard page and give us at least another page of stack to work with. - - if (!pThread->IsStackSpaceAvailable(ADJUST_PROBE(1))) - { - DontCallDirectlyForceStackOverflow(); - } - } - - RaiseException(EXCEPTION_SOFTSO, 0, 0, NULL); -} - -void CheckForSOInSOIntolerantCode() -{ - Thread *pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - return; - } - // We use the location of frames to decide SO mode. But during exception, - // we may not unwind some frames, for example: TPMethodFrame, therefore - // it is not safe to apply this check. - //_ASSERTE(!pThread->IsSOTolerant(FRAME_TOP)); - if (! pThread->IsSPBeyondLimit()) - { - return; - } - EEPolicy::HandleStackOverflow(SOD_SOIntolerantTransitor, FRAME_TOP); - _ASSERTE (!"Can not reach here"); -} - -//--------------------------------------------------------------------------------------- -// -// SetSOIntolerantTransitionMarker: Use the current frame as our marker for intolerant transition. -// -// Arguments: -// None. -// -// Return Value: -// None. -// -// Note: -// SO mode is determined by what is on stack. If we see our intolerant transtion first, we are in SO. -// Because compiler lays object in a function at random stack location, the address of our intolerant -// transition object SOIntolerantTransitionHandler may be before the HelperMethodFrame. Therefore, we -// can not use the address of the handlers. Instead we use the current top frame. -// -void SetSOIntolerantTransitionMarker() -{ - LIMITED_METHOD_CONTRACT; - - Thread *pThread = GetThreadNULLOk(); - if (pThread == NULL) - { - return; - } - Frame *pFrame = pThread->GetFrame(); - - // - // Check to see if the Frame chain is corrupt - // This can happen when unmanaged code calls back to managed code - // - if (pFrame != FRAME_TOP) - { - // SafeGetGCCookiePtr examines the value of the vtable pointer - // and makes sure that it is a legal Frame subtype. - // It returns NULL when we have an illegal (i.e. corrupt) vtable value. - // - if (!Frame::HasValidVTablePtr(pFrame)) - DoJITFailFast(); - } - - // We use pFrame - 1 as our marker so that IntolerantTransitionHandler is seen before - // a transition frame. - ClrFlsSetValue(TlsIdx_SOIntolerantTransitionHandler, (void*)(((size_t)pFrame)-1)); - - _ASSERTE(!pThread->IsSOTolerant(FRAME_TOP)); -} - -BOOL RetailStackProbeNoThrowNoThread(unsigned int n) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_SO_TOLERANT; - STATIC_CONTRACT_MODE_ANY; - - BEGIN_GETTHREAD_ALLOWED; - Thread *pThread = GetThread(); - - if (!pThread) - { - // we only probe on managed threads - return TRUE; - } - return RetailStackProbeNoThrow(n, pThread); - END_GETTHREAD_ALLOWED; -} - -// This functions are used by the stack probe infrastucture that is outside the VM -// tree. It needs to call into the VM code in order to probe properly. -void InitStackProbesRetail() -{ - LIMITED_METHOD_CONTRACT; - g_fpCheckForSOInSOIntolerantCode = CheckForSOInSOIntolerantCode; - g_fpSetSOIntolerantTransitionMarker = SetSOIntolerantTransitionMarker; - g_fpDoProbe = RetailStackProbeNoThrowNoThread; - g_fpHandleSoftStackOverflow = EEPolicy::HandleSoftStackOverflow; - - g_StackProbingEnabled = g_pConfig->ProbeForStackOverflow() != 0; -} - -// Shared by both the nothrow and throwing version. FORCEINLINE into both to avoid the call overhead. -FORCEINLINE BOOL RetailStackProbeHelper(unsigned int n, Thread *pThread) -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - SO_TOLERANT; - MODE_ANY; - } - CONTRACTL_END; - - UINT_PTR probeLimit; - - // @TODO - Need to devise a probe that doesn't require the thread object - if (pThread == NULL) - { - UINT_PTR stackLimit = (UINT_PTR)Thread::GetStackLowerBound(); - probeLimit = Thread::GetLastNormalStackAddress(stackLimit); - } - else - { - probeLimit = pThread->GetProbeLimit(); - } - UINT_PTR probeAddress = (UINT_PTR)(&pThread) - (n * GetOsPageSize()); - - // If the address we want to probe to is beyond the precalculated limit we fail - // Note that we don't check for stack probing being disabled. This is encoded in - // the value returned from GetProbeLimit, which will be 0 if probing is disabled. - if (probeAddress < probeLimit) - { -#if 0 - // @todo : remove this when iexplore, W3WP.EXE and friends allocate 512K instead - // of 256K for their stack. - if (((char *)(pThread->GetCachedStackBase()) - (char *)(pThread->GetCachedStackLimit())) < 0x41000) - { - return true; - } -#endif - return FALSE; - } - - return TRUE; -} - -BOOL RetailStackProbeNoThrowWorker(unsigned int n, Thread *pThread) -{ - WRAPPER_NO_CONTRACT; - return RetailStackProbeHelper(n, pThread); -} - -void RetailStackProbeWorker(unsigned int n, Thread *pThread) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_SO_TOLERANT; - - if (RetailStackProbeHelper(n, pThread)) - { - return; - } - ReportStackOverflow(); -} - -void DefaultRetailStackProbeWorker(Thread *pThread) -{ - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_SO_TOLERANT; - - if (RetailStackProbeHelper(ADJUST_PROBE(DEFAULT_ENTRY_PROBE_AMOUNT), pThread)) - { - return; - } - ReportStackOverflow(); -} - -#endif // FEATURE_STACK_PROBE - -#ifdef STACK_GUARDS_DEBUG - -DWORD g_InteriorProbeAmount = DEFAULT_INTERIOR_PROBE_AMOUNT; - -DWORD g_CurrentStackGuardTlsIdx = (DWORD) -1; -DWORD g_UniqueId = 0; - -// If this has a non-zero value, we'll mark only those pages whose probe line number matches. This allows us -// to turn protection on only for a specific probe so that can find multiple w/o having to rebuild. Otherwise -// can never get past that first AV in the debugger. -unsigned int g_ProtectStackPagesInDebuggerForProbeAtLine = 0; - -// These two are used to the amount probed for at a particular line number -unsigned int g_UpdateProbeAtLine = 0; -SString* g_pUpdateProbeAtLineInFile = NULL; -unsigned int g_UpdateProbeAtLineAmount = 0; - -// If this is TRUE, we'll break in the debugger if we try to probe during the handling of a -// probe-induced stack overflow. -BOOL g_BreakOnProbeDuringSO = FALSE; - -// If this is TRUE, probe cookie validation via assertion is enabled -// disable assertions on debug build. The stack consumption is different enough -// that we'll always be getting spurious failures. -BOOL g_probeAssertOnOverrun = FALSE; - -// SO logging pollutes the EH logging space and vice-versa. The SOLogger class -// allows us to turn SO logging on separately and only produce SO logging, or -// to allow both. -#undef LOG -#define LOG(x) s_SOLogger.LogSpew x - -class SOLogger { - - enum SOLogStyle { - SO_LOGGING_NONE, // No SO logging - SO_LOGGING_SEPARATE_LOG, // Log SO to separate file - SO_LOGGING_STANDARD_LOG // Log SO to standard log - }; - - SOLogStyle m_SOLogStyle; - FILE *m_SOLoggerFile; - -public: - SOLogger(); - ~SOLogger(); - - void Initialize(); - - void LogSpew(DWORD facility, DWORD level, const char *fmt, ... ); -}; - -static SOLogger s_SOLogger; - -SOLogger::SOLogger() - : m_SOLogStyle(SO_LOGGING_NONE), m_SOLoggerFile(NULL) -{ -} - -void SOLogger::Initialize() -{ - WRAPPER_NO_CONTRACT; - - DWORD SOLogger = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOLogger, SO_LOGGING_NONE); - if (SOLogger == SO_LOGGING_SEPARATE_LOG) - { - m_SOLogStyle = SO_LOGGING_SEPARATE_LOG; - int ec = fopen_s(&m_SOLoggerFile, "SOLogSpewFile.log", "w"); - _ASSERTE(SUCCEEDED(ec)); - } - else if (SOLogger == SO_LOGGING_STANDARD_LOG) - { - m_SOLogStyle = SO_LOGGING_STANDARD_LOG; - } - else if (SOLogger == SO_LOGGING_NONE) - { - m_SOLogStyle = SO_LOGGING_NONE; - } - else - { - _ASSERTE(!"Invalid SOLogger value"); - } -} - -SOLogger::~SOLogger() -{ - LIMITED_METHOD_CONTRACT; - if (m_SOLoggerFile != NULL) - { - fclose(m_SOLoggerFile); - } -} - -void SOLogger::LogSpew(DWORD facility, DWORD level, const char *fmt, ... ) -{ - STATIC_CONTRACT_WRAPPER; - - if (m_SOLogStyle == SO_LOGGING_NONE) - { - return; - } - - va_list args; - va_start(args, fmt); - if (m_SOLogStyle == SO_LOGGING_SEPARATE_LOG) - { - vfprintf(m_SOLoggerFile, fmt, args); - } - else if (LoggingEnabled()) - { - LogSpewValist (facility, level, fmt, args); - } - va_end(args); -} - -#define MORE_INFO_STRING \ - "\nPlease open a bug against the feature owner.\n" \ - "\nFor details about this feature, see, in a CLR enlistment, src\\ndp\\clr\\doc\\OtherDevDocs\\untriaged\\clrdev_web\\SO Guide for CLR Developers.doc\n" - - -// The following are used to support the SO-injection framework -HMODULE BaseStackGuard::m_hProbeCallBack = 0; -BaseStackGuard::ProbeCallbackType BaseStackGuard::m_pfnProbeCallback = NULL; - -// -// ShouldValidateSOToleranceOnThisThread determines if we should check for SO_Tolerance on this -// thread. -// -// If it is a thread we care about, then we will assert if it calls an SO-intolerant function -// outside of a probe -// -BOOL ShouldValidateSOToleranceOnThisThread() -{ - LIMITED_METHOD_CONTRACT; - - if (g_StackProbingEnabled == false || g_fEEShutDown == TRUE) - { - return FALSE; - } - - BEGIN_GETTHREAD_ALLOWED; - Thread *pThread = GetThread(); - if (pThread == NULL || ShouldProbeOnThisThread() == FALSE) - { - return FALSE; - } - - // We only want to probe on managed threads that have IL on the stack behind them. But - // there's not an easy way to check for that, so we use whether or not we own the thread and - // whether or not a stack guard is in place. - // - // If we don't own the thread, then just make sure that we didn't get here by leaving the EE and coming - // back in. (In which case we would have installed a probe and the GetCurrentStackGuard is non-NULL). - // We are only probing on managed threads, but we want to avoid asserting for cases where an unmanaged - // app starts the EE (thereby creating a managed thread), and runs completely unmanaged, but uses some of the CLR's - // infrastructure, such as Crsts. - if (pThread->DoWeOwn() == FALSE && pThread->GetCurrentStackGuard() == NULL) - { - return FALSE; - } - - if (! IsHandleNullUnchecked(pThread->GetThrowableAsHandle())) - { - return FALSE; - } - - return TRUE; - END_GETTHREAD_ALLOWED; -} - - -BOOL BaseStackGuard_RequiresNStackPages(BaseStackGuardGeneric *pGuard, unsigned int n, BOOL fThrowOnSO) -{ - return ((BaseStackGuard*)pGuard)->RequiresNStackPages(n, fThrowOnSO); -} - -void BaseStackGuard_CheckStack(BaseStackGuardGeneric *pGuard) -{ - WRAPPER_NO_CONTRACT; - ((BaseStackGuard*)pGuard)->CheckStack(); -} - -BOOL CheckNStackPagesAvailable(unsigned int n) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_SO_TOLERANT; - STATIC_CONTRACT_MODE_ANY; - - BEGIN_GETTHREAD_ALLOWED; - Thread *pThread = GetThread(); - - // If we don't have a managed thread object, we assume that we have the requested - // number of pages available. - if (!pThread) - return TRUE; - - _ASSERTE(FitsIn<float>(n)); - return pThread->IsStackSpaceAvailable(static_cast<float>(n)); - END_GETTHREAD_ALLOWED; -} - -void InitStackProbes() -{ - WRAPPER_NO_CONTRACT; - - g_CurrentStackGuardTlsIdx = TlsIdx_StackProbe; - - s_SOLogger.Initialize(); - - // If we're in a debugger, and if the config word below is set, then we'll go ahead and protect stack pages - // when we're run under a debugger. - //if (IsDebuggerPresent()) - //{ - if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOEnableStackProtectionInDebugger) == 1) - { - g_ProtectStackPagesInDebugger = TRUE; - } - g_ProtectStackPagesInDebuggerForProbeAtLine = - CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOEnableStackProtectionInDebuggerForProbeAtLine); - - g_UpdateProbeAtLine = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOUpdateProbeAtLine); - g_UpdateProbeAtLineAmount = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOUpdateProbeAtLineAmount); - LPWSTR wszUpdateProbeAtLineInFile = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOUpdateProbeAtLineInFile); - g_pUpdateProbeAtLineInFile = new SString(wszUpdateProbeAtLineInFile); - g_pUpdateProbeAtLineInFile->Normalize(); - - if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_SOBreakOnProbeDuringSO) == 1) - { - g_BreakOnProbeDuringSO = TRUE; - } - //} - - // Never let g_EntryPointProbeAmount get set to an invalid value of <= 0 to avoid races in places that might be - // about to probe as we set it. - BOOL entryPointProbeAmount = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOEntryPointProbe, g_EntryPointProbeAmount); - if (entryPointProbeAmount > 0) - { - g_EntryPointProbeAmount = entryPointProbeAmount; - } - - BOOL interiorProbeAmount = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOInteriorProbe, g_InteriorProbeAmount); - if (interiorProbeAmount > 0) - { - g_InteriorProbeAmount = interiorProbeAmount; - } - - BOOL enableBackoutStackValidation = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOEnableBackoutStackValidation, FALSE); - - g_EnableDefaultRWValidation = 1; - - BOOL enableDefaultRWValidation = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOEnableDefaultRWValidation, g_EnableDefaultRWValidation); - - - - // put this first because it will cause probe validation via contract otherwise - g_probeAssertOnOverrun = REGUTIL::GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SOProbeAssertOnOverrun, g_probeAssertOnOverrun); - - BaseStackGuard::InitProbeReportingToFaultInjectionFramework(); - - g_EnableBackoutStackValidation = enableBackoutStackValidation; - - g_EnableDefaultRWValidation = enableDefaultRWValidation; - - g_fpShouldValidateSOToleranceOnThisThread = ShouldValidateSOToleranceOnThisThread; - - g_fpRestoreCurrentStackGuard = BaseStackGuard::RestoreCurrentGuard; - g_fpHandleStackOverflowAfterCatch = EEPolicy::HandleStackOverflowAfterCatch; - - - g_fp_BaseStackGuard_RequiresNStackPages = BaseStackGuard_RequiresNStackPages; - g_fp_BaseStackGuard_CheckStack = BaseStackGuard_CheckStack; - - g_fpCheckNStackPagesAvailable = CheckNStackPagesAvailable; - - InitStackProbesRetail(); - -} - -void CloseSOTolerantViolationFile(); - -// -// This function is called when the EE is shutting down and we want to stop -// doing stack probing. Don't clear the g_CurrentStackGuardTlsIdx field though, -// because there may still be other threads in the process of probing and -// they'll AV if we pull the g_CurrentStackGuardTlsIdx out from under them. -void TerminateStackProbes() -{ - WRAPPER_NO_CONTRACT; - - - CloseSOTolerantViolationFile(); - - // Don't actually shut down the SO infrastructure. We've got multiple threads - // racing around in the runtime, and they can be left in an inconsisent state - // if we flip this off. - - return; -#if 0 - // Yank the stack guard on this thread - StackGuardDisabler __guardDisable; - __guardDisable.NeverRestoreGuard(); - - // Clear out the current guard in case we terminate and its cleanup code - // does not get to run. - BaseStackGuard::SetCurrentGuard(NULL); - - g_StackProbingEnabled = false; - g_EnableBackoutStackValidation = FALSE; - g_fpShouldValidateSOToleranceOnThisThread = NULL; -#endif -} - -//----------------------------------------------------------------------------- -// Error handling when we go past a stack guard. -// We have different messages to more aggressively diagnose the problem -//----------------------------------------------------------------------------- - -// Called by Check_Stack when we overwrite the cookie -void BaseStackGuard::HandleOverwrittenThisStackGuard(__in_z char *stackID) -{ - LIMITED_METHOD_CONTRACT; - - if (! g_probeAssertOnOverrun) - { - return; - } - - ClrDebugState *pState = GetClrDebugState(); - _ASSERTE(pState); - if (pState->IsSONotMainline()) - { - return; - } - - // This prevents infinite loops in this function if we call something that probes. - // Must do it after the check for pState->IsSONotMainline() to give the first invocation - // a chance to run. - SO_NOT_MAINLINE_FUNCTION; - - // This fires at a closing Check_Stack. - // The cookie set by Requires_?K_stack was overwritten. We detected that at - // the closing call to check_stack. - - // To fix, increase the guard size at the specified ip. - // - // A debugging trick: If you can set a breakpoint at the opening Requires_?K_Stack - // macro for this instance, you can step in and see where the cookie is actually - // placed. Then, place a breakpoint that triggers when (DWORD*) 0xYYYYYYYY changes. - // Continue execution. The breakpoint will fire exactly when the cookie is over-written. - char buff[1024]; - buff[0] = '\0'; - - sprintf_s(buff, COUNTOF(buff), - "STACK GUARD VIOLATION\n" - "The%s stack guard installed in %s at \"%s\" @ %d requested %d pages of stack.\n" - "\nIf this is easily reproduced, please rerun the test under the debugger with the\n" - "DWORD environment variable COMPlus_SOEnableStackProtectionInDebugger\n" - "set to 1. This will cause an AV at the point of overrun.\n" - "Attach the stack trace at that point to the bug in addition to this assert." - MORE_INFO_STRING, stackID ? stackID : "", - m_szFunction, m_szFile, m_lineNum, m_numPages); - - LOG((LF_EH, LL_INFO100000, "%s", buff)); - - DbgAssertDialog((char *)m_szFile, m_lineNum, buff); - -} - -void BaseStackGuard::HandleOverwrittenPreviousStackGuard(int probeShortFall, __in_z char *stackID) -{ - LIMITED_METHOD_CONTRACT; - - if (! g_probeAssertOnOverrun) - { - return; - } - - ClrDebugState *pState = GetClrDebugState(); - _ASSERTE(pState); - if (pState->IsSONotMainline()) - { - return; - } - - // This prevents infinite loops in this function if we call something that probes. - // Must do it after the check for pState->IsSONotMainline() to give the first invocation - // a chance to run. - SO_NOT_MAINLINE_FUNCTION; - - // This fires at an opening Requires_?K_Stack - // We detected that we were already passed our parent's stack guard. So this guard is - // ok, but our parent's guard is too small. Note that if this test was removed, - // the failure would be detected by our parent's closing Check_Stack. But if we detect it - // here, we have more information. - // - // We can see how many bytes short our parent is and adjust it properly. - char buff[2048]; - buff[0] = '\0'; - - // We don't come in here unless we have a previous guard. - _ASSERTE(m_pPrevGuard != NULL); - - sprintf_s(buff, COUNTOF(buff), - "STACK GUARD VIOLATION\n" - " The%s stack guard being installed in %s at \"%s\" @ %d is already in violation of the previous stack guard.\n" - " The previous guard was installed in %s at \"%s\" @ %d and requested %d pages of stack.\n" - "The stack requested by the previous guard is at least %d pages (%d bytes) short.\n" - MORE_INFO_STRING, stackID ? stackID : "", m_szFunction, m_szFile, m_lineNum, - m_pPrevGuard->m_szFunction, m_pPrevGuard->m_szFile, m_pPrevGuard->m_lineNum, m_pPrevGuard->m_numPages, - probeShortFall/GetOsPageSize() + (probeShortFall%GetOsPageSize() ? 1 : 0), probeShortFall); - - LOG((LF_EH, LL_INFO100000, "%s", buff)); - - DbgAssertDialog((char *)m_szFile, m_lineNum, buff); -} - -void BaseStackGuard::HandleOverwrittenCurrentStackGuard(void *pGuard, int shortFall, __in_z char *stackID) -{ - ( (BaseStackGuard *)pGuard)->HandleOverwrittenCurrentStackGuard(shortFall, stackID); -} - -void BaseStackGuard::HandleOverwrittenCurrentStackGuard(int probeShortFall, __in_z char *stackID) -{ - DEBUG_ONLY_FUNCTION; - - if (! g_probeAssertOnOverrun) - { - return; - } - - // This fires during probe invariant validation. - // We detected that our current stack was already past the current probe depth. Note that if this - // test were removed, the failure should be detected the current guard's closing Check_Stack. - // But if we detect it here, we have more information. - // - // We can see how many bytes short the guard is and adjust it properly. - char buff[2048]; - buff[0] = '\0'; - - sprintf_s(buff, COUNTOF(buff), - "STACK GUARD VIOLATION\n\n" - "The%s stack guard installed in %s at \"%s\" @ %d has been violated\n\n" - "The guard requested %d pages of stack and is at least %d pages (%d bytes) short.\n" - MORE_INFO_STRING, stackID ? stackID : "", m_szFunction, m_szFile, m_lineNum, m_numPages, - probeShortFall/GetOsPageSize() + (probeShortFall%GetOsPageSize() ? 1 : 0), probeShortFall); - - LOG((LF_EH, LL_INFO100000, buff)); - - DbgAssertDialog((char *)m_szFile, m_lineNum, buff); -} - -//----------------------------------------------------------------------------- -// Function to do the actual touching of memory during probing, so we can have -// a good approximation of the address we should be overflowing at. -//----------------------------------------------------------------------------- -static __declspec(noinline) void PlaceMarker(UINT_PTR *pMarker) -{ - LIMITED_METHOD_CONTRACT; - *pMarker = STACK_COOKIE_VALUE; -} - - -StackGuardDisabler::StackGuardDisabler() -{ - LIMITED_METHOD_CONTRACT; - BaseStackGuard *pGuard = BaseStackGuard::GetCurrentGuard(); - - if (pGuard == NULL || !BaseStackGuard::IsProbeGuard(pGuard) || !pGuard->Enabled()) - { - // If there's no guard or its a boundary guard, there's nothing to do - m_fDisabledGuard = FALSE; - return; - } - - // If the guard is currently enabled, then we'll need to change the page protection - pGuard->UndoPageProtectionInDebugger(); - pGuard->DisableGuard(); - m_fDisabledGuard = TRUE; -}// StackGuardDisabler - -void StackGuardDisabler::NeverRestoreGuard() -{ - m_fDisabledGuard = FALSE; -} - -StackGuardDisabler::~StackGuardDisabler() -{ - WRAPPER_NO_CONTRACT; - if (m_fDisabledGuard) - { - BaseStackGuard::RestoreCurrentGuard(TRUE); - } -}// ~StackProbeDisabler - -//----------------------------------------------------------------------------- -// BaseStackGuard::RestoreCurrentGuard -// -// Function to restore the current marker's cookie after an EH. -// -// During an exception, we cannot restore stack guard cookies as we unwind our stack guards -// because the stack has not been unwound and we might corrupt it. So we just pop off our -// guards as we go and deal with restoring the cookie after the exception. -// There are two cases: -// -// 1) the exception is caught outside the EE -// 2) the exception is caught in the EE -// -// Case 1: If we catch the exception outside the EE, then the boundary guard that we installed before -// leaving the EE will still be intact, so we have no work to do. -// -// Case 2: If we caught the exception in the EE, then on EX_END_CATCH, after we have unwound the stack, we need to -// restore the cookie for the topmost stack guard. That is what RestoreCurrentGuard does. -// -//----------------------------------------------------------------------------- -void BaseStackGuard::RestoreCurrentGuard(BOOL fWasDisabled) -{ - if (!IsStackProbingEnabled()) - { - // nothing to do - return; - } - - LPVOID pSP = (LPVOID)GetCurrentSP(); - BaseStackGuard *pGuard = GetCurrentGuard(); - - if (pGuard == NULL || !IsProbeGuard(pGuard)) - { - // If there's no guard or its a boundary guard, there's nothing to do - // Just set state to SO-tolerant and quit. - GetClrDebugState()->SetSOTolerance(); - return; - } - - if (reinterpret_cast<LPVOID>(pGuard->m_pMarker) > pSP) - { - // We have caught an exception while processing an exception. So can't restore the marker and must - // wait until the catcher of the original exception handles it. - if (!IsBackoutCalledForEH((BYTE *)(pGuard), static_cast<BYTE *>(pSP))) - { - // verfiy that really are processing an exception. We could have some false positives here, but in - // general this is a good check. - _ASSERTE(!"After an exception was caught, we couldn't restore the marker because it is greater than the SP\n" - "This should only happen if we caught a nested exception when already processing an exception, but" - " the distance between the SP and the probe does not indicate an exception is in flight."); - } - return; - } - - // Reset the SO-tolerance state - - // We should never get here with a guard beyond the current SP - _ASSERTE(reinterpret_cast<LPVOID>(pGuard) > pSP); - - LOG((LF_EH, LL_INFO100000, "BSG::RSG: G: %p D: %d \n", pGuard, pGuard->m_depth)); - - // If we have EX_TRY {EX_TRY {...}EX_CATCH{...}EX_END_CATCH}EX_CATCH{...}EX_END_CATCH, - // the inner EX_END_CATCH will mark the current guard protected. When we reach the - // outer EX_END_CATCH, we will AV when placing marker. - pGuard->UndoPageProtectionInDebugger(); - if (fWasDisabled) - pGuard->EnableGuard(); - // Replace the marker for the current guard - PlaceMarker(pGuard->m_pMarker); - - // Protect marker page in debugger if we need it - pGuard->ProtectMarkerPageInDebugger(); - GetClrDebugState()->ResetSOTolerance(); - pGuard->m_fEHInProgress = FALSE; -} - -//----------------------------------------------------------------------------- -// This places a marker outside the bounds of a probe. We don't want to use -// PlaceMarker because that is how we detect if a proper SO was triggered (via -// StackProbeContainsIP -//----------------------------------------------------------------------------- -static __declspec(noinline) void PlaceMarkerBeyondProbe(UINT_PTR *pMarker) -{ - *pMarker = STACK_COOKIE_VALUE; -} - -//--------------------------------------------------------------------------------------------- -// Determine if we should check integrity of previous cookie. Only check if the previous was a probe guard. -//--------------------------------------------------------------------------------------------- -inline BOOL BaseStackGuard::ShouldCheckPreviousCookieIntegrity() -{ - WRAPPER_NO_CONTRACT; - if (m_pPrevGuard == NULL || - IsBoundaryGuard(m_pPrevGuard) || - m_pPrevGuard->m_pMarker==NULL || - m_pPrevGuard->m_fEHInProgress || - !m_pPrevGuard->Enabled()) - { - return FALSE; - } - return TRUE; -} - -//--------------------------------------------------------------------------------------------- -// Determine if we should check integrity of this cookie. -//--------------------------------------------------------------------------------------------- -inline BOOL BaseStackGuard::ShouldCheckThisCookieIntegrity() -{ - WRAPPER_NO_CONTRACT; - // We only need to check if this is a probe guard and it has a non-null marker. - // Anything else, we don't care about. - return IsProbeGuard(this) && m_pMarker != NULL && Enabled(); -} - -BOOL BaseStackGuard::RequiresNStackPages(unsigned int n, BOOL fThrowsOnSO) -{ - WRAPPER_NO_CONTRACT; - - return RequiresNStackPagesInternal(n, fThrowsOnSO); -} - -BOOL BaseStackGuard::RequiresNStackPagesThrowing(unsigned int n) -{ -// STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_THROWS; - STATIC_CONTRACT_MODE_ANY; - STATIC_CONTRACT_SO_TOLERANT; - STATIC_CONTRACT_GC_NOTRIGGER; - - return RequiresNStackPagesInternal(n, TRUE); -} - -BOOL BaseStackGuard::RequiresNStackPagesNoThrow(unsigned int n) -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_MODE_ANY; - STATIC_CONTRACT_SO_TOLERANT; - STATIC_CONTRACT_GC_NOTRIGGER; - - return RequiresNStackPagesInternal(n, FALSE); -} - -//----------------------------------------------------------------------------- -// Place guard in stack. -//----------------------------------------------------------------------------- -BOOL BaseStackGuard::RequiresNStackPagesInternal(unsigned int n, BOOL fThrowOnSO) -{ - CONTRACTL - { - DISABLED(THROWS); - GC_NOTRIGGER; - MODE_ANY; - SO_TOLERANT; - } - CONTRACTL_END; - - BOOL fRet; - - // Temporarily initialize the exception occurred flag - m_exceptionOccurred = FALSE; - - // Code below checks if there's a Thread, and exits immediately if not. - // So the rest of the function rightly assumes there is a Thread - BEGIN_GETTHREAD_ALLOWED; - - // only probe on managed threads. No thread, no probe. - if (! IsStackProbingEnabled() || GetThread() == NULL) - { - return TRUE; - } - - // Don't try to probe if we are checking backout and there are active backout markers on - // the stack to avoid collision - if (g_EnableBackoutStackValidation) - { - if ((!(GetClrDebugState()->GetStackMarkerStack().IsEmpty())) - && (!(GetClrDebugState()->GetStackMarkerStack().IsDisabled()))) - { - return TRUE; - } - } - - if (n <= 1) - { - // Our calculation below doesn't handle 1-page probes. - _ASSERTE(!"RequiresNStackPages called with a probe amount less than 2"); - } - - // Retrieve the current stack pointer which will be used to calculate the marker. - LPVOID pStack = (LPVOID)GetCurrentSP(); - - // Setup some helpful debugging information. Get our caller's ip. This is useful for debugging (so we can see - // when the previous guard was set). - m_UniqueId = g_UniqueId++; - m_numPages = n; - - // Get the address of the last few bytes on the penultimate page we probed for. This is slightly early than the probe point, - // but gives us more conservatism in our overrun checking. ("Last" here means the bytes with the smallest address.) - m_pMarker = ((UINT_PTR*)pStack) - (GetOsPageSize() / sizeof(UINT_PTR) * (n-1)); - m_pMarker = (UINT_PTR*)((UINT_PTR)m_pMarker & ~(GetOsPageSize() - 1)); - - // Grab the previous guard, if any, and update our depth. - m_pPrevGuard = GetCurrentGuard(); - - if (m_pPrevGuard == NULL) - { - m_depth = 0; - } - else - { - // If we've already got a probe in place that exceeds the reach of this one, then - // don't install this one. This avoids problems where we've installed an entry point - // probe and then called into a function that happens to do an interior probe. If we - // install the interior probe, then we effectively lose our deep entry point probe - // and end up with probe overrun violations. Check for it being a probe guard - // because boundary guards will always have 0 markers and we'd never probe - // after a boundary guard otherwise. - if (IsProbeGuard(m_pPrevGuard) && m_pPrevGuard->m_pMarker < m_pMarker) - { - return TRUE; - } - m_depth = m_pPrevGuard->m_depth + 1; - - // We need to undo the page protection that we setup when we put the previous guard in place so we don't - // trip over it with this guard. Also, track that we came next. - if (IsProbeGuard(m_pPrevGuard) && m_pPrevGuard->m_pMarker != NULL) - { - m_pPrevGuard->UndoPageProtectionInDebugger(); - m_pPrevGuard->m_szNextFunction = m_szFunction; - m_pPrevGuard->m_szNextFile = m_szFile; - m_pPrevGuard->m_nextLineNum = m_lineNum; - } - } - - if (ShouldCheckPreviousCookieIntegrity()) - { - UINT_PTR *approxSP = (UINT_PTR*)GetCurrentSP(); - if (approxSP <= m_pPrevGuard->m_pMarker) - { - UINT_PTR uProbeShortFall = (char*)m_pPrevGuard->m_pMarker - (char*)approxSP; - _ASSERTE(FitsIn<int>(uProbeShortFall)); - HandleOverwrittenPreviousStackGuard(static_cast<int>(uProbeShortFall), NULL); - } - } - - m_eInitialized = cPartialInit; - - fRet = DoProbe(m_numPages, fThrowOnSO); - END_GETTHREAD_ALLOWED; - return fRet; -} - -BOOL BaseStackGuard::DoProbe(unsigned int n, BOOL fThrowOnSO) -{ - CONTRACTL - { - DISABLED(THROWS); - MODE_ANY; - WRAPPER(GC_TRIGGERS); - SO_TOLERANT; - } - CONTRACTL_END; - - if (! IsStackProbingEnabled() || m_eInitialized != cPartialInit) - { - return TRUE; - } - - LOG((LF_EH, LL_INFO100000, "BSG::DP: %d pages, depth %d, probe 0x%p, fcn %s, prev 0x%p\n", - m_numPages, m_depth, this, this->m_szFunction, m_pPrevGuard)); - - // For cases where have a separate call to DoProbe, make sure the probe amounts match. - _ASSERTE(n == m_numPages); - - // We'll probe for 12 pages + 4 for cleanup.... we'll just put our marker at the 12 page point. - unsigned int nPagesToProbe = n + static_cast<unsigned int>(ADJUST_PROBE(BACKOUT_CODE_STACK_LIMIT)); - - Thread *pThread = GetThread(); - - // We already checked in RequiresNPagesStack that we've got a thread. But ASSERT just to - // be sure. - _ASSERTE(pThread); - - // Check if we have enough space left in the stack - if (fThrowOnSO) - { - RetailStackProbe(nPagesToProbe, pThread); - } - else if (! RetailStackProbeNoThrow(nPagesToProbe, pThread)) - { - return FALSE; - } - - // The fault injection framework will tell us when it want to inject - // an SO. If it returns TRUE, then inject an SO depending on the fThrowOnSO flag - if (ReportProbeToFaultInjectionFramework() == TRUE) - { - if (fThrowOnSO) - { - COMPlusThrowSO(); - } - // return probe failure (ie SO) if not in a throwing probe - return FALSE; - } - - LOG((LF_EH, LL_INFO100000, "BSG::DP: pushing to 0x%p\n", m_pMarker)); - - // See if we're able to get a TLS slot to mark our guard page - HRESULT hr = PrepGuard(); - - // Since we can be here only with a valid managed thread object, - // it will already have its TLS setup. Thus, accessing TLS in PrepGuard - // call above shouldn't fail. - _ASSERTE(SUCCEEDED(hr)); - - // make sure the guard page is beyond the marker page, otherwise we could AV or when the guard - // page moves up, it could wipe out our debugger page protection - UINT_PTR *sp = (UINT_PTR*)GetCurrentSP(); - while (sp >= m_pMarker) - { - sp -= (GetOsPageSize() / sizeof(UINT_PTR)); - *sp = NULL; - } - - // Write the cookie onto the stack. - PlaceMarker(m_pMarker); - - // We'll protect the page where we put the marker if a debugger is attached. That way, you get an AV right away - // when you go past the guard when running under a debugger. - ProtectMarkerPageInDebugger(); - - // Mark that we're initialized (and didn't get interupted from an exception) - m_eInitialized = cInit; - - // Initialize the exception occurred flag - m_exceptionOccurred = TRUE; - - // setup flag to tell if we're unwinding due to an exception - m_fEHInProgress = FALSE; - - // By this point, everything is working, so go ahead and hook up. - SetCurrentGuard(this); - - return TRUE; -} - - -//----------------------------------------------------------------------------- -// PopGuardForEH -// -// If we are being popped during an EH unwind, our cookie is likely corrupt so we can't check it. -// So just pop ourselves off the stack and return. We will restore the markers -// after we've caught the exception. -// -// We also set the EHInProgress bit on the previous guard to indicate that the -// current guard was unwound during EH and couldn't restore the previous guard's -// cookie. -// -// Also need to clear the protection bit as go down because it will no -// longer be protected. -//----------------------------------------------------------------------------- -void BaseStackGuard::PopGuardForEH() -{ - LIMITED_METHOD_CONTRACT; - // If we've protected this page, undo the protection - UndoPageProtectionInDebugger(); - - if (m_pPrevGuard) - { - m_pPrevGuard->m_fEHInProgress = TRUE; - - // Indicate that we haven't reprotected the previous guard - m_pPrevGuard->m_fProtectedStackPage = FALSE; - } - // Mark it as unwound for EH. This is for debugging purposes only so we - // know how it was popped. - m_eInitialized = cEHUnwound; - SetCurrentGuard(m_pPrevGuard); -} - -//----------------------------------------------------------------------------- -// Check guard in stack -// This must be called 1:1 with RequiresNPagesStack, else: -// - the function's stack cookie isn't restored -// - the stack chain in TLS gets out of wack. -//----------------------------------------------------------------------------- -void BaseStackGuard::CheckStack() -{ - WRAPPER_NO_CONTRACT; - - if (! IsStackProbingEnabled() || m_eInitialized != cInit) - { - return; - } - - // If we are being popped during an EH unwind, our cookie is likely corrupt so we can't check it. - // So just pop ourselves off the stack and return. We will restore the markers - // after we've caught the exception. - if (DidExceptionOccur()) - { - // We may not be the topmost in the stack, but we'd better not be called when we've already - // unwound the stack past this guy. - _ASSERTE(GetCurrentGuard() <= this); - - // Make sure that if we didn't get to the END_SO_INTOLERANT_CODE that the stack usage - // indicates an exception. This is only a rough check - we might miss some cases where the - // stack grew a lot between construction and descrution of the guard. However, it will - // catch most short-circuits. - if (!IsBackoutCalledForEH((BYTE *)(this), static_cast<BYTE *>((LPVOID)GetCurrentSP()))) - { - _ASSERTE(!"Short-circuit of END_SO_INTOLERANT_CODE detected. You cannot short-cirtuit return from an SO-intolerant region"); - } - - LOG((LF_EH, LL_INFO100000, "BSG::CS on EH path sp 0x %p popping probe 0x%p depth %d \n", GetCurrentSP(), this, m_depth)); - PopGuardForEH(); - return; - } - - LOG((LF_EH, LL_INFO100000, "BSG::CS checking probe 0x%p depth %d \n", this, m_depth)); - - // if we aren't being unwound during EH, then we shouldn't have our EHInProgress bit set. That - // means we caught the exception in the EE and didn't call RestoreGuard or we missed a SO-tolerant - // transition out of the EE and the exception occurred above us. - _ASSERTE(m_fEHInProgress == FALSE); - - // we should only ever be popping ourselves if we are not on the EH unwind path - _ASSERTE(GetCurrentGuard() == this); - - // Can have 0-sized probes for cases where have an entry that is small enough not to need a probe. But still - // need to put something in place for the boundary probe assertions to work properly. So just remove it and - // don't do any cookie checking. - if (m_numPages == 0) - { - // Just unhook our guard from the chain. We're done. 0-page probes don't have anything preceding them. - ResetCurrentGuard(m_pPrevGuard); - return; - } - - // We need to undo the page protection that we setup when we put the guard in place. - UndoPageProtectionInDebugger(); - - CheckMarkerIntegrity(); - - RestorePreviousGuard(); -} - -void BaseStackGuard::CheckMarkerIntegrity() -{ - LIMITED_METHOD_CONTRACT; - - if (m_pMarker == 0) - { - return; - } - - // Make sure our cookie is still on the stack where it belongs. - if (ShouldCheckThisCookieIntegrity() && IsMarkerOverrun(m_pMarker)) - { - HandleOverwrittenThisStackGuard(NULL); - } -} - - -void BaseStackGuard::RestorePreviousGuard() -{ - WRAPPER_NO_CONTRACT; - - if (! IsProbeGuard(m_pPrevGuard) || !m_pPrevGuard->Enabled()) - { - LOG((LF_EH, LL_INFO100000, "BSG::RPG depth %d, probe 0x%p, prev 0x%p not probe\n", - m_depth, this, m_pPrevGuard)); - // Unhook our guard from the chain. - ResetCurrentGuard(m_pPrevGuard); - return; - } - - if (m_pPrevGuard->m_fEHInProgress) - { - // If the marker was lost during exception processing, we cannot restore it and it will be restored on the catch. - // This can happen if we were partway through an EH unwind and then called something that probed. We'll have unwound our - // probe guards but won't have been able to put the cookie back, and we're still in that same position. - LOG((LF_EH, LL_INFO100000, "BSG::RPG depth %d, probe 0x%p, EH in progress, not resetting prev 0x%p\n", - m_depth, this, m_pPrevGuard)); - // Unhook our guard from the chain. - ResetCurrentGuard(m_pPrevGuard); - return; - } - - if (m_pPrevGuard->m_pMarker == NULL) - { - // Previous guard had no marker. - // We're done, so just unhook ourselves from the chain and leave. - ResetCurrentGuard(m_pPrevGuard); - } - - // Restore last cookie, so that our previous guard will be able to properly check whether it gets overwritten. Note: - // we don't restore the previous cookie if we overwrote it with this guard. Doing so, by definition, corrupts the - // stack. Its better to have the previous guard report the over-write. - PlaceMarker(m_pPrevGuard->m_pMarker); - LOG((LF_EH, LL_INFO100000, "BSG::RPG depth %d, probe 0x%p " - "for prev 0x%p at 0x%p in %s\n", - m_depth, this, m_pPrevGuard, m_pPrevGuard->m_pMarker, m_pPrevGuard->m_szFunction)); - // And, of course, restore the previous guard's page protection (if it had done any.) - if (m_pPrevGuard->m_fProtectedStackPage) - { - m_pPrevGuard->ProtectMarkerPageInDebugger(); - } - - // Mark it as unwound on normal path. This is for debugging purposes only so we - // know how it was popped. - m_eInitialized = cUnwound; - - // Unhook our guard from the chain. - ResetCurrentGuard(m_pPrevGuard); -} - -void BaseStackGuard::ProtectMarkerPageInDebugger(void *pGuard) -{ - ((BaseStackGuard *)pGuard)->ProtectMarkerPageInDebugger(); -} - -//----------------------------------------------------------------------------- -// Protect the page where we put the marker if a debugger is attached. That way, you get an AV right away -// when you go past the guard when running under a debugger. -//----------------------------------------------------------------------------- -void BaseStackGuard::ProtectMarkerPageInDebugger() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - SO_TOLERANT; - MODE_ANY; - } - CONTRACTL_END; - - DEBUG_ONLY_FUNCTION; - - if (! (g_ProtectStackPagesInDebugger || g_ProtectStackPagesInDebuggerForProbeAtLine)) - { - return; - } - - DWORD flOldProtect; - - LOG((LF_EH, LL_INFO100000, "BSG::PMP: m_pMarker 0x%p, value 0x%p\n", m_pMarker, *m_pMarker)); - - // We cannot call into host for VirtualProtect. EEVirtualProtect will try to restore previous - // guard, but the location has been marked with PAGE_NOACCESS. -#undef VirtualProtect - BOOL fSuccess = ::VirtualProtect(m_pMarker, 1, PAGE_NOACCESS, &flOldProtect); - _ASSERTE(fSuccess); - -#define VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) \ - Dont_Use_VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) - - m_fProtectedStackPage = fSuccess; -} - - -void BaseStackGuard::UndoPageProtectionInDebugger(void *pGuard) -{ - ((BaseStackGuard *)pGuard)->UndoPageProtectionInDebugger(); -} - -//----------------------------------------------------------------------------- -// Remove page protection installed for this probe -//----------------------------------------------------------------------------- -void BaseStackGuard::UndoPageProtectionInDebugger() -{ - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - SO_TOLERANT; - MODE_ANY; - } - CONTRACTL_END; - - DEBUG_ONLY_FUNCTION; - - if (!m_fProtectedStackPage) - { - return; - } - - _ASSERTE(IsProbeGuard()); - - DWORD flOldProtect; - // EEVirtualProtect installs a BoundaryStackGuard. To avoid recursion, we call - // into OS for VirtualProtect instead. -#undef VirtualProtect - BOOL fSuccess = ::VirtualProtect(m_pMarker, 1, PAGE_READWRITE, &flOldProtect); - _ASSERTE(fSuccess); - - LOG((LF_EH, LL_INFO100000, "BSG::UMP m_pMarker 0x%p\n", m_pMarker)); - // Frankly, if we had protected the stack page, then we shouldn't have gone past the guard, right? :) - _ASSERTE(!Enabled() || !IsMarkerOverrun(m_pMarker)); - -#define VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) \ - Dont_Use_VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect) -} - -void BaseStackGuard::InitProbeReportingToFaultInjectionFramework() -{ - WRAPPER_NO_CONTRACT; - - if (! g_pConfig->ShouldInjectFault(INJECTFAULT_SO)) - { - return; - } - - m_hProbeCallBack = CLRLoadLibrary(MAKEDLLNAME_W(W("FaultHostingLayer"))); - if (!m_hProbeCallBack) { - fprintf(stderr, "StackProbing: Failed to load " MAKEDLLNAME_A("FaultHostingLayer") ". LastErr=%d\n", - GetLastError()); - return; - } - - m_pfnProbeCallback = (ProbeCallbackType)GetProcAddress(m_hProbeCallBack, "StackProbeCallback"); - if (!m_pfnProbeCallback) { - fprintf(stderr, "StackProbing: Couldn't find StackProbeCallback() in FaultHostingLayer\n"); - return; - } -} - -// The fault injection framework will return TRUE if we should -// inject an SO at the point of the current probe. -BOOL BaseStackGuard::ReportProbeToFaultInjectionFramework() -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_SO_TOLERANT; - STATIC_CONTRACT_MODE_ANY; - - if (! g_pConfig->ShouldInjectFault(INJECTFAULT_SO) || ! m_pfnProbeCallback) - { - return FALSE; - } - - // FORBIDGC_LOADER_USE_ENABLED says we are only doing a minimal amount of work and will not - // update global state (just read it.) Code running in this state cannot tolerate a fault injection. - if (FORBIDGC_LOADER_USE_ENABLED()) - { - return FALSE; - } - - // For codepaths that are not mainline or are debug only, we don't care about fault injection because - // taking an SO here won't matter (or can't happen). However, we'd like to still probe on those paths - // just to give us more conservative probe coverage, so we still do the probe, just not the fault injection. - ClrDebugState *pDebugState = GetClrDebugState(); - if (pDebugState && pDebugState->IsSONotMainline() || pDebugState->IsDebugOnly()) - { - return FALSE; - } - - - // Faults injected into the default domain are process fatal. Probing is still going to occur - // but we never trigger fault injection. - { - //Attempting to figure out if we are in the default domain will trigger SO probes so - // we temporarily mark ourselves SONotMainline during the check to prevent recursive probes - SO_NOT_MAINLINE_REGION(); - Thread *pThread = GetThreadNULLOk(); - if (pThread && pThread->GetDomain(TRUE)->IsDefaultDomain()) - { - return FALSE; - } - } - - return m_pfnProbeCallback(m_lineNum, m_szFile); -} - -void BaseStackGuard::SetCurrentGuard(BaseStackGuard* pGuard) -{ - WRAPPER_NO_CONTRACT; - - ClrFlsSetValue(g_CurrentStackGuardTlsIdx, pGuard); - - Thread * pThread = GetThreadNULLOk(); - if (pThread) - { - // For faster access, store the guard in the thread object, if available - pThread->SetCurrentStackGuard(pGuard); - } -} - -// Reset the current guard state back to this one's -void BaseStackGuard::ResetCurrentGuard(BaseStackGuard* pGuard) -{ - WRAPPER_NO_CONTRACT; - - SetCurrentGuard(pGuard); -} - -// This puts a boundary probe in the list when we leave the EE -DEBUG_NOINLINE void BoundaryStackGuard::Push() -{ - SCAN_SCOPE_BEGIN; - ANNOTATION_FN_SO_TOLERANT; - - if (! IsStackProbingEnabled()) - { - return; - } - - - m_isBoundaryGuard = TRUE; - m_pPrevGuard = GetCurrentGuard(); - - if (m_pPrevGuard) - { - // @todo can remove the check for IsProbeGuard when have all the probes in place - if (IsProbeGuard(m_pPrevGuard)) - { - // ensure that the previous probe was sufficiently large - if (ShouldCheckPreviousCookieIntegrity()) - { - // Grab an approximation of our current stack pointer. - void *approxStackPointer = (LPVOID)GetCurrentSP(); - - if (((UINT_PTR*) approxStackPointer <= m_pPrevGuard->Marker())) - { - UINT_PTR uProbeShortFall = (char*)m_pPrevGuard->Marker() - (char*)this; - _ASSERTE(FitsIn<int>(uProbeShortFall)); - HandleOverwrittenPreviousStackGuard(static_cast<int>(uProbeShortFall), NULL); - } - } - m_pPrevGuard->UndoPageProtectionInDebugger(); // undo previuos guard's page protection - m_pPrevGuard->m_szNextFunction = m_szFunction; // track that we came next - m_pPrevGuard->m_szNextFile = m_szFile; - m_pPrevGuard->m_nextLineNum= m_lineNum; - } - m_depth = m_pPrevGuard->Depth(); // don't increment, but record so can transfer to next probe - } - LOG((LF_EH, LL_INFO100000, "BNSG::PS probe 0x%p, depth %d, prev 0x%p in %s\n", - this, m_depth, m_pPrevGuard, m_pPrevGuard ? m_pPrevGuard->FunctionName() : NULL)); - - // See if we're able to get a TLS slot to mark our guard page. If not, this will just be an unitialized - // guard. This generally happens in callbacks to the host before the EE infrastructure is set up on - // the thread, so there won't be interesting probes to protect anyway. - if (FAILED(PrepGuard())) - { - return; - } - - // Mark that we're initialized (and didn't get interupted from an exception) - m_eInitialized = cInit; - - // setup flag to tell if we're unwinding due to an exception - m_exceptionOccurred = TRUE; - - SetCurrentGuard(this); -} - - - -// Pop the boundary probe and reset the original probe's cookie when -// return into the EE -DEBUG_NOINLINE void BoundaryStackGuard::Pop() -{ - SCAN_SCOPE_END; - - if (! IsStackProbingEnabled() || m_eInitialized != cInit) - { - return; - } - - // If we are being popped during an EH unwind, we cannot restore the probe cookie because it will - // corrupt the stack. So just pop ourselves off the stack and return. We will restore the markers - // after we've caught the exception. - if (DidExceptionOccur()) - { - // We may not be the topmost in the stack, but we'd better not be called when we've already - // unwound the stack past this guy. - _ASSERTE(GetCurrentGuard() <= this); - - // Make sure that if we didn't get to the END_SO_TOLERANT_CODE that the stack usage - // indicates an exception. This is only a rough check - we might miss some cases where the - // stack grew a lot between construction and descrution of the guard. However, it will - // catch most short-circuits. - if (!IsBackoutCalledForEH((BYTE *)(this), static_cast<BYTE *>((LPVOID)GetCurrentSP()))) - { - _ASSERTE(!"Short-circuit of END_SO_TOLERANT_CODE detected. You cannot short-cirtuit return from an SO-tolerant region"); - } - - LOG((LF_EH, LL_INFO100000, "BNSG::PP popping on EH path 0x%p depth %d \n", this, m_depth)); - PopGuardForEH(); - return; - } - - LOG((LF_EH, LL_INFO100000, "BNSG::PP 0x%p depth %d restoring CK at 0x%p " - " probe 0x%p in %s\n", - this, m_depth, (!IsProbeGuard(m_pPrevGuard) ? 0 : m_pPrevGuard->Marker()), - m_pPrevGuard, m_pPrevGuard ? m_pPrevGuard->FunctionName() : NULL)); - - // we should only ever be popping ourselves - _ASSERTE(GetCurrentGuard() == this); - - RestorePreviousGuard(); -} - - -// -// IsBackoutCalledForEH -// -// Uses heuristics to determines whether the backout code is being called on an EH path or -// not based on the original SP and the SP when the backout code is called. -// -// origSP: The SP when the mainline code was called. For example, the SP of a ctor or code in a try block -// -// backoutSP: The SP when the backout code is called. -// -// Returns: boolean indicating whether or not the backout code is being called on an EH path. -// -BOOL IsBackoutCalledForEH(BYTE *origSP, - BYTE *backoutSP) -{ - // We need to determine if we are being called in the normal or exception path. (Sure would be - // nice if the CRT would tell us.) We use the stack pointer to determine this. On the normal path - // the stack pointer should be not far from the this pointer, whereas on the exception path it - // will typically be a lot higher up the stack. We will make the following assumptions: - // - // 1) on EH path the OS has to push a context onto the stack. So the SP will be increased by - // at least the size of a context when calling a destructor through EH path. - // - // 2) the CRT will use minimal stack space to call a destructor. This is assumed to be less - // than the size of a context. - // - // Caveats: - // - // 1) If there is less than a context on the stack on the EH path, we will miss the fact that - // an exception occurred - // - // 2) If the CRT uses near the size of a context before calling the destructor in the normal case, - // we will assume we've got an exception and ASSERT. - // - // So if we arrive at our backout code and the SP is more than the size of a context beyond the original SP, - // we assume we are on an EH path. - // - return (origSP - sizeof(CONTEXT)) > backoutSP; - -} - - -DebugSOIntolerantTransitionHandlerBeginOnly::DebugSOIntolerantTransitionHandlerBeginOnly(EEThreadHandle thread) -{ - SCAN_SCOPE_BEGIN; - ANNOTATION_FN_SO_INTOLERANT; - - // save the SP so that we can check if the dtor is being called with a much bigger one - m_ctorSP = (char *)GetCurrentSP(); - m_clrDebugState = GetClrDebugState(); - m_prevSOTolerantState = m_clrDebugState->BeginSOIntolerant(); -} - -DebugSOIntolerantTransitionHandlerBeginOnly::~DebugSOIntolerantTransitionHandlerBeginOnly() -{ - SCAN_SCOPE_END; - - // A DebugSOIntolerantTransitionHandlerBeginOnly is instantiated only for cases where we will not see - // an exception. So the desctructor should never be called on an exception path. This will check if - // we are handling an exception and raise an assert if so. - - // - // We need to determine if we are being called in the normal or exception path. (Sure would be - // nice if the CRT would tell us.) We use the stack pointer to determine this. On the normal path - // the stack pointer should be not far from the this pointer, whereas on the exception path it - // will typically be a lot higher up the stack. We will make the following assumptions: - // - // 1) on EH path the OS has to push a context onto the stack. So the SP will be increased by - // at least the size of a context when calling a destructor through EH path. - // - // 2) the CRT will use minimal stack space to call a destructor. This is assumed to be less - // than the size of a context. - // - // Caveats: - // - // 1) If there is less than a context on the stack on the EH path, we will miss the fact that - // an exception occurred - // - // 2) If the CRT uses near the size of a context before calling the destructor in the normal case, - // we will assume we've got an exception and ASSERT. - // - // So if we arrive at our destructor and the SP is within the size of a context beyond the SP when - // we called the ctor, we assume we are on normal path. - if ((m_ctorSP - sizeof(CONTEXT)) > (LPVOID)GetCurrentSP()) - { - _ASSERTE(!"An exception cannot leak through a SO_INTOLERANT_CODE_NOTHROW boundary"); - } - - m_clrDebugState->SetSOTolerance(m_prevSOTolerantState); -} -#endif // STACK_GUARDS_DEBUG - -#if defined(FEATURE_STACK_PROBE) && defined(_DEBUG) - -#undef __STACKPROBE_inl__ - -#define INCLUDE_RETAIL_STACK_PROBE - -#include "stackprobe.inl" - -#endif // defined(FEATURE_STACK_PROBE) && defined(_DEBUG) - -#if 0 //FEATURE_FUSION_FAST_CLOSURE - was too buggy at the end of Dev10, not used since then. Delete it after Dev12 if it is still not fixed and used. - -#ifdef FEATURE_STACK_PROBE -// This is a helper that fusion (CFastAssemblyBindingClosure) uses to -// do an interior stack probe. -HRESULT InteriorStackProbeNothrowCheckThread() -{ - STATIC_CONTRACT_NOTHROW; - STATIC_CONTRACT_GC_NOTRIGGER; - STATIC_CONTRACT_MODE_ANY; - - HRESULT hr = S_OK; - INTERIOR_STACK_PROBE_NOTHROW_CHECK_THREAD(hr = E_OUTOFMEMORY;); - END_INTERIOR_STACK_PROBE; - - return hr; -} -#endif - -#endif //0 - FEATURE_FUSION_FAST_CLOSURE |